From 90d866f6f724d7394c139825e50e4cb5b124503c Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Thu, 11 Jan 2024 11:29:53 +0100 Subject: [PATCH 0001/1670] Bootstrap project repo --- README.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000000..a8858e975f --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# Welcome to OpenID Connect Broker + +[actions-image]: https://github.com/ubuntu/oidc-broker/actions/workflows/ci.yaml/badge.svg +[actions-url]: https://github.com/ubuntu/oidc-broker/actions?query=workflow%3ACI + +[license-image]: https://img.shields.io/badge/License-GPL3.0-blue.svg + +[codecov-image]: https://codecov.io/gh/ubuntu/oidc-broker/graph/badge.svg +[codecov-url]: https://codecov.io/gh/ubuntu/oidc-broker + +[user-documentation-image]: https://pkg.go.dev/github.com/ubuntu/oidc-broker +[user-documentation-url]: https://pkg.go.dev/github.com/ubuntu/oidc-broker + +[goreport-image]: https://goreportcard.com/badge/github.com/ubuntu/oidc-broker +[goreport-url]: https://goreportcard.com/report/github.com/ubuntu/oidc-broker + +[![Code quality][actions-image]][actions-url] +[![License][license-image]](LICENSE) +[![Code coverage][codecov-image]][codecov-url] +[![User Documentation][user-documentation-image]][user-documentation-url] +[![Go Report Card][goreport-image]][goreport-url] + +This is the code repository for OpenID Connect (OIDC) broker. It is used in conjunction with Ubuntu authentication daemon authd. + +This project contains specific code for different OpenID Connect providers. We build different binaries based on build tags. + +TODO: More general description about the project. + +For general details, including [installation](TODO link to installation instruction) and [Getting started](TODO link to getting started instructions) guides, head over to our [PROJECT_TODO documentation](link to project documentation). + +## Troubleshooting + +TODO: Add details on how to debug this project, where to increase verbosity, how to find logs, how to run in debug mode. + +## Get involved + +This is an [open source](LICENSE) project and we warmly welcome community contributions, suggestions, and constructive feedback. If you're interested in contributing, please take a look at our [Contribution guidelines](CONTRIBUTING.md) first. + +- to report an issue, please file a bug report against our repository, using a bug template. +- for suggestions and constructive feedback, report a feature request bug report, using the proposed template. + +## Get in touch + +We're friendly! We have a community forum at [https://discourse.ubuntu.com](https://discourse.ubuntu.com) where we discuss feature plans, development news, issues, updates and troubleshooting. + +For news and updates, follow the [Ubuntu twitter account](https://twitter.com/ubuntu) and on [Facebook](https://www.facebook.com/ubuntu). From e6af72424cad425764256029c783e4471f90d4fc Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Thu, 11 Jan 2024 11:32:26 +0100 Subject: [PATCH 0002/1670] Add gitignore --- .gitignore | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..f69237766c --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +/oid-broker +cmd/oidc-broker/oidc-broker +/microsoft-entra-id-broker +cmd/oidc-broker/microsoft-entra-id-broker + +# Test binary, built with `go test -c` +*.test +coverage/ + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +vendor/ + +# Go workspace file +go.work From 5b0d7e25d41d44a95befe18c29fd88549621ce08 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Thu, 11 Jan 2024 11:33:33 +0100 Subject: [PATCH 0003/1670] Bootstrap broker code For now, allow to start the broker, exposed on the bus, which can have different names based on the binary name. Allow generic oidc or specific code. Integrate with systemd. We can use a local bus too for easier testing and not using the system one. --- cmd/oidc-broker/daemon/config.go | 104 +++++++ cmd/oidc-broker/daemon/daemon.go | 174 ++++++++++++ cmd/oidc-broker/daemon/daemon_test.go | 351 ++++++++++++++++++++++++ cmd/oidc-broker/daemon/export_test.go | 67 +++++ cmd/oidc-broker/daemon/fs.go | 25 ++ cmd/oidc-broker/daemon/version.go | 24 ++ cmd/oidc-broker/main.go | 81 ++++++ cmd/oidc-broker/main_test.go | 118 ++++++++ go.mod | 40 +++ go.sum | 113 ++++++++ internal/broker/broker.go | 16 ++ internal/broker/microsoft-entra-id.go | 11 + internal/broker/noprovider.go | 11 + internal/brokerservice/brokerservice.go | 114 ++++++++ internal/brokerservice/localbus.go | 32 +++ internal/brokerservice/methods.go | 14 + internal/brokerservice/systembus.go | 18 ++ internal/consts/consts.go | 20 ++ internal/daemon/daemon.go | 82 ++++++ internal/log/log.go | 19 ++ internal/testutils/dbus.go | 102 +++++++ po/embedder.go | 11 + po/fr.po | 0 po/mofiles.go | 7 + 24 files changed, 1554 insertions(+) create mode 100644 cmd/oidc-broker/daemon/config.go create mode 100644 cmd/oidc-broker/daemon/daemon.go create mode 100644 cmd/oidc-broker/daemon/daemon_test.go create mode 100644 cmd/oidc-broker/daemon/export_test.go create mode 100644 cmd/oidc-broker/daemon/fs.go create mode 100644 cmd/oidc-broker/daemon/version.go create mode 100644 cmd/oidc-broker/main.go create mode 100644 cmd/oidc-broker/main_test.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/broker/broker.go create mode 100644 internal/broker/microsoft-entra-id.go create mode 100644 internal/broker/noprovider.go create mode 100644 internal/brokerservice/brokerservice.go create mode 100644 internal/brokerservice/localbus.go create mode 100644 internal/brokerservice/methods.go create mode 100644 internal/brokerservice/systembus.go create mode 100644 internal/consts/consts.go create mode 100644 internal/daemon/daemon.go create mode 100644 internal/log/log.go create mode 100644 internal/testutils/dbus.go create mode 100644 po/embedder.go create mode 100644 po/fr.po create mode 100644 po/mofiles.go diff --git a/cmd/oidc-broker/daemon/config.go b/cmd/oidc-broker/daemon/config.go new file mode 100644 index 0000000000..2b0be1c949 --- /dev/null +++ b/cmd/oidc-broker/daemon/config.go @@ -0,0 +1,104 @@ +package daemon + +import ( + "errors" + "fmt" + "log/slog" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/ubuntu/decorate" + "github.com/ubuntu/oidc-broker/internal/consts" + "github.com/ubuntu/oidc-broker/internal/log" +) + +// initViperConfig sets verbosity level and add config env variables and file support based on name prefix. +func initViperConfig(name string, cmd *cobra.Command, vip *viper.Viper) (err error) { + defer decorate.OnError(&err, "can't load configuration") + + // Force a visit of the local flags so persistent flags for all parents are merged. + //cmd.LocalFlags() // TODO: still necessary? + + // Get cmdline flag for verbosity to configure logger until we have everything parsed. + v, err := cmd.Flags().GetCount("verbosity") + if err != nil { + return fmt.Errorf("internal error: no persistent verbosity flag installed on cmd: %w", err) + } + setVerboseMode(v) + + // Handle configuration. + if v, err := cmd.Flags().GetString("config"); err == nil && v != "" { + vip.SetConfigFile(v) + } else { + vip.SetConfigName(name) + vip.AddConfigPath("./") + vip.AddConfigPath("$HOME/") + vip.AddConfigPath("$SNAP_DATA/") + vip.AddConfigPath(filepath.Join("/etc", cmdName)) + // Add the executable path to the config search path. + if binPath, err := os.Executable(); err != nil { + slog.Warn(fmt.Sprintf("Failed to get current executable path, not adding it as a config dir: %v", err)) + } else { + vip.AddConfigPath(filepath.Dir(binPath)) + } + } + + if err := vip.ReadInConfig(); err != nil { + var e viper.ConfigFileNotFoundError + if errors.As(err, &e) { + slog.Info(fmt.Sprintf("No configuration file: %v.\nWe will only use the defaults, env variables or flags.", e)) + } else { + return fmt.Errorf("invalid configuration file: %w", err) + } + } else { + slog.Info(fmt.Sprintf("Using configuration file: %v", vip.ConfigFileUsed())) + } + + // Handle environment. + vip.SetEnvPrefix(name) + vip.AutomaticEnv() + + // Visit manually env to bind every possibly related environment variable to be able to unmarshall + // those into a struct. + // More context on https://github.com/spf13/viper/pull/1429. + prefix := strings.ToUpper(name) + "_" + for _, e := range os.Environ() { + if !strings.HasPrefix(e, prefix) { + continue + } + + s := strings.Split(e, "=") + k := strings.ReplaceAll(strings.TrimPrefix(s[0], prefix), "_", ".") + if err := vip.BindEnv(k, s[0]); err != nil { + return fmt.Errorf("could not bind environment variable: %w", err) + } + } + + return nil +} + +// installConfigFlag installs a --config option. +func installConfigFlag(cmd *cobra.Command) *string { + return cmd.PersistentFlags().StringP("config", "c", "", "use a specific configuration file") +} + +// SetVerboseMode change ErrorFormat and logs between very, middly and non verbose. +func setVerboseMode(level int) { + //var reportCaller bool + switch level { + case 0: + log.SetLevel(consts.DefaultLevelLog) + case 1: + log.SetLevel(slog.LevelDebug) + case 3: + //reportCaller = true + fallthrough + default: + log.SetLevel(slog.LevelDebug) + } + + //slog.SetReportCaller(reportCaller) +} diff --git a/cmd/oidc-broker/daemon/daemon.go b/cmd/oidc-broker/daemon/daemon.go new file mode 100644 index 0000000000..a5cc8754f6 --- /dev/null +++ b/cmd/oidc-broker/daemon/daemon.go @@ -0,0 +1,174 @@ +// Package daemon represents the oidc broker binary +package daemon + +import ( + "context" + "fmt" + "log/slog" + "os" + "path/filepath" + "runtime" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/ubuntu/decorate" + "github.com/ubuntu/oidc-broker/internal/brokerservice" + "github.com/ubuntu/oidc-broker/internal/consts" + "github.com/ubuntu/oidc-broker/internal/daemon" +) + +// cmdName is the binary name for the agent. +var cmdName = filepath.Base(os.Args[0]) + +// App encapsulate commands and options of the daemon, which can be controlled by env variables and config files. +type App struct { + rootCmd cobra.Command + viper *viper.Viper + config daemonConfig + + daemon *daemon.Daemon + + ready chan struct{} +} + +// only overriable for tests. +type systemPaths struct { + BrokersConf string + Cache string +} + +// daemonConfig defines configuration parameters of the daemon. +type daemonConfig struct { + Verbosity int + Paths systemPaths +} + +// New registers commands and return a new App. +func New() *App { + a := App{ready: make(chan struct{})} + a.rootCmd = cobra.Command{ + Use: fmt.Sprintf("%s COMMAND", cmdName), + Short: fmt.Sprintf("%s authentication broker", cmdName), + Long: fmt.Sprintf("Authentication daemon %s to communicate with our authentication daemon.", cmdName), + Args: cobra.NoArgs, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + // Command parsing has been successful. Returns to not print usage anymore. + a.rootCmd.SilenceUsage = true + + // Set config defaults + systemCache := filepath.Join("/var", "lib", cmdName) + if snapData := os.Getenv("SNAP_DATA"); snapData != "" { + systemCache = filepath.Join(snapData, "cache") + } + a.config = daemonConfig{ + Paths: systemPaths{ + BrokersConf: filepath.Join(consts.DefaultBrokersConfPath, cmdName), + Cache: systemCache, + }, + } + + // Install and unmarshall configuration + if err := initViperConfig(cmdName, &a.rootCmd, a.viper); err != nil { + return err + } + if err := a.viper.Unmarshal(&a.config); err != nil { + return fmt.Errorf("unable to decode configuration into struct: %w", err) + } + + setVerboseMode(a.config.Verbosity) + slog.Debug("Debug mode is enabled") + + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + return a.serve(a.config) + }, + // We display usage error ourselves + SilenceErrors: true, + } + viper := viper.New() + + a.viper = viper + + installVerbosityFlag(&a.rootCmd, a.viper) + installConfigFlag(&a.rootCmd) + + // subcommands + a.installVersion() + + return &a +} + +// serve creates new dbus service on the system bus. This call is blocking until we quit it. +func (a *App) serve(config daemonConfig) error { + ctx := context.Background() + + if err := ensureDirWithPerms(config.Paths.Cache, 0700); err != nil { + close(a.ready) + return fmt.Errorf("error initializing users cache directory at %q: %v", config.Paths.Cache, err) + } + + s, err := brokerservice.New(ctx, cmdName) + if err != nil { + close(a.ready) + return err + } + + var daemonopts []daemon.Option + daemon, err := daemon.New(ctx, s, daemonopts...) + if err != nil { + _ = s.Stop() + close(a.ready) + return err + } + + a.daemon = daemon + close(a.ready) + + return daemon.Serve(ctx) +} + +// installVerbosityFlag adds the -v and -vv options and returns the reference to it. +func installVerbosityFlag(cmd *cobra.Command, viper *viper.Viper) *int { + r := cmd.PersistentFlags().CountP("verbosity", "v" /*i18n.G(*/, "issue INFO (-v), DEBUG (-vv) or DEBUG with caller (-vvv) output") //) + decorate.LogOnError(viper.BindPFlag("verbosity", cmd.PersistentFlags().Lookup("verbosity"))) + return r +} + +// Run executes the command and associated process. It returns an error on syntax/usage error. +func (a *App) Run() error { + return a.rootCmd.Execute() +} + +// UsageError returns if the error is a command parsing or runtime one. +func (a App) UsageError() bool { + return !a.rootCmd.SilenceUsage +} + +// Hup prints all goroutine stack traces and return false to signal you shouldn't quit. +func (a App) Hup() (shouldQuit bool) { + buf := make([]byte, 1<<16) + runtime.Stack(buf, true) + fmt.Printf("%s", buf) + return false +} + +// Quit gracefully shutdown the service. +func (a *App) Quit() { + a.WaitReady() + if a.daemon == nil { + return + } + a.daemon.Quit() +} + +// WaitReady signals when the daemon is ready +// Note: we need to use a pointer to not copy the App object before the daemon is ready, and thus, creates a data race. +func (a *App) WaitReady() { + <-a.ready +} + +// RootCmd returns a copy of the root command for the app. Shouldn't be in general necessary apart when running generators. +func (a App) RootCmd() cobra.Command { + return a.rootCmd +} diff --git a/cmd/oidc-broker/daemon/daemon_test.go b/cmd/oidc-broker/daemon/daemon_test.go new file mode 100644 index 0000000000..46a79f7ddb --- /dev/null +++ b/cmd/oidc-broker/daemon/daemon_test.go @@ -0,0 +1,351 @@ +package daemon_test + +import ( + "bytes" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/ubuntu/oidc-broker/cmd/oidc-broker/daemon" + "github.com/ubuntu/oidc-broker/internal/consts" + "github.com/ubuntu/oidc-broker/internal/testutils" +) + +func TestHelp(t *testing.T) { + a := daemon.NewForTests(t, nil, "--help") + + getStdout := captureStdout(t) + + err := a.Run() + require.NoErrorf(t, err, "Run should not return an error with argument --help. Stdout: %v", getStdout()) +} + +func TestCompletion(t *testing.T) { + a := daemon.NewForTests(t, nil, "completion", "bash") + + getStdout := captureStdout(t) + + err := a.Run() + require.NoError(t, err, "Completion should not start the daemon. Stdout: %v", getStdout()) +} + +func TestVersion(t *testing.T) { + a := daemon.NewForTests(t, nil, "version") + + getStdout := captureStdout(t) + + err := a.Run() + require.NoError(t, err, "Run should not return an error") + + out := getStdout() + + fields := strings.Fields(out) + require.Len(t, fields, 2, "wrong number of fields in version: %s", out) + + want := "authd" + + require.Equal(t, want, fields[0], "Wrong executable name") + require.Equal(t, consts.Version, fields[1], "Wrong version") +} + +func TestNoUsageError(t *testing.T) { + a := daemon.NewForTests(t, nil, "completion", "bash") + + getStdout := captureStdout(t) + err := a.Run() + + require.NoError(t, err, "Run should not return an error, stdout: %v", getStdout()) + isUsageError := a.UsageError() + require.False(t, isUsageError, "No usage error is reported as such") +} + +func TestUsageError(t *testing.T) { + t.Parallel() + + a := daemon.NewForTests(t, nil, "doesnotexist") + + err := a.Run() + require.Error(t, err, "Run should return an error, stdout: %v") + isUsageError := a.UsageError() + require.True(t, isUsageError, "Usage error is reported as such") +} + +func TestCanQuitWhenExecute(t *testing.T) { + t.Parallel() + + a, wait := startDaemon(t, nil) + defer wait() + + a.Quit() +} + +func TestCanQuitTwice(t *testing.T) { + t.Parallel() + + a, wait := startDaemon(t, nil) + + a.Quit() + wait() + + require.NotPanics(t, a.Quit) +} + +func TestAppCanQuitWithoutExecute(t *testing.T) { + t.Skipf("This test is skipped because it is flaky. There is no way to guarantee Quit has been called before run.") + + t.Parallel() + + a := daemon.NewForTests(t, nil) + + requireGoroutineStarted(t, a.Quit) + err := a.Run() + require.Error(t, err, "Should return an error") + + require.Containsf(t, err.Error(), "grpc: the server has been stopped", "Unexpected error message") +} + +func TestAppRunFailsOnComponentsCreationAndQuit(t *testing.T) { + t.Parallel() + // Trigger the error with a cache directory that cannot be created over an + // existing file + + // TODO + t.Fail() + +} + +func TestAppCanSigHupWhenExecute(t *testing.T) { + r, w, err := os.Pipe() + require.NoError(t, err, "Setup: pipe shouldn't fail") + + a, wait := startDaemon(t, nil) + + defer wait() + defer a.Quit() + + orig := os.Stdout + os.Stdout = w + + a.Hup() + + os.Stdout = orig + w.Close() + + var out bytes.Buffer + _, err = io.Copy(&out, r) + require.NoError(t, err, "Couldn't copy stdout to buffer") + require.NotEmpty(t, out.String(), "Stacktrace is printed") +} + +func TestAppCanSigHupAfterExecute(t *testing.T) { + r, w, err := os.Pipe() + require.NoError(t, err, "Setup: pipe shouldn't fail") + + a, wait := startDaemon(t, nil) + a.Quit() + wait() + + orig := os.Stdout + os.Stdout = w + + a.Hup() + + os.Stdout = orig + w.Close() + + var out bytes.Buffer + _, err = io.Copy(&out, r) + require.NoError(t, err, "Couldn't copy stdout to buffer") + require.NotEmpty(t, out.String(), "Stacktrace is printed") +} + +func TestAppCanSigHupWithoutExecute(t *testing.T) { + r, w, err := os.Pipe() + require.NoError(t, err, "Setup: pipe shouldn't fail") + + a := daemon.NewForTests(t, nil) + + orig := os.Stdout + os.Stdout = w + + a.Hup() + + os.Stdout = orig + w.Close() + + var out bytes.Buffer + _, err = io.Copy(&out, r) + require.NoError(t, err, "Couldn't copy stdout to buffer") + require.NotEmpty(t, out.String(), "Stacktrace is printed") +} + +func TestAppGetRootCmd(t *testing.T) { + t.Parallel() + + a := daemon.NewForTests(t, nil) + require.NotNil(t, a.RootCmd(), "Returns root command") +} + +func TestConfigLoad(t *testing.T) { + // TODO + t.Fail() + + /*customizedSocketPath := filepath.Join(t.TempDir(), "mysocket") + var config daemon.DaemonConfig + config.Verbosity = 1 + config.Paths.Socket = customizedSocketPath + + a, wait := startDaemon(t, &config) + defer wait() + defer a.Quit() + + _, err := os.Stat(customizedSocketPath) + require.NoError(t, err, "Socket should exist") + require.Equal(t, 1, a.Config().Verbosity, "Verbosity is set from config")*/ +} + +func TestAutoDetectConfig(t *testing.T) { + // TODO + t.Fail() + + customizedSocketPath := filepath.Join(t.TempDir(), "mysocket") + var config daemon.DaemonConfig + config.Verbosity = 1 + + configPath := daemon.GenerateTestConfig(t, &config) + configNextToBinaryPath := filepath.Join(filepath.Dir(os.Args[0]), "authd.yaml") + err := os.Rename(configPath, configNextToBinaryPath) + require.NoError(t, err, "Could not relocate authd configuration file in the binary directory") + // Remove configuration next binary for other tests to not pick it up. + defer os.Remove(configNextToBinaryPath) + + a := daemon.New() + + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + err := a.Run() + require.NoError(t, err, "Run should exits without any error") + }() + a.WaitReady() + time.Sleep(50 * time.Millisecond) + + defer wg.Wait() + defer a.Quit() + + _, err = os.Stat(customizedSocketPath) + require.NoError(t, err, "Socket should exist") + require.Equal(t, 1, a.Config().Verbosity, "Verbosity is set from config") +} + +func TestNoConfigSetDefaults(t *testing.T) { + // TODO + t.Fail() + + a := daemon.New() + // Use version to still run preExec to load no config but without running server + a.SetArgs("version") + + err := a.Run() + require.NoError(t, err, "Run should not return an error") + + require.Equal(t, 0, a.Config().Verbosity, "Default Verbosity") + require.Equal(t, consts.DefaultBrokersConfPath, a.Config().Paths.BrokersConf, "Default brokers configuration path") + //require.Equal(t, consts.DefaultCacheDir, a.Config().Paths.Cache, "Default cache directory") +} + +func TestBadConfigReturnsError(t *testing.T) { + a := daemon.New() + // Use version to still run preExec to load no config but without running server + a.SetArgs("version", "--config", "/does/not/exist.yaml") + + err := a.Run() + require.Error(t, err, "Run should return an error on config file") +} + +// requireGoroutineStarted starts a goroutine and blocks until it has been launched. +func requireGoroutineStarted(t *testing.T, f func()) { + t.Helper() + + launched := make(chan struct{}) + + go func() { + close(launched) + f() + }() + + <-launched +} + +// startDaemon prepares and starts the daemon in the background. The done function should be called +// to wait for the daemon to stop. +func startDaemon(t *testing.T, conf *daemon.DaemonConfig) (app *daemon.App, done func()) { + t.Helper() + + a := daemon.NewForTests(t, conf) + + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + err := a.Run() + require.NoError(t, err, "Run should exits without any error") + }() + a.WaitReady() + time.Sleep(50 * time.Millisecond) + + return a, func() { + wg.Wait() + } +} + +// captureStdout capture current process stdout and returns a function to get the captured buffer. +func captureStdout(t *testing.T) func() string { + t.Helper() + + r, w, err := os.Pipe() + require.NoError(t, err, "Setup: pipe shouldn't fail") + + orig := os.Stdout + os.Stdout = w + + t.Cleanup(func() { + os.Stdout = orig + w.Close() + }) + + var out bytes.Buffer + errch := make(chan error) + go func() { + _, err = io.Copy(&out, r) + errch <- err + close(errch) + }() + + return func() string { + w.Close() + w = nil + require.NoError(t, <-errch, "Couldn't copy stdout to buffer") + + return out.String() + } +} + +func TestMain(m *testing.M) { + // Start system bus mock. + cleanup, err := testutils.StartSystemBusMock() + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } + defer cleanup() + + m.Run() +} diff --git a/cmd/oidc-broker/daemon/export_test.go b/cmd/oidc-broker/daemon/export_test.go new file mode 100644 index 0000000000..a2f67ef986 --- /dev/null +++ b/cmd/oidc-broker/daemon/export_test.go @@ -0,0 +1,67 @@ +package daemon + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +type ( + DaemonConfig = daemonConfig + SystemPaths = systemPaths +) + +func NewForTests(t *testing.T, conf *DaemonConfig, args ...string) *App { + t.Helper() + + p := GenerateTestConfig(t, conf) + argsWithConf := []string{"--config", p} + argsWithConf = append(argsWithConf, args...) + + a := New() + a.rootCmd.SetArgs(argsWithConf) + return a +} + +func GenerateTestConfig(t *testing.T, origConf *daemonConfig) string { + t.Helper() + + var conf daemonConfig + + if origConf != nil { + conf = *origConf + } + + if conf.Verbosity == 0 { + conf.Verbosity = 2 + } + if conf.Paths.Cache == "" { + conf.Paths.Cache = t.TempDir() + //nolint: gosec // This is a directory owned only by the current user for tests. + err := os.Chmod(conf.Paths.Cache, 0700) + require.NoError(t, err, "Setup: could not change permission on cache directory for tests") + } + d, err := yaml.Marshal(conf) + require.NoError(t, err, "Setup: could not marshal configuration for tests") + + confPath := filepath.Join(t.TempDir(), "testconfig.yaml") + err = os.WriteFile(confPath, d, 0600) + require.NoError(t, err, "Setup: could not create configuration for tests") + + return confPath +} + +// Config returns a DaemonConfig for tests. +// +//nolint:revive // DaemonConfig is a type alias for tests +func (a App) Config() DaemonConfig { + return a.config +} + +// SetArgs set some arguments on root command for tests. +func (a *App) SetArgs(args ...string) { + a.rootCmd.SetArgs(args) +} diff --git a/cmd/oidc-broker/daemon/fs.go b/cmd/oidc-broker/daemon/fs.go new file mode 100644 index 0000000000..bd6bafd0be --- /dev/null +++ b/cmd/oidc-broker/daemon/fs.go @@ -0,0 +1,25 @@ +package daemon + +import ( + "fmt" + "io/fs" + "os" + "syscall" +) + +// ensureDirWithPerms creates a directory at path if it doesn't exist yet with perm as permissions. +// If the path exists, it will check if it’s a directory with those perms. +func ensureDirWithPerms(path string, perm os.FileMode) error { + dir, err := os.Stat(path) + if err == nil { + if !dir.IsDir() { + return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR} + } + if dir.Mode() != (perm | fs.ModeDir) { + return fmt.Errorf("permissions %v don't match what we desired: %v", dir.Mode(), perm|fs.ModeDir) + } + + return nil + } + return os.Mkdir(path, perm) +} diff --git a/cmd/oidc-broker/daemon/version.go b/cmd/oidc-broker/daemon/version.go new file mode 100644 index 0000000000..5136a17be3 --- /dev/null +++ b/cmd/oidc-broker/daemon/version.go @@ -0,0 +1,24 @@ +package daemon + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/ubuntu/oidc-broker/internal/consts" +) + +func (a *App) installVersion() { + cmd := &cobra.Command{ + Use: "version", + Short:/*i18n.G(*/ "Returns version of daemon and exits", /*)*/ + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { return getVersion() }, + } + a.rootCmd.AddCommand(cmd) +} + +// getVersion returns the current service version. +func getVersion() (err error) { + fmt.Printf( /*i18n.G(*/ "%s\t%s" /*)*/ +"\n", cmdName, consts.Version) + return nil +} diff --git a/cmd/oidc-broker/main.go b/cmd/oidc-broker/main.go new file mode 100644 index 0000000000..212104dc38 --- /dev/null +++ b/cmd/oidc-broker/main.go @@ -0,0 +1,81 @@ +// Package main is the entry point. +package main + +import ( + "log/slog" + "os" + "os/signal" + "sync" + "syscall" + + "github.com/ubuntu/go-i18n" + "github.com/ubuntu/oidc-broker/cmd/oidc-broker/daemon" + "github.com/ubuntu/oidc-broker/internal/consts" + "github.com/ubuntu/oidc-broker/po" +) + +//FIXME go:generate go run ../generate_completion_documentation.go completion ../../generated +//FIXME go:generate go run ../generate_completion_documentation.go update-readme +//FIXME go:generate go run ../generate_completion_documentation.go update-doc-cli-ref + +func main() { + i18n.InitI18nDomain(consts.TEXTDOMAIN, po.Files) + a := daemon.New() + os.Exit(run(a)) +} + +type app interface { + Run() error + UsageError() bool + Hup() bool + Quit() +} + +func run(a app) int { + defer installSignalHandler(a)() + + if err := a.Run(); err != nil { + slog.Error(err.Error()) + + if a.UsageError() { + return 2 + } + return 1 + } + + return 0 +} + +func installSignalHandler(a app) func() { + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) + + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + for { + switch v, ok := <-c; v { + case syscall.SIGINT, syscall.SIGTERM: + a.Quit() + return + case syscall.SIGHUP: + if a.Hup() { + a.Quit() + return + } + default: + // channel was closed: we exited + if !ok { + return + } + } + } + }() + + return func() { + signal.Stop(c) + close(c) + wg.Wait() + } +} diff --git a/cmd/oidc-broker/main_test.go b/cmd/oidc-broker/main_test.go new file mode 100644 index 0000000000..cb6dfaddf0 --- /dev/null +++ b/cmd/oidc-broker/main_test.go @@ -0,0 +1,118 @@ +package main + +import ( + "errors" + "syscall" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +type myApp struct { + done chan struct{} + + runError bool + usageErrorReturn bool + hupReturn bool +} + +func (a *myApp) Run() error { + <-a.done + if a.runError { + return errors.New("Error requested") + } + return nil +} + +func (a myApp) UsageError() bool { + return a.usageErrorReturn +} + +func (a myApp) Hup() bool { + return a.hupReturn +} + +func (a *myApp) Quit() { + close(a.done) +} + +//nolint:tparallel // Signal handlers tests: subtests can't be parallel +func TestRun(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + runError bool + usageErrorReturn bool + hupReturn bool + sendSig syscall.Signal + + wantReturnCode int + }{ + "Run and exit successfully": {}, + "Run and return error": {runError: true, wantReturnCode: 1}, + "Run and return usage error": {usageErrorReturn: true, runError: true, wantReturnCode: 2}, + "Run and usage error only does not fail": {usageErrorReturn: true, runError: false, wantReturnCode: 0}, + + // Signals handling + "Send SIGINT exits": {sendSig: syscall.SIGINT}, + "Send SIGTERM exits": {sendSig: syscall.SIGTERM}, + "Send SIGHUP without exiting": {sendSig: syscall.SIGHUP}, + "Send SIGHUP with exit": {sendSig: syscall.SIGHUP, hupReturn: true}, + } + for name, tc := range tests { + tc := tc + t.Run(name, func(t *testing.T) { + a := myApp{ + done: make(chan struct{}), + runError: tc.runError, + usageErrorReturn: tc.usageErrorReturn, + hupReturn: tc.hupReturn, + } + + var rc int + wait := make(chan struct{}) + go func() { + rc = run(&a) + close(wait) + }() + + time.Sleep(100 * time.Millisecond) + + var exited bool + switch tc.sendSig { + case syscall.SIGINT: + fallthrough + case syscall.SIGTERM: + err := syscall.Kill(syscall.Getpid(), tc.sendSig) + require.NoError(t, err, "Teardown: kill should return no error") + select { + case <-time.After(50 * time.Millisecond): + exited = false + case <-wait: + exited = true + } + require.Equal(t, true, exited, "Expect to exit on SIGINT and SIGTERM") + case syscall.SIGHUP: + err := syscall.Kill(syscall.Getpid(), syscall.SIGHUP) + require.NoError(t, err, "Teardown: kill should return no error") + select { + case <-time.After(50 * time.Millisecond): + exited = false + case <-wait: + exited = true + } + // if SIGHUP returns false: do nothing and still wait. + // Otherwise, it means that we wanted to stop + require.Equal(t, tc.hupReturn, exited, "Expect to exit only on SIGHUP returning True") + } + + if !exited { + a.Quit() + <-wait + } + + require.Equal(t, tc.wantReturnCode, rc, "Return expected code") + }) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000000..19ee646c5b --- /dev/null +++ b/go.mod @@ -0,0 +1,40 @@ +module github.com/ubuntu/oidc-broker + +go 1.21.5 + +require ( + github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf + github.com/godbus/dbus/v5 v5.1.0 + github.com/spf13/cobra v1.8.0 + github.com/spf13/viper v1.18.2 + github.com/stretchr/testify v1.8.4 + github.com/ubuntu/decorate v0.0.0-20231211084900-69db9a41777a + github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000000..98a0993ed9 --- /dev/null +++ b/go.sum @@ -0,0 +1,113 @@ +github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= +github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +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/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +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/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 h1:9ZM/t54vDjI3XRw2+HC2DxrH2Giv/jAyv7+Vq2QgXmg= +github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069/go.mod h1:AT4NpQrOmyj1L/+hLja6aR0lk81yYYL4ePnj2kp7d6M= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/stretchr/objx v0.1.0/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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +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/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/ubuntu/decorate v0.0.0-20231211084900-69db9a41777a h1:VojaxMh5vh94q0uRrJP2mucbItb4gCMzEg6X1RzlM+M= +github.com/ubuntu/decorate v0.0.0-20231211084900-69db9a41777a/go.mod h1:edGgz97NOqS2oqzbKrZqO9YU9neosRrkEZbVJVQynAA= +github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 h1:CA2dVorxvzdsGtszqhSjyvkrXxZi4bS52ZKvP0Ko634= +github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47/go.mod h1:ZRhdDyx6YkKz/YiMWi0gS3uMCltgdaKz9IpkiNf/GRg= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/broker/broker.go b/internal/broker/broker.go new file mode 100644 index 0000000000..2c172d2257 --- /dev/null +++ b/internal/broker/broker.go @@ -0,0 +1,16 @@ +// package broker is the generic oidc business code. +package broker + +import ( + "fmt" +) + +// Broker is the real implementation of the broker to track sessions and process oidc calls. +type Broker struct{} + +// IsAuthenticated evaluates the provided authenticationData and returns the authentication status for the user. +func (b *Broker) IsAuthenticated(sessionID, authenticationData string) (access, data string, err error) { + fmt.Println("IsAuthenticated generic OIDC code") + getGroups() + return "", "", nil +} diff --git a/internal/broker/microsoft-entra-id.go b/internal/broker/microsoft-entra-id.go new file mode 100644 index 0000000000..676a97d6d6 --- /dev/null +++ b/internal/broker/microsoft-entra-id.go @@ -0,0 +1,11 @@ +//go:build withmsentraid + +// This is the Microsoft Entra ID specific extension. + +package broker + +import "fmt" + +func getGroups() { + fmt.Println("Microsoft Entra ID getGroups") +} diff --git a/internal/broker/noprovider.go b/internal/broker/noprovider.go new file mode 100644 index 0000000000..76ac0dcc42 --- /dev/null +++ b/internal/broker/noprovider.go @@ -0,0 +1,11 @@ +//go:build !withmsentraid + +// This is the generic oidc extension. It’s a no-op for most of the calls. + +package broker + +import "fmt" + +func getGroups() { + fmt.Println("No provider getGroups") +} diff --git a/internal/brokerservice/brokerservice.go b/internal/brokerservice/brokerservice.go new file mode 100644 index 0000000000..df33c7e508 --- /dev/null +++ b/internal/brokerservice/brokerservice.go @@ -0,0 +1,114 @@ +// package brokerservice is the dbus service implementation delegating its functional call to brokers. +package brokerservice + +import ( + "context" + "fmt" + "strings" + + "github.com/godbus/dbus/v5" + "github.com/godbus/dbus/v5/introspect" + "github.com/ubuntu/decorate" + + "github.com/ubuntu/oidc-broker/internal/broker" +) + +const intro = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ` + introspect.IntrospectDataString + ` ` + +// Service is the handler exposing our broker methods on the system bus. +type Service struct { + name string + broker broker.Broker + + serve chan struct{} + disconnect func() +} + +// New returns a new dbus service after exporting to the system bus our name. +func New(ctx context.Context, serviceName string) (s *Service, err error) { + serviceName = strings.ReplaceAll(serviceName, "-", "_") + defer decorate.OnError(&err, "cannot create dbus service %q", serviceName) + + name := fmt.Sprintf("com.ubuntu.oidc.%s", serviceName) + iface := "com.ubuntu.authd.Broker" + object := dbus.ObjectPath(fmt.Sprintf("/com/ubuntu/oidc/%s", serviceName)) + + s = &Service{ + name: name, + broker: broker.Broker{}, + serve: make(chan struct{}), + } + + conn, err := s.getBus() + if err != nil { + return nil, err + } + + if err := conn.Export(s, object, iface); err != nil { + return nil, err + } + if err := conn.Export(introspect.Introspectable(fmt.Sprintf(intro, iface)), object, "org.freedesktop.DBus.Introspectable"); err != nil { + return nil, err + } + + reply, err := conn.RequestName(name, dbus.NameFlagDoNotQueue) + if err != nil { + s.disconnect() + return nil, err + } + if reply != dbus.RequestNameReplyPrimaryOwner { + s.disconnect() + return nil, fmt.Errorf("%q is already taken in the bus", name) + } + + return s, nil +} + +// Addr returns the address of the service. +func (s Service) Addr() string { + return s.name +} + +// Serve wait for the service. +func (s *Service) Serve() error { + <-s.serve + return nil +} + +// Stop stop the service and do all the necessary cleanup operation. +func (s *Service) Stop() error { + close(s.serve) + s.disconnect() + return nil +} diff --git a/internal/brokerservice/localbus.go b/internal/brokerservice/localbus.go new file mode 100644 index 0000000000..97957ac363 --- /dev/null +++ b/internal/brokerservice/localbus.go @@ -0,0 +1,32 @@ +//go:build withlocalbus + +package brokerservice + +import ( + "fmt" + "log/slog" + "os" + + "github.com/godbus/dbus/v5" + "github.com/ubuntu/oidc-broker/internal/testutils" +) + +// getBus creates the local bus and returns a connection to the bus. +// It attaches a disconnect handler to stop the local bus subprocess. +func (s *Service) getBus() (*dbus.Conn, error) { + cleanup, err := testutils.StartSystemBusMock() + if err != nil { + return nil, err + } + slog.Info(fmt.Sprintf("Using local bus address: %s", os.Getenv("DBUS_SYSTEM_BUS_ADDRESS"))) + conn, err := dbus.ConnectSystemBus() + if err != nil { + return nil, err + } + + s.disconnect = func() { + conn.Close() + cleanup() + } + return conn, err +} diff --git a/internal/brokerservice/methods.go b/internal/brokerservice/methods.go new file mode 100644 index 0000000000..15ecf55c8d --- /dev/null +++ b/internal/brokerservice/methods.go @@ -0,0 +1,14 @@ +package brokerservice + +import ( + "github.com/godbus/dbus/v5" +) + +// IsAuthenticated is the method through which the broker and the daemon will communicate once dbusInterface.IsAuthenticated is called. +func (s *Service) IsAuthenticated(sessionID, authenticationData string) (access, data string, dbusErr *dbus.Error) { + access, data, err := s.broker.IsAuthenticated(sessionID, authenticationData) + if err != nil { + return "", "", dbus.MakeFailedError(err) + } + return access, data, nil +} diff --git a/internal/brokerservice/systembus.go b/internal/brokerservice/systembus.go new file mode 100644 index 0000000000..4eaa276866 --- /dev/null +++ b/internal/brokerservice/systembus.go @@ -0,0 +1,18 @@ +//go:build !withlocalbus + +package brokerservice + +import ( + "github.com/godbus/dbus/v5" +) + +// getBus returns the system bus and attach a disconnect handler. +func (s *Service) getBus() (*dbus.Conn, error) { + conn, err := dbus.ConnectSystemBus() + if err != nil { + return nil, err + } + s.disconnect = func() { _ = conn.Close() } + + return conn, nil +} diff --git a/internal/consts/consts.go b/internal/consts/consts.go new file mode 100644 index 0000000000..5ae79ddd12 --- /dev/null +++ b/internal/consts/consts.go @@ -0,0 +1,20 @@ +// Package consts defines the constants used by the project. +package consts + +import "log/slog" + +var ( + // Version is the version of the executable. + Version = "Dev" +) + +const ( + // TEXTDOMAIN is the gettext domain for l10n. + TEXTDOMAIN = "oidc-broker" + + // DefaultLevelLog is the default logging level selected without any option. + DefaultLevelLog = slog.LevelWarn + + // DefaultBrokersConfPath is the default configuration directory for the brokers. + DefaultBrokersConfPath = "/etc/authd/brokers.d/" +) diff --git a/internal/daemon/daemon.go b/internal/daemon/daemon.go new file mode 100644 index 0000000000..0b19ee1ee8 --- /dev/null +++ b/internal/daemon/daemon.go @@ -0,0 +1,82 @@ +// Package daemon handles the dbus daemon with systemd support. +package daemon + +import ( + "context" + "fmt" + "log/slog" + + "github.com/coreos/go-systemd/daemon" + "github.com/ubuntu/decorate" +) + +// Daemon is a grpc daemon with systemd support. +type Daemon struct { + service Service + + systemdSdNotifier systemdSdNotifier +} + +type options struct { + // private member that we export for tests. + systemdSdNotifier func(unsetEnvironment bool, state string) (bool, error) +} + +type systemdSdNotifier func(unsetEnvironment bool, state string) (bool, error) + +// Option is the function signature used to tweak the daemon creation. +type Option func(*options) + +// Service is a server that can Serve and be Stopped by our daemon. +type Service interface { + Addr() string + Serve() error + Stop() error +} + +// New returns an new, initialized daemon server, which handles systemd activation. +// If systemd activation is used, it will override any socket passed here. +func New(ctx context.Context, service Service, args ...Option) (d *Daemon, err error) { + defer decorate.OnError(&err, "can't create daemon") + + slog.Debug("Building new daemon") + + // Set default options. + opts := options{ + systemdSdNotifier: daemon.SdNotify, + } + // Apply given args. + for _, f := range args { + f(&opts) + } + + return &Daemon{ + service: service, + + systemdSdNotifier: opts.systemdSdNotifier, + }, nil +} + +// Serve signals systemd that we are ready to receive from the service. +func (d *Daemon) Serve(ctx context.Context) (err error) { + defer decorate.OnError(&err, "error while serving") + + slog.Debug("Starting to serve requests") + + // Signal to systemd that we are ready. + if sent, err := d.systemdSdNotifier(false, "READY=1"); err != nil { + return fmt.Errorf("couldn't send ready notification to systemd: %v", err) + } else if sent { + slog.Debug("Ready state sent to systemd") + } + + slog.Info(fmt.Sprintf("Serving requests as %v", d.service.Addr())) + return d.service.Serve() +} + +// Quit gracefully quits listening loop and stops the grpc server. +// It can drops any existing connexion is force is true. +func (d Daemon) Quit() { + slog.Info("Stopping daemon requested.") + _ = d.service.Stop() +} diff --git a/internal/log/log.go b/internal/log/log.go new file mode 100644 index 0000000000..e15cf31b6f --- /dev/null +++ b/internal/log/log.go @@ -0,0 +1,19 @@ +package log + +import ( + "log/slog" + "os" +) + +var globalLevel = &slog.LevelVar{} + +func init() { + h := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: globalLevel}) + slog.SetDefault(slog.New(h)) + globalLevel.Set(slog.LevelWarn) +} + +// SetLevel change global handler log level. +func SetLevel(l slog.Level) { + globalLevel.Set(l) +} diff --git a/internal/testutils/dbus.go b/internal/testutils/dbus.go new file mode 100644 index 0000000000..387bbd0890 --- /dev/null +++ b/internal/testutils/dbus.go @@ -0,0 +1,102 @@ +// Package testutils provides utility functions and behaviors for testing. +package testutils + +import ( + "context" + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "testing" + "time" + + "github.com/godbus/dbus/v5" +) + +const defaultSystemBusAddress = "unix:path=/var/run/dbus/system_bus_socket" + +var systemBusMockCfg = ` + + system + + unix:path=%s + + + + + + + +` + +// StartSystemBusMock starts a mock dbus daemon and returns a cancel function to stop it. +// +// This function uses os.Setenv to set the DBUS_SYSTEM_BUS_ADDRESS environment, so it shouldn't be used in parallel tests +// that rely on the mentioned variable. +func StartSystemBusMock() (func(), error) { + if isRunning() { + return nil, errors.New("system bus mock is already running") + } + + tmp, err := os.MkdirTemp(os.TempDir(), "authd-system-bus-mock") + if err != nil { + return nil, err + } + + cfgPath := filepath.Join(tmp, "bus.conf") + listenPath := filepath.Join(tmp, "bus.sock") + + err = os.WriteFile(cfgPath, []byte(fmt.Sprintf(systemBusMockCfg, listenPath)), 0600) + if err != nil { + err = errors.Join(err, os.RemoveAll(tmp)) + return nil, err + } + + busCtx, busCancel := context.WithCancel(context.Background()) + //#nosec:G204 // This is a test helper and we are in control of the arguments. + cmd := exec.CommandContext(busCtx, "dbus-daemon", "--config-file="+cfgPath) + if err := cmd.Start(); err != nil { + busCancel() + err = errors.Join(err, os.RemoveAll(tmp)) + return nil, err + } + // Give some time for the daemon to start. + time.Sleep(500 * time.Millisecond) + + prev, set := os.LookupEnv("DBUS_SYSTEM_BUS_ADDRESS") + os.Setenv("DBUS_SYSTEM_BUS_ADDRESS", "unix:path="+listenPath) + + return func() { + busCancel() + _ = cmd.Wait() + _ = os.RemoveAll(tmp) + + if !set { + os.Unsetenv("DBUS_SYSTEM_BUS_ADDRESS") + } else { + os.Setenv("DBUS_SYSTEM_BUS_ADDRESS", prev) + } + }, nil +} + +// GetSystemBusConnection returns a connection to the system bus with a safety check to avoid mistakenly connecting to the +// actual system bus. +func GetSystemBusConnection(t *testing.T) (*dbus.Conn, error) { + t.Helper() + if !isRunning() { + return nil, errors.New("system bus mock is not running. If that's intended, manually connect to the system bus instead of using this function") + } + conn, err := dbus.ConnectSystemBus() + if err != nil { + return nil, err + } + return conn, nil +} + +// isRunning checks if the system bus mock is running. +func isRunning() bool { + busAddr := os.Getenv("DBUS_SYSTEM_BUS_ADDRESS") + return !(busAddr == "" || busAddr == defaultSystemBusAddress) +} diff --git a/po/embedder.go b/po/embedder.go new file mode 100644 index 0000000000..734c4dd21c --- /dev/null +++ b/po/embedder.go @@ -0,0 +1,11 @@ +//go:build !withmo || windows + +// Package po allows embedding po files in project in development mode or windows. +package po + +import "embed" + +// Files containing po files +// +//go:embed *.po +var Files embed.FS diff --git a/po/fr.po b/po/fr.po new file mode 100644 index 0000000000..e69de29bb2 diff --git a/po/mofiles.go b/po/mofiles.go new file mode 100644 index 0000000000..db9b3e2108 --- /dev/null +++ b/po/mofiles.go @@ -0,0 +1,7 @@ +//go:build withmo && !windows + +package po + +import "embed" + +var Files embed.FS From fb464f60f048ac072da99857137099b313c7e5b6 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Thu, 11 Jan 2024 11:36:59 +0100 Subject: [PATCH 0004/1670] License under GPL 3 --- LICENSE | 674 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 674 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..f288702d2f --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. From 7d62201c84cbca53f3226c553967eac4ca8098ea Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Wed, 17 Jan 2024 07:55:03 -0400 Subject: [PATCH 0005/1670] Set up CI for the project Adding the CI skeleton for the project, setting up issue templates, codeowners, quality assessment and so on. --- .github/.jira_sync_config.yml | 6 + .github/CODEOWNERS | 5 + .github/ISSUE_TEMPLATE/bug_report.yml | 93 ++ .github/ISSUE_TEMPLATE/config.yml | 5 + .github/ISSUE_TEMPLATE/feature_request.yml | 89 ++ .github/dependabot.yml | 31 + .github/workflows/ci.yml | 58 ++ .github/workflows/cla-check.yml | 12 + .golangci.yaml | 63 ++ tools/go.mod | 189 ++++ tools/go.sum | 1008 ++++++++++++++++++++ tools/tools.go | 7 + 12 files changed, 1566 insertions(+) create mode 100644 .github/.jira_sync_config.yml create mode 100644 .github/CODEOWNERS create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/cla-check.yml create mode 100644 .golangci.yaml create mode 100644 tools/go.mod create mode 100644 tools/go.sum create mode 100644 tools/tools.go diff --git a/.github/.jira_sync_config.yml b/.github/.jira_sync_config.yml new file mode 100644 index 0000000000..c90816d70c --- /dev/null +++ b/.github/.jira_sync_config.yml @@ -0,0 +1,6 @@ +# For more information on configuring the Jira Sync bot, see: https://github.com/canonical/gh-jira-sync-bot +settings: + jira_project_key: "UDENG" + status_mapping: + opened: Untriaged + closed: Done diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..6c3bbf0ac4 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,5 @@ +# This is the squad name responsible for this project + +## It will be automatically assigned to opened PR + +* @ubuntu/enterprise-desktop-team diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000000..b7a75cac5b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,93 @@ +name: Report an issue +description: Create a bug report to fix an issue +title: 'Issue: ' +labels: ['bug'] +body: + - type: markdown + attributes: + value: > + :warning: **Please do not report security vulnerabilities here** + + Be careful with sensitive information and security vulnerabilities. In order to report bugs that could contain + sensitive information, use [Launchpad](https://bugs.launchpad.net/ubuntu/+source/oidc-broker/+filebug) instead. + On Ubuntu machines, you can use `ubuntu-bug oidc-broker` to collect relevant information. + + + Thanks for taking the time to report an issue and help improve oidc-broker! Please fill out the form below as + best as you can so that we can help you. + + Your additional work here is greatly appreciated and will help us respond as quickly as possible. For general + support or usage questions, use [Ubuntu Discourse](https://discourse.ubuntu.com/c/desktop/8). + + By submitting an Issue to this repository, you agree to the terms within the + [Ubuntu Code of Conduct](https://ubuntu.com/community/code-of-conduct). + - type: checkboxes + attributes: + label: Is there an existing issue for this? + options: + - label: I have searched the existing issues and found none that matched mine + required: true + - type: textarea + attributes: + label: Describe the issue + description: > + Provide a clear and concise description of what the issue is, including what you expected to happen. + validations: + required: true + - type: textarea + attributes: + label: Steps to reproduce it + description: > + Detail the steps taken to reproduce this error, what was expected, and whether this issue can be reproduced + consistently or if it is intermittent. + placeholder: | + 1. In this environment... + 2. With this config... + 3. Run '...' + 4. See error... + + Please include screenshots (you can drag any image here) where applicable. Redact any sensitive information. + validations: + required: true + - type: textarea + attributes: + label: "Ubuntu users: System information and logs" + description: > + Ubuntu users can run `ubuntu-bug oidc-broker --save=/tmp/report.txt` + and drag the file below. + + It will contain useful information pertaining to the system and the packages installed. + - type: textarea + attributes: + label: "Non Ubuntu users: System information and logs" + description: | + For users of distributions other than Ubuntu, provide details about the environment you experienced the issue in: + value: | + ### Environment + * oidc-broker version: please run //TODO + * Distribution: (**NAME** in `/etc/os-release`) + * Distribution version: (**VERSION_ID** on `/etc/os-release`): + + ### Log files + Please redact/remove sensitive information: + ```raw + oidc-broker logs can be found in //TODO + ``` + + ### Application settings + Please redact/remove sensitive information: + ```raw + You can get the configuration file from //TODO + ``` + - type: textarea + attributes: + label: Relevant information + description: > + Please look at our [Troubleshooting guide](../#troubleshooting) and provide logs for the oidc-broker package. + placeholder: Remember to redact any sensitive information from them. + - type: checkboxes + attributes: + label: Double check your logs + options: + - label: I have redacted any sensitive information from the logs + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..52e8c5b101 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Contact Canonical + url: https://ubuntu.com/contact-us/form + about: Contact us to get more information diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000000..a7ee9fb194 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,89 @@ +name: Request a feature +description: Suggest new functionality or improvements for this project +title: 'Feature: ' +labels: ['feature'] +body: + - type: markdown + attributes: + value: > + :warning: **Please do not report security vulnerabilities here** + + Be careful with sensitive information and security vulnerabilities. In order to report bugs that could contain + sensitive information, use [Launchpad](https://bugs.launchpad.net/ubuntu/+source/oidc-broker/+filebug) instead. + On Ubuntu machines, you can use `ubuntu-bug oidc-broker` to collect relevant information. + + + Thanks for taking the time to report an issue and help improve oidc-broker! Please fill out the form below as + best as you can so that we can help you. + + Your additional work here is greatly appreciated and will help us respond as quickly as possible. For general + support or usage questions, use [Ubuntu Discourse](https://discourse.ubuntu.com/c/desktop/8). + + By submitting an Issue to this repository, you agree to the terms within the + [Ubuntu Code of Conduct](https://ubuntu.com/community/code-of-conduct). + - type: checkboxes + attributes: + label: Is there an existing request for this feature? + options: + - label: I have searched the existing issues and found none that matched mine + required: true + - type: textarea + attributes: + label: Describe the feature + description: > + A clear and concise description of what the problem is. Ex.: \"I'm always frustrated when ...\" + validations: + required: true + - type: textarea + attributes: + label: Describe the ideal solution + description: | + A clear and concise description of what you want to happen. + - type: textarea + attributes: + label: Alternatives and current workarounds + description: | + A clear and concise description of any alternatives you've considered or any workarounds that are currently in + place. + - type: textarea + attributes: + label: "Ubuntu users: System information and logs" + description: > + Ubuntu users can run `ubuntu-bug oidc-broker --save=/tmp/report.txt` + and drag the file below. + + It will contain useful information pertaining to the system and the packages installed. + - type: textarea + attributes: + label: "Non Ubuntu users: System information and logs" + description: | + For users of distributions other than Ubuntu, provide details about the environment you experienced the issue in: + value: | + ### Environment + * oidc-broker version: please run //TODO + * Distribution: (**NAME** in `/etc/os-release`) + * Distribution version: (**VERSION_ID** on `/etc/os-release`): + + ### Log files + Please redact/remove sensitive information: + ```raw + oidc-broker logs can be found in //TODO + ``` + + ### Application settings + Please redact/remove sensitive information: + ```raw + You can get the configuration file from //TODO + ``` + - type: textarea + attributes: + label: Relevant information + description: > + Please look at our [Troubleshooting guide](../#troubleshooting) and provide logs for the oidc-broker package. + placeholder: Remember to redact any sensitive information from them. + - type: checkboxes + attributes: + label: Double check your logs + options: + - label: I have redacted any sensitive information from the logs + required: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..01e37164fa --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,31 @@ +# See the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + # Infrastructure + ## GitHub Actions + - package-ecosystem: "github-actions" + # Workflow files stored in the + # default location of `.github/workflows` + directory: "/" + schedule: + interval: "weekly" + commit-message: + prefix: "deps(ci)" + + # Codebase + ## Go dependencies + - package-ecosystem: "gomod" + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + commit-message: + prefix: "deps(go)" + + - package-ecosystem: "gomod" + directory: "/tools" + schedule: + interval: "weekly" + commit-message: + prefix: "deps(go-tools)" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..5385268b46 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,58 @@ +name: QA & sanity checks +on: + push: + branches: + - main + tags: + - "*" + pull_request: + +jobs: + go-sanity: + name: "Go: Code sanity" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Go code sanity check + uses: canonical/desktop-engineering/gh-actions/go/code-sanity@main + with: + golangci-lint-configfile: ".golangci.yaml" + tools-directory: "tools" + + go-tests: + name: "Go: Tests" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Run tests (with coverage collection) + run: | + set -eu + + # The coverage is not written if the output directory does not exist, so we need to create it. + raw_cov_dir="/tmp/raw_files" + rm -fr "${raw_cov_dir}" + mkdir -p "${raw_cov_dir}" + + # Overriding the default coverage directory is not an exported flag of go test (yet), so + # we need to override it using the test.gocoverdir flag instead. + #TODO: Update when https://go-review.googlesource.com/c/go/+/456595 is merged. + go test -cover -covermode=set ./... -shuffle=on -args -test.gocoverdir="${raw_cov_dir}" + + # Convert the raw coverage data into textfmt so we can merge the Rust one into it + go tool covdata textfmt -i="${raw_cov_dir}" -o="/tmp/coverage.out" + + # Filter out the testutils package and the pb.go file + grep -v -e "testutils" "/tmp/coverage.out" >"/tmp/coverage.out.filtered" + + - name: Run tests (with race detector) + run: | + go test -race ./... + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + file: /tmp/coverage.out.filtered diff --git a/.github/workflows/cla-check.yml b/.github/workflows/cla-check.yml new file mode 100644 index 0000000000..32aca7f840 --- /dev/null +++ b/.github/workflows/cla-check.yml @@ -0,0 +1,12 @@ +name: Check if CLA is signed +on: [pull_request_target] + +jobs: + cla-check: + name: Check if CLA is signed + runs-on: ubuntu-latest + steps: + - name: Check if CLA signed + uses: canonical/has-signed-canonical-cla@v1 + with: + accept-existing-contributors: true diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000000..f3b2c50f9b --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,63 @@ +# This is for linting. To run it, please use: +# golangci-lint run ${MODULE}/... [--fix] + +linters: + # linters to run in addition to default ones + enable: + - dupl + - durationcheck + - errname + - errorlint + - exportloopref + - forbidigo + - forcetypeassert + - gci + - godot + - gofmt + - gosec + - misspell + - nakedret + - nolintlint + - revive + - thelper + - tparallel + - unconvert + - unparam + - whitespace + +run: + timeout: 5m + +# Get all linter issues, even if duplicated +issues: + exclude-use-default: false + max-issues-per-linter: 0 + max-same-issues: 0 + fix: false # we don’t want this in CI + exclude: + # EXC0001 errcheck: most errors are in defer calls, which are safe to ignore and idiomatic Go (would be good to only ignore defer ones though) + - 'Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*print(f|ln)?|os\.(Un)?Setenv|w\.Stop). is not checked' + # EXC0008 gosec: duplicated of errcheck + - (G104|G307) + # EXC0010 gosec: False positive is triggered by 'src, err := ioutil.ReadFile(filename)' + - Potential file inclusion via variable + # We don't wrap errors on purpose + - non-wrapping format verb for fmt.Errorf. Use `%w` to format errors + # We want named parameters even if unused, as they help better document the function + - unused-parameter + # Sometimes it is more readable it do a `if err:=a(); err != nil` tha simpy `return a()` + - if-return + +nolintlint: + require-explanation: true + require-specific: true + +linters-settings: + # Forbid the usage of deprecated ioutil and debug prints + forbidigo: + forbid: + - ioutil\. + - ^print.*$ + # Never have naked return ever + nakedret: + max-func-lines: 1 diff --git a/tools/go.mod b/tools/go.mod new file mode 100644 index 0000000000..ec8057ae5d --- /dev/null +++ b/tools/go.mod @@ -0,0 +1,189 @@ +module github.com/ubuntu/oidc-broker/tools + +go 1.21.5 + +require github.com/golangci/golangci-lint v1.55.2 + +require ( + 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect + 4d63.com/gochecknoglobals v0.2.1 // indirect + github.com/4meepo/tagalign v1.3.3 // indirect + github.com/Abirdcfly/dupword v0.0.13 // indirect + github.com/Antonboom/errname v0.1.12 // indirect + github.com/Antonboom/nilnil v0.1.7 // indirect + github.com/Antonboom/testifylint v0.2.3 // indirect + github.com/BurntSushi/toml v1.3.2 // indirect + github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect + github.com/GaijinEntertainment/go-exhaustruct/v3 v3.1.0 // indirect + github.com/Masterminds/semver v1.5.0 // indirect + github.com/OpenPeeDeeP/depguard/v2 v2.1.0 // indirect + github.com/alecthomas/go-check-sumtype v0.1.3 // indirect + github.com/alexkohler/nakedret/v2 v2.0.2 // indirect + github.com/alexkohler/prealloc v1.0.0 // indirect + github.com/alingse/asasalint v0.0.11 // indirect + github.com/ashanbrown/forbidigo v1.6.0 // indirect + github.com/ashanbrown/makezero v1.1.1 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bkielbasa/cyclop v1.2.1 // indirect + github.com/blizzy78/varnamelen v0.8.0 // indirect + github.com/bombsimon/wsl/v3 v3.4.0 // indirect + github.com/breml/bidichk v0.2.7 // indirect + github.com/breml/errchkjson v0.3.6 // indirect + github.com/butuzov/ireturn v0.2.2 // indirect + github.com/butuzov/mirror v1.1.0 // indirect + github.com/catenacyber/perfsprint v0.2.0 // indirect + github.com/ccojocar/zxcvbn-go v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/charithe/durationcheck v0.0.10 // indirect + github.com/chavacava/garif v0.1.0 // indirect + github.com/curioswitch/go-reassign v0.2.0 // indirect + github.com/daixiang0/gci v0.11.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/denis-tingaikin/go-header v0.4.3 // indirect + github.com/esimonov/ifshort v1.0.4 // indirect + github.com/ettle/strcase v0.1.1 // indirect + github.com/fatih/color v1.15.0 // indirect + github.com/fatih/structtag v1.2.0 // indirect + github.com/firefart/nonamedreturns v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/fzipp/gocyclo v0.6.0 // indirect + github.com/ghostiam/protogetter v0.2.3 // indirect + github.com/go-critic/go-critic v0.9.0 // indirect + github.com/go-toolsmith/astcast v1.1.0 // indirect + github.com/go-toolsmith/astcopy v1.1.0 // indirect + github.com/go-toolsmith/astequal v1.1.0 // indirect + github.com/go-toolsmith/astfmt v1.1.0 // indirect + github.com/go-toolsmith/astp v1.1.0 // indirect + github.com/go-toolsmith/strparse v1.1.0 // indirect + github.com/go-toolsmith/typep v1.1.0 // indirect + github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/gofrs/flock v0.8.1 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 // indirect + github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect + github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe // indirect + github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e // indirect + github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 // indirect + github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca // indirect + github.com/golangci/misspell v0.4.1 // indirect + github.com/golangci/revgrep v0.5.2 // indirect + github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601 // indirect + github.com/gostaticanalysis/analysisutil v0.7.1 // indirect + github.com/gostaticanalysis/comment v1.4.2 // indirect + github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect + github.com/gostaticanalysis/nilerr v0.1.1 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hexops/gotextdiff v1.0.3 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jgautheron/goconst v1.6.0 // indirect + github.com/jingyugao/rowserrcheck v1.1.1 // indirect + github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect + github.com/julz/importas v0.1.0 // indirect + github.com/kisielk/errcheck v1.6.3 // indirect + github.com/kisielk/gotool v1.0.0 // indirect + github.com/kkHAIKE/contextcheck v1.1.4 // indirect + github.com/kulti/thelper v0.6.3 // indirect + github.com/kunwardeep/paralleltest v1.0.8 // indirect + github.com/kyoh86/exportloopref v0.1.11 // indirect + github.com/ldez/gomoddirectives v0.2.3 // indirect + github.com/ldez/tagliatelle v0.5.0 // indirect + github.com/leonklingele/grouper v1.1.1 // indirect + github.com/lufeee/execinquery v1.2.1 // indirect + github.com/macabu/inamedparam v0.1.2 // indirect + github.com/magiconair/properties v1.8.6 // indirect + github.com/maratori/testableexamples v1.0.0 // indirect + github.com/maratori/testpackage v1.1.1 // indirect + github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/mbilski/exhaustivestruct v1.2.0 // indirect + github.com/mgechev/revive v1.3.4 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/moricho/tparallel v0.3.1 // indirect + github.com/nakabonne/nestif v0.3.1 // indirect + github.com/nishanths/exhaustive v0.11.0 // indirect + github.com/nishanths/predeclared v0.2.2 // indirect + github.com/nunnatsa/ginkgolinter v0.14.1 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/polyfloyd/go-errorlint v1.4.5 // indirect + github.com/prometheus/client_golang v1.12.1 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.32.1 // indirect + github.com/prometheus/procfs v0.7.3 // indirect + github.com/quasilyte/go-ruleguard v0.4.0 // indirect + github.com/quasilyte/gogrep v0.5.0 // indirect + github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect + github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect + github.com/ryancurrah/gomodguard v1.3.0 // indirect + github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect + github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect + github.com/sashamelentyev/interfacebloat v1.1.0 // indirect + github.com/sashamelentyev/usestdlibvars v1.24.0 // indirect + github.com/securego/gosec/v2 v2.18.2 // indirect + github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sivchari/containedctx v1.0.3 // indirect + github.com/sivchari/nosnakecase v1.7.0 // indirect + github.com/sivchari/tenv v1.7.1 // indirect + github.com/sonatard/noctx v0.0.2 // indirect + github.com/sourcegraph/go-diff v0.7.0 // indirect + github.com/spf13/afero v1.8.2 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/cobra v1.7.0 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.12.0 // indirect + github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect + github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect + github.com/stretchr/objx v0.5.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect + github.com/subosito/gotenv v1.4.1 // indirect + github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c // indirect + github.com/tdakkota/asciicheck v0.2.0 // indirect + github.com/tetafro/godot v1.4.15 // indirect + github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect + github.com/timonwong/loggercheck v0.9.4 // indirect + github.com/tomarrell/wrapcheck/v2 v2.8.1 // indirect + github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect + github.com/ultraware/funlen v0.1.0 // indirect + github.com/ultraware/whitespace v0.0.5 // indirect + github.com/uudashr/gocognit v1.1.2 // indirect + github.com/xen0n/gosmopolitan v1.2.2 // indirect + github.com/yagipy/maintidx v1.0.0 // indirect + github.com/yeya24/promlinter v0.2.0 // indirect + github.com/ykadowak/zerologlint v0.1.3 // indirect + gitlab.com/bosi/decorder v0.4.1 // indirect + go-simpler.org/sloglint v0.1.2 // indirect + go.tmz.dev/musttag v0.7.2 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + go.uber.org/zap v1.24.0 // indirect + golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect + golang.org/x/exp/typeparams v0.0.0-20230307190834-24139beb5833 // indirect + golang.org/x/mod v0.13.0 // indirect + golang.org/x/sync v0.4.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/tools v0.14.0 // indirect + google.golang.org/protobuf v1.28.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + honnef.co/go/tools v0.4.6 // indirect + mvdan.cc/gofumpt v0.5.0 // indirect + mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect + mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect + mvdan.cc/unparam v0.0.0-20221223090309-7455f1af531d // indirect +) diff --git a/tools/go.sum b/tools/go.sum new file mode 100644 index 0000000000..8c0d284fc6 --- /dev/null +++ b/tools/go.sum @@ -0,0 +1,1008 @@ +4d63.com/gocheckcompilerdirectives v1.2.1 h1:AHcMYuw56NPjq/2y615IGg2kYkBdTvOaojYCBcRE7MA= +4d63.com/gocheckcompilerdirectives v1.2.1/go.mod h1:yjDJSxmDTtIHHCqX0ufRYZDL6vQtMG7tJdKVeWwsqvs= +4d63.com/gochecknoglobals v0.2.1 h1:1eiorGsgHOFOuoOiJDy2psSrQbRdIHrlge0IJIkUgDc= +4d63.com/gochecknoglobals v0.2.1/go.mod h1:KRE8wtJB3CXCsb1xy421JfTHIIbmT3U5ruxw2Qu8fSU= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/4meepo/tagalign v1.3.3 h1:ZsOxcwGD/jP4U/aw7qeWu58i7dwYemfy5Y+IF1ACoNw= +github.com/4meepo/tagalign v1.3.3/go.mod h1:Q9c1rYMZJc9dPRkbQPpcBNCLEmY2njbAsXhQOZFE2dE= +github.com/Abirdcfly/dupword v0.0.13 h1:SMS17YXypwP000fA7Lr+kfyBQyW14tTT+nRv9ASwUUo= +github.com/Abirdcfly/dupword v0.0.13/go.mod h1:Ut6Ue2KgF/kCOawpW4LnExT+xZLQviJPE4klBPMK/5Y= +github.com/Antonboom/errname v0.1.12 h1:oh9ak2zUtsLp5oaEd/erjB4GPu9w19NyoIskZClDcQY= +github.com/Antonboom/errname v0.1.12/go.mod h1:bK7todrzvlaZoQagP1orKzWXv59X/x0W0Io2XT1Ssro= +github.com/Antonboom/nilnil v0.1.7 h1:ofgL+BA7vlA1K2wNQOsHzLJ2Pw5B5DpWRLdDAVvvTow= +github.com/Antonboom/nilnil v0.1.7/go.mod h1:TP+ScQWVEq0eSIxqU8CbdT5DFWoHp0MbP+KMUO1BKYQ= +github.com/Antonboom/testifylint v0.2.3 h1:MFq9zyL+rIVpsvLX4vDPLojgN7qODzWsrnftNX2Qh60= +github.com/Antonboom/testifylint v0.2.3/go.mod h1:IYaXaOX9NbfAyO+Y04nfjGI8wDemC1rUyM/cYolz018= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= +github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.1.0 h1:3ZBs7LAezy8gh0uECsA6CGU43FF3zsx5f4eah5FxTMA= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.1.0/go.mod h1:rZLTje5A9kFBe0pzhpe2TdhRniBF++PRHQuRpR8esVc= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/OpenPeeDeeP/depguard/v2 v2.1.0 h1:aQl70G173h/GZYhWf36aE5H0KaujXfVMnn/f1kSDVYY= +github.com/OpenPeeDeeP/depguard/v2 v2.1.0/go.mod h1:PUBgk35fX4i7JDmwzlJwJ+GMe6NfO1723wmJMgPThNQ= +github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= +github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= +github.com/alecthomas/go-check-sumtype v0.1.3 h1:M+tqMxB68hcgccRXBMVCPI4UJ+QUfdSx0xdbypKCqA8= +github.com/alecthomas/go-check-sumtype v0.1.3/go.mod h1:WyYPfhfkdhyrdaligV6svFopZV8Lqdzn5pyVBaV6jhQ= +github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= +github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alexkohler/nakedret/v2 v2.0.2 h1:qnXuZNvv3/AxkAb22q/sEsEpcA99YxLFACDtEw9TPxE= +github.com/alexkohler/nakedret/v2 v2.0.2/go.mod h1:2b8Gkk0GsOrqQv/gPWjNLDSKwG8I5moSXG1K4VIBcTQ= +github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= +github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= +github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= +github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= +github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY= +github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= +github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= +github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bkielbasa/cyclop v1.2.1 h1:AeF71HZDob1P2/pRm1so9cd1alZnrpyc4q2uP2l0gJY= +github.com/bkielbasa/cyclop v1.2.1/go.mod h1:K/dT/M0FPAiYjBgQGau7tz+3TMh4FWAEqlMhzFWCrgM= +github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= +github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= +github.com/bombsimon/wsl/v3 v3.4.0 h1:RkSxjT3tmlptwfgEgTgU+KYKLI35p/tviNXNXiL2aNU= +github.com/bombsimon/wsl/v3 v3.4.0/go.mod h1:KkIB+TXkqy6MvK9BDZVbZxKNYsE1/oLRJbIFtf14qqo= +github.com/breml/bidichk v0.2.7 h1:dAkKQPLl/Qrk7hnP6P+E0xOodrq8Us7+U0o4UBOAlQY= +github.com/breml/bidichk v0.2.7/go.mod h1:YodjipAGI9fGcYM7II6wFvGhdMYsC5pHDlGzqvEW3tQ= +github.com/breml/errchkjson v0.3.6 h1:VLhVkqSBH96AvXEyclMR37rZslRrY2kcyq+31HCsVrA= +github.com/breml/errchkjson v0.3.6/go.mod h1:jhSDoFheAF2RSDOlCfhHO9KqhZgAYLyvHe7bRCX8f/U= +github.com/butuzov/ireturn v0.2.2 h1:jWI36dxXwVrI+RnXDwux2IZOewpmfv930OuIRfaBUJ0= +github.com/butuzov/ireturn v0.2.2/go.mod h1:RfGHUvvAuFFxoHKf4Z8Yxuh6OjlCw1KvR2zM1NFHeBk= +github.com/butuzov/mirror v1.1.0 h1:ZqX54gBVMXu78QLoiqdwpl2mgmoOJTk7s4p4o+0avZI= +github.com/butuzov/mirror v1.1.0/go.mod h1:8Q0BdQU6rC6WILDiBM60DBfvV78OLJmMmixe7GF45AE= +github.com/catenacyber/perfsprint v0.2.0 h1:azOocHLscPjqXVJ7Mf14Zjlkn4uNua0+Hcg1wTR6vUo= +github.com/catenacyber/perfsprint v0.2.0/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= +github.com/ccojocar/zxcvbn-go v1.0.1 h1:+sxrANSCj6CdadkcMnvde/GWU1vZiiXRbqYSCalV4/4= +github.com/ccojocar/zxcvbn-go v1.0.1/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= +github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= +github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= +github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= +github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= +github.com/daixiang0/gci v0.11.2 h1:Oji+oPsp3bQ6bNNgX30NBAVT18P4uBH4sRZnlOlTj7Y= +github.com/daixiang0/gci v0.11.2/go.mod h1:xtHP9N7AHdNvtRNfcx9gwTDfw7FRJx4bZUsiEfiNNAI= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denis-tingaikin/go-header v0.4.3 h1:tEaZKAlqql6SKCY++utLmkPLd6K8IBM20Ha7UVm+mtU= +github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYBAC2Mra5RassOIQ2/c= +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= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/esimonov/ifshort v1.0.4 h1:6SID4yGWfRae/M7hkVDVVyppy8q/v9OuxNdmjLQStBA= +github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0= +github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= +github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y= +github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= +github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= +github.com/ghostiam/protogetter v0.2.3 h1:qdv2pzo3BpLqezwqfGDLZ+nHEYmc5bUpIdsMbBVwMjw= +github.com/ghostiam/protogetter v0.2.3/go.mod h1:KmNLOsy1v04hKbvZs8EfGI1fk39AgTdRDxWNYPfXVc4= +github.com/go-critic/go-critic v0.9.0 h1:Pmys9qvU3pSML/3GEQ2Xd9RZ/ip+aXHKILuxczKGV/U= +github.com/go-critic/go-critic v0.9.0/go.mod h1:5P8tdXL7m/6qnyG6oRAlYLORvoXH0WDypYgAEmagT40= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= +github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= +github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= +github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw= +github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= +github.com/go-toolsmith/astequal v1.1.0 h1:kHKm1AWqClYn15R0K1KKE4RG614D46n+nqUQ06E1dTw= +github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ= +github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco= +github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4= +github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= +github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA= +github.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk= +github.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus= +github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= +github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw= +github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= +github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= +github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= +github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U= +github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +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 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= +github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe h1:6RGUuS7EGotKx6J5HIP8ZtyMdiDscjMLfRBSPuzVVeo= +github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe/go.mod h1:gjqyPShc/m8pEMpk0a3SeagVb0kaqvhscv+i9jI5ZhQ= +github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e h1:ULcKCDV1LOZPFxGZaA6TlQbiM3J2GCPnkx/bGF6sX/g= +github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e/go.mod h1:Pm5KhLPA8gSnQwrQ6ukebRcapGb/BG9iUkdaiCcGHJM= +github.com/golangci/golangci-lint v1.55.2 h1:yllEIsSJ7MtlDBwDJ9IMBkyEUz2fYE0b5B8IUgO1oP8= +github.com/golangci/golangci-lint v1.55.2/go.mod h1:H60CZ0fuqoTwlTvnbyjhpZPWp7KmsjwV2yupIMiMXbM= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= +github.com/golangci/misspell v0.4.1 h1:+y73iSicVy2PqyX7kmUefHusENlrP9YwuHZHPLGQj/g= +github.com/golangci/misspell v0.4.1/go.mod h1:9mAN1quEo3DlpbaIKKyEvRxK1pwqR9s/Sea1bJCtlNI= +github.com/golangci/revgrep v0.5.2 h1:EndcWoRhcnfj2NHQ+28hyuXpLMF+dQmCN+YaeeIl4FU= +github.com/golangci/revgrep v0.5.2/go.mod h1:bjAMA+Sh/QUfTDcHzxfyHxr4xKvllVr/0sCv2e7jJHA= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601 h1:mrEEilTAUmaAORhssPPkxj84TsHrPMLBGW2Z4SoTxm8= +github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= +github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= +github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= +github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= +github.com/gostaticanalysis/comment v1.4.2 h1:hlnx5+S2fY9Zo9ePo4AhgYsYHbM2+eAv8m/s1JiCd6Q= +github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= +github.com/gostaticanalysis/forcetypeassert v0.1.0 h1:6eUflI3DiGusXGK6X7cCcIgVCpZ2CiZ1Q7jl6ZxNV70= +github.com/gostaticanalysis/forcetypeassert v0.1.0/go.mod h1:qZEedyP/sY1lTGV1uJ3VhWZ2mqag3IkWsDHVbplHXak= +github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk= +github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= +github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= +github.com/gostaticanalysis/testutil v0.4.0 h1:nhdCmubdmDF6VEatUNjgUZBJKWRqugoISdUv3PPQgHY= +github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jgautheron/goconst v1.6.0 h1:gbMLWKRMkzAc6kYsQL6/TxaoBUg3Jm9LSF/Ih1ADWGA= +github.com/jgautheron/goconst v1.6.0/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= +github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= +github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= +github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af h1:KA9BjwUk7KlCh6S9EAGWBt1oExIUv9WyNCiRz5amv48= +github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= +github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= +github.com/kisielk/errcheck v1.6.3 h1:dEKh+GLHcWm2oN34nMvDzn1sqI0i0WxPvrgiJA5JuM8= +github.com/kisielk/errcheck v1.6.3/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkHAIKE/contextcheck v1.1.4 h1:B6zAaLhOEEcjvUgIYEqystmnFk1Oemn8bvJhbt0GMb8= +github.com/kkHAIKE/contextcheck v1.1.4/go.mod h1:1+i/gWqokIa+dm31mqGLZhZJ7Uh44DJGZVmr6QRBNJg= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= +github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= +github.com/kunwardeep/paralleltest v1.0.8 h1:Ul2KsqtzFxTlSU7IP0JusWlLiNqQaloB9vguyjbE558= +github.com/kunwardeep/paralleltest v1.0.8/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= +github.com/kyoh86/exportloopref v0.1.11 h1:1Z0bcmTypkL3Q4k+IDHMWTcnCliEZcaPiIe0/ymEyhQ= +github.com/kyoh86/exportloopref v0.1.11/go.mod h1:qkV4UF1zGl6EkF1ox8L5t9SwyeBAZ3qLMd6up458uqA= +github.com/ldez/gomoddirectives v0.2.3 h1:y7MBaisZVDYmKvt9/l1mjNCiSA1BVn34U0ObUcJwlhA= +github.com/ldez/gomoddirectives v0.2.3/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0= +github.com/ldez/tagliatelle v0.5.0 h1:epgfuYt9v0CG3fms0pEgIMNPuFf/LpPIfjk4kyqSioo= +github.com/ldez/tagliatelle v0.5.0/go.mod h1:rj1HmWiL1MiKQuOONhd09iySTEkUuE/8+5jtPYz9xa4= +github.com/leonklingele/grouper v1.1.1 h1:suWXRU57D4/Enn6pXR0QVqqWWrnJ9Osrz+5rjt8ivzU= +github.com/leonklingele/grouper v1.1.1/go.mod h1:uk3I3uDfi9B6PeUjsCKi6ndcf63Uy7snXgR4yDYQVDY= +github.com/lufeee/execinquery v1.2.1 h1:hf0Ems4SHcUGBxpGN7Jz78z1ppVkP/837ZlETPCEtOM= +github.com/lufeee/execinquery v1.2.1/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM= +github.com/macabu/inamedparam v0.1.2 h1:RR5cnayM6Q7cDhQol32DE2BGAPGMnffJ31LFE+UklaU= +github.com/macabu/inamedparam v0.1.2/go.mod h1:Xg25QvY7IBRl1KLPV9Rbml8JOMZtF/iAkNkmV7eQgjw= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= +github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= +github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= +github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc= +github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 h1:gWg6ZQ4JhDfJPqlo2srm/LN17lpybq15AryXIRcWYLE= +github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= +github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mbilski/exhaustivestruct v1.2.0 h1:wCBmUnSYufAHO6J4AVWY6ff+oxWxsVFrwgOdMUQePUo= +github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc= +github.com/mgechev/revive v1.3.4 h1:k/tO3XTaWY4DEHal9tWBkkUMJYO/dLDVyMmAQxmIMDc= +github.com/mgechev/revive v1.3.4/go.mod h1:W+pZCMu9qj8Uhfs1iJMQsEFLRozUfvwFwqVvRbSNLVw= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/moricho/tparallel v0.3.1 h1:fQKD4U1wRMAYNngDonW5XupoB/ZGJHdpzrWqgyg9krA= +github.com/moricho/tparallel v0.3.1/go.mod h1:leENX2cUv7Sv2qDgdi0D0fCftN8fRC67Bcn8pqzeYNI= +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/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= +github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nishanths/exhaustive v0.11.0 h1:T3I8nUGhl/Cwu5Z2hfc92l0e04D2GEW6e0l8pzda2l0= +github.com/nishanths/exhaustive v0.11.0/go.mod h1:RqwDsZ1xY0dNdqHho2z6X+bgzizwbLYOWnZbbl2wLB4= +github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= +github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= +github.com/nunnatsa/ginkgolinter v0.14.1 h1:khx0CqR5U4ghsscjJ+lZVthp3zjIFytRXPTaQ/TMiyA= +github.com/nunnatsa/ginkgolinter v0.14.1/go.mod h1:nY0pafUSst7v7F637e7fymaMlQqI9c0Wka2fGsDkzWg= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= +github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/gomega v1.28.1 h1:MijcGUbfYuznzK/5R4CPNoUP/9Xvuo20sXfEm6XxoTA= +github.com/onsi/gomega v1.28.1/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= +github.com/otiai10/copy v1.11.0 h1:OKBD80J/mLBrwnzXqGtFCzprFSGioo30JcmR4APsNwc= +github.com/otiai10/copy v1.11.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= +github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= +github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= +github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/polyfloyd/go-errorlint v1.4.5 h1:70YWmMy4FgRHehGNOUask3HtSFSOLKgmDn7ryNe7LqI= +github.com/polyfloyd/go-errorlint v1.4.5/go.mod h1:sIZEbFoDOCnTYYZoVkjc4hTnM459tuWA9H/EkdXwsKk= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/quasilyte/go-ruleguard v0.4.0 h1:DyM6r+TKL+xbKB4Nm7Afd1IQh9kEUKQs2pboWGKtvQo= +github.com/quasilyte/go-ruleguard v0.4.0/go.mod h1:Eu76Z/R8IXtViWUIHkE3p8gdH3/PKk1eh3YGfaEof10= +github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= +github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= +github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= +github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= +github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= +github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryancurrah/gomodguard v1.3.0 h1:q15RT/pd6UggBXVBuLps8BXRvl5GPBcwVA7BJHMLuTw= +github.com/ryancurrah/gomodguard v1.3.0/go.mod h1:ggBxb3luypPEzqVtq33ee7YSN35V28XeGnid8dnni50= +github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= +github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= +github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/NsJFwFK7Uxc= +github.com/sanposhiho/wastedassign/v2 v2.0.7/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= +github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= +github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= +github.com/sashamelentyev/usestdlibvars v1.24.0 h1:MKNzmXtGh5N0y74Z/CIaJh4GlB364l0K1RUT08WSWAc= +github.com/sashamelentyev/usestdlibvars v1.24.0/go.mod h1:9cYkq+gYJ+a5W2RPdhfaSCnTVUC1OQP/bSiiBhq3OZE= +github.com/securego/gosec/v2 v2.18.2 h1:DkDt3wCiOtAHf1XkiXZBhQ6m6mK/b9T/wD257R3/c+I= +github.com/securego/gosec/v2 v2.18.2/go.mod h1:xUuqSF6i0So56Y2wwohWAmB07EdBkUN6crbLlHwbyJs= +github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= +github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/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/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= +github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= +github.com/sivchari/nosnakecase v1.7.0 h1:7QkpWIRMe8x25gckkFd2A5Pi6Ymo0qgr4JrhGt95do8= +github.com/sivchari/nosnakecase v1.7.0/go.mod h1:CwDzrzPea40/GB6uynrNLiorAlgFRvRbFSgJx2Gs+QY= +github.com/sivchari/tenv v1.7.1 h1:PSpuD4bu6fSmtWMxSGWcvqUUgIn7k3yOJhOIzVWn8Ak= +github.com/sivchari/tenv v1.7.1/go.mod h1:64yStXKSOxDfX47NlhVwND4dHwfZDdbp2Lyl018Icvg= +github.com/sonatard/noctx v0.0.2 h1:L7Dz4De2zDQhW8S0t+KUjY0MAQJd6SgVwhzNIc4ok00= +github.com/sonatard/noctx v0.0.2/go.mod h1:kzFz+CzWSjQ2OzIm46uJZoXuBpa2+0y3T36U18dWqIo= +github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= +github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= +github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= +github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= +github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= +github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= +github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= +github.com/stbenjam/no-sprintf-host-port v0.1.1 h1:tYugd/yrm1O0dV+ThCbaKZh195Dfm07ysF0U6JQXczc= +github.com/stbenjam/no-sprintf-host-port v0.1.1/go.mod h1:TLhvtIvONRzdmkFiio4O8LHsN9N74I+PhRquPsxpL0I= +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 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.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/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= +github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c h1:+aPplBwWcHBo6q9xrfWdMrT9o4kltkmmvpemgIjep/8= +github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c/go.mod h1:SbErYREK7xXdsRiigaQiQkI9McGRzYMvlKYaP3Nimdk= +github.com/tdakkota/asciicheck v0.2.0 h1:o8jvnUANo0qXtnslk2d3nMKTFNlOnJjRrNcj0j9qkHM= +github.com/tdakkota/asciicheck v0.2.0/go.mod h1:Qb7Y9EgjCLJGup51gDHFzbI08/gbGhL/UVhYIPWG2rg= +github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= +github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= +github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= +github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= +github.com/tetafro/godot v1.4.15 h1:QzdIs+XB8q+U1WmQEWKHQbKmCw06QuQM7gLx/dky2RM= +github.com/tetafro/godot v1.4.15/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= +github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 h1:quvGphlmUVU+nhpFa4gg4yJyTRJ13reZMDHrKwYw53M= +github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966/go.mod h1:27bSVNWSBOHm+qRp1T9qzaIpsWEP6TbUnei/43HK+PQ= +github.com/timonwong/loggercheck v0.9.4 h1:HKKhqrjcVj8sxL7K77beXh0adEm6DLjV/QOGeMXEVi4= +github.com/timonwong/loggercheck v0.9.4/go.mod h1:caz4zlPcgvpEkXgVnAJGowHAMW2NwHaNlpS8xDbVhTg= +github.com/tomarrell/wrapcheck/v2 v2.8.1 h1:HxSqDSN0sAt0yJYsrcYVoEeyM4aI9yAm3KQpIXDJRhQ= +github.com/tomarrell/wrapcheck/v2 v2.8.1/go.mod h1:/n2Q3NZ4XFT50ho6Hbxg+RV1uyo2Uow/Vdm9NQcl5SE= +github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= +github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= +github.com/ultraware/funlen v0.1.0 h1:BuqclbkY6pO+cvxoq7OsktIXZpgBSkYTQtmwhAK81vI= +github.com/ultraware/funlen v0.1.0/go.mod h1:XJqmOQja6DpxarLj6Jj1U7JuoS8PvL4nEqDaQhy22p4= +github.com/ultraware/whitespace v0.0.5 h1:hh+/cpIcopyMYbZNVov9iSxvJU3OYQg78Sfaqzi/CzI= +github.com/ultraware/whitespace v0.0.5/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= +github.com/uudashr/gocognit v1.1.2 h1:l6BAEKJqQH2UpKAPKdMfZf5kE4W/2xk8pfU1OVLvniI= +github.com/uudashr/gocognit v1.1.2/go.mod h1:aAVdLURqcanke8h3vg35BC++eseDm66Z7KmchI5et4k= +github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= +github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= +github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= +github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= +github.com/yeya24/promlinter v0.2.0 h1:xFKDQ82orCU5jQujdaD8stOHiv8UN68BSdn2a8u8Y3o= +github.com/yeya24/promlinter v0.2.0/go.mod h1:u54lkmBOZrpEbQQ6gox2zWKKLKu2SGe+2KOiextY+IA= +github.com/ykadowak/zerologlint v0.1.3 h1:TLy1dTW3Nuc+YE3bYRPToG1Q9Ej78b5UUN6bjbGdxPE= +github.com/ykadowak/zerologlint v0.1.3/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +gitlab.com/bosi/decorder v0.4.1 h1:VdsdfxhstabyhZovHafFw+9eJ6eU0d2CkFNJcZz/NU4= +gitlab.com/bosi/decorder v0.4.1/go.mod h1:jecSqWUew6Yle1pCr2eLWTensJMmsxHsBwt+PVbkAqA= +go-simpler.org/assert v0.6.0 h1:QxSrXa4oRuo/1eHMXSBFHKvJIpWABayzKldqZyugG7E= +go-simpler.org/assert v0.6.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= +go-simpler.org/sloglint v0.1.2 h1:IjdhF8NPxyn0Ckn2+fuIof7ntSnVUAqBFcQRrnG9AiM= +go-simpler.org/sloglint v0.1.2/go.mod h1:2LL+QImPfTslD5muNPydAEYmpXIj6o/WYcqnJjLi4o4= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.tmz.dev/musttag v0.7.2 h1:1J6S9ipDbalBSODNT5jCep8dhZyMr4ttnjQagmGYR5s= +go.tmz.dev/musttag v0.7.2/go.mod h1:m6q5NiiSKMnQYokefa2xGoyoXnrswCbJ0AWYzf4Zs28= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-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-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4= +golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20230307190834-24139beb5833 h1:jWGQJV4niP+CCmFW9ekjA9Zx8vYORzOUH2/Nl5WPuLQ= +golang.org/x/exp/typeparams v0.0.0-20230307190834-24139beb5833/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-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-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/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-20220702020025-31831981b65f/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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.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.6.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.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190321232350-e250d351ecad/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201001104356-43ebab892c4c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= +golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= +golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +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.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +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-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +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.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.4.6 h1:oFEHCKeID7to/3autwsWfnuv69j3NsfcXbvJKuIcep8= +honnef.co/go/tools v0.4.6/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= +mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E= +mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= +mvdan.cc/unparam v0.0.0-20221223090309-7455f1af531d h1:3rvTIIM22r9pvXk+q3swxUQAQOxksVMGK7sml4nG57w= +mvdan.cc/unparam v0.0.0-20221223090309-7455f1af531d/go.mod h1:IeHQjmn6TOD+e4Z3RFiZMMsLVL+A96Nvptar8Fj71is= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/tools/tools.go b/tools/tools.go new file mode 100644 index 0000000000..41172d6fd9 --- /dev/null +++ b/tools/tools.go @@ -0,0 +1,7 @@ +//go:build tools + +package tools + +import ( + _ "github.com/golangci/golangci-lint/cmd/golangci-lint" +) From 4ad41bf105d8aec940384b40d53c8c6011632816 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Wed, 17 Jan 2024 09:28:46 -0400 Subject: [PATCH 0006/1670] Fix linter issues --- cmd/oidc-broker/daemon/daemon_test.go | 3 ++- internal/broker/broker.go | 2 +- internal/brokerservice/brokerservice.go | 5 ++--- internal/log/log.go | 1 + 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cmd/oidc-broker/daemon/daemon_test.go b/cmd/oidc-broker/daemon/daemon_test.go index 46a79f7ddb..c2a68c1120 100644 --- a/cmd/oidc-broker/daemon/daemon_test.go +++ b/cmd/oidc-broker/daemon/daemon_test.go @@ -117,7 +117,6 @@ func TestAppRunFailsOnComponentsCreationAndQuit(t *testing.T) { // TODO t.Fail() - } func TestAppCanSigHupWhenExecute(t *testing.T) { @@ -286,6 +285,8 @@ func requireGoroutineStarted(t *testing.T, f func()) { // startDaemon prepares and starts the daemon in the background. The done function should be called // to wait for the daemon to stop. +// +//nolint:unparam // TODO: This should be removed once we implement the config tests. func startDaemon(t *testing.T, conf *daemon.DaemonConfig) (app *daemon.App, done func()) { t.Helper() diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 2c172d2257..2f506c481c 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -1,4 +1,4 @@ -// package broker is the generic oidc business code. +// Package broker is the generic oidc business code. package broker import ( diff --git a/internal/brokerservice/brokerservice.go b/internal/brokerservice/brokerservice.go index df33c7e508..177c2bfa3d 100644 --- a/internal/brokerservice/brokerservice.go +++ b/internal/brokerservice/brokerservice.go @@ -1,4 +1,4 @@ -// package brokerservice is the dbus service implementation delegating its functional call to brokers. +// Package brokerservice is the dbus service implementation delegating its functional call to brokers. package brokerservice import ( @@ -9,7 +9,6 @@ import ( "github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5/introspect" "github.com/ubuntu/decorate" - "github.com/ubuntu/oidc-broker/internal/broker" ) @@ -56,7 +55,7 @@ type Service struct { } // New returns a new dbus service after exporting to the system bus our name. -func New(ctx context.Context, serviceName string) (s *Service, err error) { +func New(_ context.Context, serviceName string) (s *Service, err error) { serviceName = strings.ReplaceAll(serviceName, "-", "_") defer decorate.OnError(&err, "cannot create dbus service %q", serviceName) diff --git a/internal/log/log.go b/internal/log/log.go index e15cf31b6f..65f38886f6 100644 --- a/internal/log/log.go +++ b/internal/log/log.go @@ -1,3 +1,4 @@ +// Package log configures the logging utilities for the project. package log import ( From c8d85dcdd1240704c9e42cfb5c151fb7e9831910 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Thu, 18 Jan 2024 06:11:59 -0400 Subject: [PATCH 0007/1670] Update go.mod syntax and bump version to 1.21.6 --- go.mod | 4 +++- tools/go.mod | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 19ee646c5b..52d1bea577 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/ubuntu/oidc-broker -go 1.21.5 +go 1.21.0 + +toolchain go1.21.6 require ( github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf diff --git a/tools/go.mod b/tools/go.mod index ec8057ae5d..0b3bcd22a7 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -1,6 +1,8 @@ module github.com/ubuntu/oidc-broker/tools -go 1.21.5 +go 1.21.0 + +toolchain go1.21.6 require github.com/golangci/golangci-lint v1.55.2 From 5af4cb71204226ebeceb9ecabda504b38cf7f282 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Wed, 24 Jan 2024 07:52:08 +0100 Subject: [PATCH 0008/1670] Ship generic oicd configuration This configuration should be functional for the default oidc broker. It will then be transformed when doing the snap build. UDENG-2046 --- config/oidc-broker.broker | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 config/oidc-broker.broker diff --git a/config/oidc-broker.broker b/config/oidc-broker.broker new file mode 100644 index 0000000000..d372a35b25 --- /dev/null +++ b/config/oidc-broker.broker @@ -0,0 +1,5 @@ +[authd] +name = OIDC Broker +brand_icon = broker_icon.png +dbus_name = com.ubuntu.authd.oidc_broker +dbus_object = /com/ubuntu/authd/oidc_broker From 88260db4a9186a6eafdb9951716172d7f5e179a8 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Mon, 22 Jan 2024 06:58:48 -0400 Subject: [PATCH 0009/1670] Allow to create broker daemon with custom name The daemon used to rely on the executable name to define its internal name. This is far from ideal when dealing with tests (especially in parallel). Now, the New() function takes a name argument that allows setting a custom name for the daemon. This is very useful for tests. --- cmd/oidc-broker/daemon/config.go | 2 +- cmd/oidc-broker/daemon/daemon.go | 22 ++++++++++------------ cmd/oidc-broker/daemon/daemon_test.go | 15 +++++---------- cmd/oidc-broker/daemon/export_test.go | 2 +- cmd/oidc-broker/daemon/version.go | 6 +++--- cmd/oidc-broker/main.go | 3 ++- 6 files changed, 22 insertions(+), 28 deletions(-) diff --git a/cmd/oidc-broker/daemon/config.go b/cmd/oidc-broker/daemon/config.go index 2b0be1c949..09d6c9320d 100644 --- a/cmd/oidc-broker/daemon/config.go +++ b/cmd/oidc-broker/daemon/config.go @@ -37,7 +37,7 @@ func initViperConfig(name string, cmd *cobra.Command, vip *viper.Viper) (err err vip.AddConfigPath("./") vip.AddConfigPath("$HOME/") vip.AddConfigPath("$SNAP_DATA/") - vip.AddConfigPath(filepath.Join("/etc", cmdName)) + vip.AddConfigPath(filepath.Join("/etc", name)) // Add the executable path to the config search path. if binPath, err := os.Executable(); err != nil { slog.Warn(fmt.Sprintf("Failed to get current executable path, not adding it as a config dir: %v", err)) diff --git a/cmd/oidc-broker/daemon/daemon.go b/cmd/oidc-broker/daemon/daemon.go index a5cc8754f6..25b6b8222f 100644 --- a/cmd/oidc-broker/daemon/daemon.go +++ b/cmd/oidc-broker/daemon/daemon.go @@ -17,9 +17,6 @@ import ( "github.com/ubuntu/oidc-broker/internal/daemon" ) -// cmdName is the binary name for the agent. -var cmdName = filepath.Base(os.Args[0]) - // App encapsulate commands and options of the daemon, which can be controlled by env variables and config files. type App struct { rootCmd cobra.Command @@ -27,6 +24,7 @@ type App struct { config daemonConfig daemon *daemon.Daemon + name string ready chan struct{} } @@ -44,31 +42,31 @@ type daemonConfig struct { } // New registers commands and return a new App. -func New() *App { - a := App{ready: make(chan struct{})} +func New(name string) *App { + a := App{ready: make(chan struct{}), name: name} a.rootCmd = cobra.Command{ - Use: fmt.Sprintf("%s COMMAND", cmdName), - Short: fmt.Sprintf("%s authentication broker", cmdName), - Long: fmt.Sprintf("Authentication daemon %s to communicate with our authentication daemon.", cmdName), + Use: fmt.Sprintf("%s COMMAND", name), + Short: fmt.Sprintf("%s authentication broker", name), + Long: fmt.Sprintf("Authentication daemon %s to communicate with our authentication daemon.", name), Args: cobra.NoArgs, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { // Command parsing has been successful. Returns to not print usage anymore. a.rootCmd.SilenceUsage = true // Set config defaults - systemCache := filepath.Join("/var", "lib", cmdName) + systemCache := filepath.Join("/var", "lib", name) if snapData := os.Getenv("SNAP_DATA"); snapData != "" { systemCache = filepath.Join(snapData, "cache") } a.config = daemonConfig{ Paths: systemPaths{ - BrokersConf: filepath.Join(consts.DefaultBrokersConfPath, cmdName), + BrokersConf: filepath.Join(consts.DefaultBrokersConfPath, name), Cache: systemCache, }, } // Install and unmarshall configuration - if err := initViperConfig(cmdName, &a.rootCmd, a.viper); err != nil { + if err := initViperConfig(name, &a.rootCmd, a.viper); err != nil { return err } if err := a.viper.Unmarshal(&a.config); err != nil { @@ -108,7 +106,7 @@ func (a *App) serve(config daemonConfig) error { return fmt.Errorf("error initializing users cache directory at %q: %v", config.Paths.Cache, err) } - s, err := brokerservice.New(ctx, cmdName) + s, err := brokerservice.New(ctx, a.name) if err != nil { close(a.ready) return err diff --git a/cmd/oidc-broker/daemon/daemon_test.go b/cmd/oidc-broker/daemon/daemon_test.go index c2a68c1120..07ceb9c233 100644 --- a/cmd/oidc-broker/daemon/daemon_test.go +++ b/cmd/oidc-broker/daemon/daemon_test.go @@ -48,9 +48,7 @@ func TestVersion(t *testing.T) { fields := strings.Fields(out) require.Len(t, fields, 2, "wrong number of fields in version: %s", out) - want := "authd" - - require.Equal(t, want, fields[0], "Wrong executable name") + require.Equal(t, t.Name(), fields[0], "Wrong executable name") require.Equal(t, consts.Version, fields[1], "Wrong version") } @@ -224,8 +222,7 @@ func TestAutoDetectConfig(t *testing.T) { // Remove configuration next binary for other tests to not pick it up. defer os.Remove(configNextToBinaryPath) - a := daemon.New() - + a := daemon.New(t.Name()) wg := sync.WaitGroup{} wg.Add(1) go func() { @@ -248,21 +245,19 @@ func TestNoConfigSetDefaults(t *testing.T) { // TODO t.Fail() - a := daemon.New() - // Use version to still run preExec to load no config but without running server + a := daemon.New(t.Name()) // Use version to still run preExec to load no config but without running server a.SetArgs("version") err := a.Run() require.NoError(t, err, "Run should not return an error") require.Equal(t, 0, a.Config().Verbosity, "Default Verbosity") - require.Equal(t, consts.DefaultBrokersConfPath, a.Config().Paths.BrokersConf, "Default brokers configuration path") + require.Equal(t, filepath.Join(consts.DefaultBrokersConfPath, t.Name()), a.Config().Paths.BrokersConf, "Default brokers configuration path") //require.Equal(t, consts.DefaultCacheDir, a.Config().Paths.Cache, "Default cache directory") } func TestBadConfigReturnsError(t *testing.T) { - a := daemon.New() - // Use version to still run preExec to load no config but without running server + a := daemon.New(t.Name()) // Use version to still run preExec to load no config but without running server a.SetArgs("version", "--config", "/does/not/exist.yaml") err := a.Run() diff --git a/cmd/oidc-broker/daemon/export_test.go b/cmd/oidc-broker/daemon/export_test.go index a2f67ef986..27abc1abcd 100644 --- a/cmd/oidc-broker/daemon/export_test.go +++ b/cmd/oidc-broker/daemon/export_test.go @@ -21,7 +21,7 @@ func NewForTests(t *testing.T, conf *DaemonConfig, args ...string) *App { argsWithConf := []string{"--config", p} argsWithConf = append(argsWithConf, args...) - a := New() + a := New(t.Name()) a.rootCmd.SetArgs(argsWithConf) return a } diff --git a/cmd/oidc-broker/daemon/version.go b/cmd/oidc-broker/daemon/version.go index 5136a17be3..b814501967 100644 --- a/cmd/oidc-broker/daemon/version.go +++ b/cmd/oidc-broker/daemon/version.go @@ -12,13 +12,13 @@ func (a *App) installVersion() { Use: "version", Short:/*i18n.G(*/ "Returns version of daemon and exits", /*)*/ Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { return getVersion() }, + RunE: func(cmd *cobra.Command, args []string) error { return a.getVersion() }, } a.rootCmd.AddCommand(cmd) } // getVersion returns the current service version. -func getVersion() (err error) { - fmt.Printf( /*i18n.G(*/ "%s\t%s" /*)*/ +"\n", cmdName, consts.Version) +func (a *App) getVersion() (err error) { + fmt.Printf( /*i18n.G(*/ "%s\t%s" /*)*/ +"\n", a.name, consts.Version) return nil } diff --git a/cmd/oidc-broker/main.go b/cmd/oidc-broker/main.go index 212104dc38..f630b1ab29 100644 --- a/cmd/oidc-broker/main.go +++ b/cmd/oidc-broker/main.go @@ -5,6 +5,7 @@ import ( "log/slog" "os" "os/signal" + "path/filepath" "sync" "syscall" @@ -20,7 +21,7 @@ import ( func main() { i18n.InitI18nDomain(consts.TEXTDOMAIN, po.Files) - a := daemon.New() + a := daemon.New(filepath.Base(os.Args[0])) os.Exit(run(a)) } From f2222e49adc9c9b8cc4b7463def43ac970a6ad94 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Mon, 22 Jan 2024 07:01:41 -0400 Subject: [PATCH 0010/1670] Adjust naming for systemPaths This is a broker daemon, it should only know about its configuration and not about the general broker configuration directory --- cmd/oidc-broker/daemon/daemon.go | 8 ++++---- cmd/oidc-broker/daemon/daemon_test.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/oidc-broker/daemon/daemon.go b/cmd/oidc-broker/daemon/daemon.go index 25b6b8222f..6a6d619ff0 100644 --- a/cmd/oidc-broker/daemon/daemon.go +++ b/cmd/oidc-broker/daemon/daemon.go @@ -31,8 +31,8 @@ type App struct { // only overriable for tests. type systemPaths struct { - BrokersConf string - Cache string + BrokerConf string + Cache string } // daemonConfig defines configuration parameters of the daemon. @@ -60,8 +60,8 @@ func New(name string) *App { } a.config = daemonConfig{ Paths: systemPaths{ - BrokersConf: filepath.Join(consts.DefaultBrokersConfPath, name), - Cache: systemCache, + BrokerConf: filepath.Join(consts.DefaultBrokersConfPath, name), + Cache: systemCache, }, } diff --git a/cmd/oidc-broker/daemon/daemon_test.go b/cmd/oidc-broker/daemon/daemon_test.go index 07ceb9c233..7f39f21d8b 100644 --- a/cmd/oidc-broker/daemon/daemon_test.go +++ b/cmd/oidc-broker/daemon/daemon_test.go @@ -252,7 +252,7 @@ func TestNoConfigSetDefaults(t *testing.T) { require.NoError(t, err, "Run should not return an error") require.Equal(t, 0, a.Config().Verbosity, "Default Verbosity") - require.Equal(t, filepath.Join(consts.DefaultBrokersConfPath, t.Name()), a.Config().Paths.BrokersConf, "Default brokers configuration path") + require.Equal(t, filepath.Join(consts.DefaultBrokersConfPath, t.Name()), a.Config().Paths.BrokerConf, "Default broker configuration path") //require.Equal(t, consts.DefaultCacheDir, a.Config().Paths.Cache, "Default cache directory") } From 17f960241b3ecde411938577c22e0d8e1e991022 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Mon, 22 Jan 2024 07:17:08 -0400 Subject: [PATCH 0011/1670] Prevent s.Stop function from panicking if called many times --- internal/brokerservice/brokerservice.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/internal/brokerservice/brokerservice.go b/internal/brokerservice/brokerservice.go index 177c2bfa3d..5f311ce8d2 100644 --- a/internal/brokerservice/brokerservice.go +++ b/internal/brokerservice/brokerservice.go @@ -107,7 +107,12 @@ func (s *Service) Serve() error { // Stop stop the service and do all the necessary cleanup operation. func (s *Service) Stop() error { - close(s.serve) - s.disconnect() + // Check if already stopped. + select { + case <-s.serve: + default: + close(s.serve) + s.disconnect() + } return nil } From 60dd22fdb40de30c8c36d19203fe27225c19a051 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 30 Jan 2024 07:15:48 -0400 Subject: [PATCH 0012/1670] Complete cmd/daemon unit tests --- cmd/oidc-broker/daemon/daemon_test.go | 98 +++++++++++++++++++-------- 1 file changed, 69 insertions(+), 29 deletions(-) diff --git a/cmd/oidc-broker/daemon/daemon_test.go b/cmd/oidc-broker/daemon/daemon_test.go index 7f39f21d8b..d08358ec31 100644 --- a/cmd/oidc-broker/daemon/daemon_test.go +++ b/cmd/oidc-broker/daemon/daemon_test.go @@ -110,11 +110,52 @@ func TestAppCanQuitWithoutExecute(t *testing.T) { func TestAppRunFailsOnComponentsCreationAndQuit(t *testing.T) { t.Parallel() - // Trigger the error with a cache directory that cannot be created over an - // existing file - - // TODO - t.Fail() + const ( + // Cache errors + dirIsFile = iota + wrongPermission + noParentDir + ) + + tests := map[string]struct { + cachePathBehavior int + configBehavior int + }{ + "Error on existing cache path being a file": {cachePathBehavior: dirIsFile}, + "Error on cache path missing parent directory": {cachePathBehavior: noParentDir}, + "Error on wrong permission on cache path": {cachePathBehavior: wrongPermission}, + } + for name, tc := range tests { + tc := tc + t.Run(name, func(t *testing.T) { + t.Parallel() + + tmpDir := t.TempDir() + cachePath := filepath.Join(tmpDir, "cache") + + switch tc.cachePathBehavior { + case dirIsFile: + err := os.WriteFile(cachePath, []byte("file"), 0600) + require.NoError(t, err, "Setup: could not create cache file for tests") + case wrongPermission: + err := os.Mkdir(cachePath, 0600) + require.NoError(t, err, "Setup: could not create cache directory for tests") + case noParentDir: + cachePath = filepath.Join(tmpDir, "doesnotexist", "cache") + } + + config := daemon.DaemonConfig{ + Verbosity: 0, + Paths: daemon.SystemPaths{ + Cache: cachePath, + }, + } + + a := daemon.NewForTests(t, &config) + err := a.Run() + require.Error(t, err, "Run should return an error") + }) + } } func TestAppCanSigHupWhenExecute(t *testing.T) { @@ -190,33 +231,36 @@ func TestAppGetRootCmd(t *testing.T) { } func TestConfigLoad(t *testing.T) { - // TODO - t.Fail() + t.Parallel() - /*customizedSocketPath := filepath.Join(t.TempDir(), "mysocket") - var config daemon.DaemonConfig - config.Verbosity = 1 - config.Paths.Socket = customizedSocketPath + tmpDir := t.TempDir() + config := daemon.DaemonConfig{ + Verbosity: 1, + Paths: daemon.SystemPaths{ + BrokerConf: filepath.Join(tmpDir, "broker.conf"), + Cache: filepath.Join(tmpDir, "cache"), + }, + } a, wait := startDaemon(t, &config) defer wait() defer a.Quit() - _, err := os.Stat(customizedSocketPath) - require.NoError(t, err, "Socket should exist") - require.Equal(t, 1, a.Config().Verbosity, "Verbosity is set from config")*/ + require.Equal(t, config, a.Config(), "Config is loaded") } func TestAutoDetectConfig(t *testing.T) { - // TODO - t.Fail() - - customizedSocketPath := filepath.Join(t.TempDir(), "mysocket") - var config daemon.DaemonConfig - config.Verbosity = 1 + tmpDir := t.TempDir() + config := daemon.DaemonConfig{ + Verbosity: 1, + Paths: daemon.SystemPaths{ + BrokerConf: filepath.Join(tmpDir, "broker.conf"), + Cache: filepath.Join(tmpDir, "cache"), + }, + } configPath := daemon.GenerateTestConfig(t, &config) - configNextToBinaryPath := filepath.Join(filepath.Dir(os.Args[0]), "authd.yaml") + configNextToBinaryPath := filepath.Join(filepath.Dir(os.Args[0]), t.Name()+".yaml") err := os.Rename(configPath, configNextToBinaryPath) require.NoError(t, err, "Could not relocate authd configuration file in the binary directory") // Remove configuration next binary for other tests to not pick it up. @@ -236,14 +280,12 @@ func TestAutoDetectConfig(t *testing.T) { defer wg.Wait() defer a.Quit() - _, err = os.Stat(customizedSocketPath) - require.NoError(t, err, "Socket should exist") - require.Equal(t, 1, a.Config().Verbosity, "Verbosity is set from config") + require.Equal(t, config, a.Config(), "Did not load configuration next to binary") } func TestNoConfigSetDefaults(t *testing.T) { - // TODO - t.Fail() + tmpDir := t.TempDir() + t.Setenv("SNAP_DATA", tmpDir) a := daemon.New(t.Name()) // Use version to still run preExec to load no config but without running server a.SetArgs("version") @@ -253,7 +295,7 @@ func TestNoConfigSetDefaults(t *testing.T) { require.Equal(t, 0, a.Config().Verbosity, "Default Verbosity") require.Equal(t, filepath.Join(consts.DefaultBrokersConfPath, t.Name()), a.Config().Paths.BrokerConf, "Default broker configuration path") - //require.Equal(t, consts.DefaultCacheDir, a.Config().Paths.Cache, "Default cache directory") + require.Equal(t, filepath.Join(tmpDir, "cache"), a.Config().Paths.Cache, "Default cache directory") } func TestBadConfigReturnsError(t *testing.T) { @@ -280,8 +322,6 @@ func requireGoroutineStarted(t *testing.T, f func()) { // startDaemon prepares and starts the daemon in the background. The done function should be called // to wait for the daemon to stop. -// -//nolint:unparam // TODO: This should be removed once we implement the config tests. func startDaemon(t *testing.T, conf *daemon.DaemonConfig) (app *daemon.App, done func()) { t.Helper() From f569eba2a4fc5e6615aa9ab99122ce21f2ba4c0f Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 12 Mar 2024 06:10:48 -0400 Subject: [PATCH 0013/1670] Bump Go version to 1.22 --- go.mod | 4 ++-- tools/go.mod | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 52d1bea577..eb623cd070 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/ubuntu/oidc-broker -go 1.21.0 +go 1.22.0 -toolchain go1.21.6 +toolchain go1.22.1 require ( github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf diff --git a/tools/go.mod b/tools/go.mod index 0b3bcd22a7..7b69431b32 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -1,8 +1,8 @@ module github.com/ubuntu/oidc-broker/tools -go 1.21.0 +go 1.22.0 -toolchain go1.21.6 +toolchain go1.22.1 require github.com/golangci/golangci-lint v1.55.2 From 2487e42022691370ee3b114e714de68e842ba3b6 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 12 Mar 2024 06:11:11 -0400 Subject: [PATCH 0014/1670] Bump dependencies to latest --- go.mod | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index eb623cd070..8d66bd1506 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,8 @@ require ( github.com/godbus/dbus/v5 v5.1.0 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.18.2 - github.com/stretchr/testify v1.8.4 - github.com/ubuntu/decorate v0.0.0-20231211084900-69db9a41777a + github.com/stretchr/testify v1.9.0 + github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 gopkg.in/yaml.v3 v3.0.1 ) @@ -23,7 +23,7 @@ require ( github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect @@ -33,10 +33,9 @@ require ( github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect - go.uber.org/atomic v1.9.0 // indirect - go.uber.org/multierr v1.9.0 // indirect - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/sys v0.15.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) From 371d77f76f10a67eb046e098b686fb319a590cb9 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 12 Mar 2024 06:11:48 -0400 Subject: [PATCH 0015/1670] Update go.sum --- go.sum | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/go.sum b/go.sum index 98a0993ed9..6d4410ccf1 100644 --- a/go.sum +++ b/go.sum @@ -27,8 +27,8 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= -github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= +github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -56,27 +56,25 @@ github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMV github.com/stretchr/objx v0.1.0/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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/ubuntu/decorate v0.0.0-20231211084900-69db9a41777a h1:VojaxMh5vh94q0uRrJP2mucbItb4gCMzEg6X1RzlM+M= -github.com/ubuntu/decorate v0.0.0-20231211084900-69db9a41777a/go.mod h1:edGgz97NOqS2oqzbKrZqO9YU9neosRrkEZbVJVQynAA= +github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 h1:qO8m+4mLbo1HRpD5lfhEfr7R1PuqZvbAmjaRzYEy+tM= +github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5/go.mod h1:PUpwIgUuCQyuCz/gwiq6WYbo7IvtXXd8JqL01ez+jZE= github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 h1:CA2dVorxvzdsGtszqhSjyvkrXxZi4bS52ZKvP0Ko634= github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47/go.mod h1:ZRhdDyx6YkKz/YiMWi0gS3uMCltgdaKz9IpkiNf/GRg= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= +golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -89,8 +87,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc 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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 3690ca9eb509a8ee5ea4064d6056e3a7cae23be4 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 12 Mar 2024 06:12:13 -0400 Subject: [PATCH 0016/1670] Refactor code to account for new go version --- cmd/oidc-broker/daemon/daemon.go | 7 +++++-- cmd/oidc-broker/daemon/daemon_test.go | 2 +- cmd/oidc-broker/main_test.go | 1 - 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/cmd/oidc-broker/daemon/daemon.go b/cmd/oidc-broker/daemon/daemon.go index 6a6d619ff0..000dab16ee 100644 --- a/cmd/oidc-broker/daemon/daemon.go +++ b/cmd/oidc-broker/daemon/daemon.go @@ -11,7 +11,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/ubuntu/decorate" "github.com/ubuntu/oidc-broker/internal/brokerservice" "github.com/ubuntu/oidc-broker/internal/consts" "github.com/ubuntu/oidc-broker/internal/daemon" @@ -129,7 +128,11 @@ func (a *App) serve(config daemonConfig) error { // installVerbosityFlag adds the -v and -vv options and returns the reference to it. func installVerbosityFlag(cmd *cobra.Command, viper *viper.Viper) *int { r := cmd.PersistentFlags().CountP("verbosity", "v" /*i18n.G(*/, "issue INFO (-v), DEBUG (-vv) or DEBUG with caller (-vvv) output") //) - decorate.LogOnError(viper.BindPFlag("verbosity", cmd.PersistentFlags().Lookup("verbosity"))) + + if err := viper.BindPFlag("verbosity", cmd.PersistentFlags().Lookup("verbosity")); err != nil { + slog.Warn(err.Error()) + } + return r } diff --git a/cmd/oidc-broker/daemon/daemon_test.go b/cmd/oidc-broker/daemon/daemon_test.go index d08358ec31..28fe128829 100644 --- a/cmd/oidc-broker/daemon/daemon_test.go +++ b/cmd/oidc-broker/daemon/daemon_test.go @@ -126,13 +126,13 @@ func TestAppRunFailsOnComponentsCreationAndQuit(t *testing.T) { "Error on wrong permission on cache path": {cachePathBehavior: wrongPermission}, } for name, tc := range tests { - tc := tc t.Run(name, func(t *testing.T) { t.Parallel() tmpDir := t.TempDir() cachePath := filepath.Join(tmpDir, "cache") + //nolint:govet // This is fixed with Go 1.22.0 and is a false positive (https://github.com/securego/gosec/pull/1108) switch tc.cachePathBehavior { case dirIsFile: err := os.WriteFile(cachePath, []byte("file"), 0600) diff --git a/cmd/oidc-broker/main_test.go b/cmd/oidc-broker/main_test.go index cb6dfaddf0..8e79d93628 100644 --- a/cmd/oidc-broker/main_test.go +++ b/cmd/oidc-broker/main_test.go @@ -61,7 +61,6 @@ func TestRun(t *testing.T) { "Send SIGHUP with exit": {sendSig: syscall.SIGHUP, hupReturn: true}, } for name, tc := range tests { - tc := tc t.Run(name, func(t *testing.T) { a := myApp{ done: make(chan struct{}), From c92616ce7b94eb48ca1db12bde31274496c08b8d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Mar 2024 11:04:05 +0000 Subject: [PATCH 0017/1670] deps(go-tools): bump github.com/golangci/golangci-lint in /tools Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.55.2 to 1.56.2. - [Release notes](https://github.com/golangci/golangci-lint/releases) - [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md) - [Commits](https://github.com/golangci/golangci-lint/compare/v1.55.2...v1.56.2) --- updated-dependencies: - dependency-name: github.com/golangci/golangci-lint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- tools/go.mod | 94 +++++++++--------- tools/go.sum | 272 +++++++++++++++++++++------------------------------ 2 files changed, 157 insertions(+), 209 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index 7b69431b32..d3b342dc46 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -4,7 +4,7 @@ go 1.22.0 toolchain go1.22.1 -require github.com/golangci/golangci-lint v1.55.2 +require github.com/golangci/golangci-lint v1.56.2 require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect @@ -13,13 +13,13 @@ require ( github.com/Abirdcfly/dupword v0.0.13 // indirect github.com/Antonboom/errname v0.1.12 // indirect github.com/Antonboom/nilnil v0.1.7 // indirect - github.com/Antonboom/testifylint v0.2.3 // indirect + github.com/Antonboom/testifylint v1.1.2 // indirect github.com/BurntSushi/toml v1.3.2 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect - github.com/GaijinEntertainment/go-exhaustruct/v3 v3.1.0 // indirect + github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 // indirect github.com/Masterminds/semver v1.5.0 // indirect - github.com/OpenPeeDeeP/depguard/v2 v2.1.0 // indirect - github.com/alecthomas/go-check-sumtype v0.1.3 // indirect + github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect + github.com/alecthomas/go-check-sumtype v0.1.4 // indirect github.com/alexkohler/nakedret/v2 v2.0.2 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect @@ -28,40 +28,41 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bkielbasa/cyclop v1.2.1 // indirect github.com/blizzy78/varnamelen v0.8.0 // indirect - github.com/bombsimon/wsl/v3 v3.4.0 // indirect + github.com/bombsimon/wsl/v4 v4.2.1 // indirect github.com/breml/bidichk v0.2.7 // indirect github.com/breml/errchkjson v0.3.6 // indirect - github.com/butuzov/ireturn v0.2.2 // indirect + github.com/butuzov/ireturn v0.3.0 // indirect github.com/butuzov/mirror v1.1.0 // indirect - github.com/catenacyber/perfsprint v0.2.0 // indirect - github.com/ccojocar/zxcvbn-go v1.0.1 // indirect + github.com/catenacyber/perfsprint v0.6.0 // indirect + github.com/ccojocar/zxcvbn-go v1.0.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/charithe/durationcheck v0.0.10 // indirect github.com/chavacava/garif v0.1.0 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect - github.com/daixiang0/gci v0.11.2 // indirect + github.com/daixiang0/gci v0.12.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/denis-tingaikin/go-header v0.4.3 // indirect github.com/esimonov/ifshort v1.0.4 // indirect - github.com/ettle/strcase v0.1.1 // indirect - github.com/fatih/color v1.15.0 // indirect + github.com/ettle/strcase v0.2.0 // indirect + github.com/fatih/color v1.16.0 // indirect github.com/fatih/structtag v1.2.0 // indirect github.com/firefart/nonamedreturns v1.0.4 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/ghostiam/protogetter v0.2.3 // indirect - github.com/go-critic/go-critic v0.9.0 // indirect + github.com/ghostiam/protogetter v0.3.4 // indirect + github.com/go-critic/go-critic v0.11.1 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect - github.com/go-toolsmith/astequal v1.1.0 // indirect + github.com/go-toolsmith/astequal v1.2.0 // indirect github.com/go-toolsmith/astfmt v1.1.0 // indirect github.com/go-toolsmith/astp v1.1.0 // indirect github.com/go-toolsmith/strparse v1.1.0 // indirect github.com/go-toolsmith/typep v1.1.0 // indirect + github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gofrs/flock v0.8.1 // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 // indirect github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe // indirect @@ -72,54 +73,54 @@ require ( github.com/golangci/revgrep v0.5.2 // indirect github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601 // indirect + github.com/gordonklaus/ineffassign v0.1.0 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect github.com/gostaticanalysis/comment v1.4.2 // indirect github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect github.com/gostaticanalysis/nilerr v0.1.1 // indirect - github.com/hashicorp/errwrap v1.0.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-version v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jgautheron/goconst v1.6.0 // indirect + github.com/jgautheron/goconst v1.7.0 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect + github.com/jjti/go-spancheck v0.5.2 // indirect github.com/julz/importas v0.1.0 // indirect - github.com/kisielk/errcheck v1.6.3 // indirect + github.com/kisielk/errcheck v1.7.0 // indirect github.com/kisielk/gotool v1.0.0 // indirect github.com/kkHAIKE/contextcheck v1.1.4 // indirect github.com/kulti/thelper v0.6.3 // indirect - github.com/kunwardeep/paralleltest v1.0.8 // indirect + github.com/kunwardeep/paralleltest v1.0.9 // indirect github.com/kyoh86/exportloopref v0.1.11 // indirect github.com/ldez/gomoddirectives v0.2.3 // indirect github.com/ldez/tagliatelle v0.5.0 // indirect github.com/leonklingele/grouper v1.1.1 // indirect github.com/lufeee/execinquery v1.2.1 // indirect - github.com/macabu/inamedparam v0.1.2 // indirect + github.com/macabu/inamedparam v0.1.3 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/maratori/testableexamples v1.0.0 // indirect github.com/maratori/testpackage v1.1.1 // indirect github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mbilski/exhaustivestruct v1.2.0 // indirect - github.com/mgechev/revive v1.3.4 // indirect + github.com/mgechev/revive v1.3.7 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moricho/tparallel v0.3.1 // indirect github.com/nakabonne/nestif v0.3.1 // indirect - github.com/nishanths/exhaustive v0.11.0 // indirect + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect - github.com/nunnatsa/ginkgolinter v0.14.1 // indirect + github.com/nunnatsa/ginkgolinter v0.15.2 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/polyfloyd/go-errorlint v1.4.5 // indirect + github.com/polyfloyd/go-errorlint v1.4.8 // indirect github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect @@ -132,8 +133,8 @@ require ( github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect - github.com/sashamelentyev/usestdlibvars v1.24.0 // indirect - github.com/securego/gosec/v2 v2.18.2 // indirect + github.com/sashamelentyev/usestdlibvars v1.25.0 // indirect + github.com/securego/gosec/v2 v2.19.0 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect @@ -141,7 +142,7 @@ require ( github.com/sivchari/tenv v1.7.1 // indirect github.com/sonatard/noctx v0.0.2 // indirect github.com/sourcegraph/go-diff v0.7.0 // indirect - github.com/spf13/afero v1.8.2 // indirect + github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cobra v1.7.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect @@ -154,38 +155,39 @@ require ( github.com/subosito/gotenv v1.4.1 // indirect github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c // indirect github.com/tdakkota/asciicheck v0.2.0 // indirect - github.com/tetafro/godot v1.4.15 // indirect + github.com/tetafro/godot v1.4.16 // indirect github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect github.com/timonwong/loggercheck v0.9.4 // indirect github.com/tomarrell/wrapcheck/v2 v2.8.1 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect github.com/ultraware/funlen v0.1.0 // indirect - github.com/ultraware/whitespace v0.0.5 // indirect + github.com/ultraware/whitespace v0.1.0 // indirect github.com/uudashr/gocognit v1.1.2 // indirect github.com/xen0n/gosmopolitan v1.2.2 // indirect github.com/yagipy/maintidx v1.0.0 // indirect github.com/yeya24/promlinter v0.2.0 // indirect - github.com/ykadowak/zerologlint v0.1.3 // indirect + github.com/ykadowak/zerologlint v0.1.5 // indirect gitlab.com/bosi/decorder v0.4.1 // indirect - go-simpler.org/sloglint v0.1.2 // indirect - go.tmz.dev/musttag v0.7.2 // indirect + go-simpler.org/musttag v0.8.0 // indirect + go-simpler.org/sloglint v0.4.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect - golang.org/x/exp/typeparams v0.0.0-20230307190834-24139beb5833 // indirect - golang.org/x/mod v0.13.0 // indirect - golang.org/x/sync v0.4.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect - golang.org/x/tools v0.14.0 // indirect - google.golang.org/protobuf v1.28.0 // indirect + golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect + golang.org/x/exp/typeparams v0.0.0-20231219180239-dc181d75b848 // indirect + golang.org/x/mod v0.15.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.18.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect honnef.co/go/tools v0.4.6 // indirect - mvdan.cc/gofumpt v0.5.0 // indirect + mvdan.cc/gofumpt v0.6.0 // indirect mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect - mvdan.cc/unparam v0.0.0-20221223090309-7455f1af531d // indirect + mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14 // indirect ) diff --git a/tools/go.sum b/tools/go.sum index 8c0d284fc6..d73330ba3a 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -7,7 +7,6 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= @@ -18,9 +17,6 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -38,7 +34,6 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/4meepo/tagalign v1.3.3 h1:ZsOxcwGD/jP4U/aw7qeWu58i7dwYemfy5Y+IF1ACoNw= github.com/4meepo/tagalign v1.3.3/go.mod h1:Q9c1rYMZJc9dPRkbQPpcBNCLEmY2njbAsXhQOZFE2dE= @@ -48,24 +43,24 @@ github.com/Antonboom/errname v0.1.12 h1:oh9ak2zUtsLp5oaEd/erjB4GPu9w19NyoIskZClD github.com/Antonboom/errname v0.1.12/go.mod h1:bK7todrzvlaZoQagP1orKzWXv59X/x0W0Io2XT1Ssro= github.com/Antonboom/nilnil v0.1.7 h1:ofgL+BA7vlA1K2wNQOsHzLJ2Pw5B5DpWRLdDAVvvTow= github.com/Antonboom/nilnil v0.1.7/go.mod h1:TP+ScQWVEq0eSIxqU8CbdT5DFWoHp0MbP+KMUO1BKYQ= -github.com/Antonboom/testifylint v0.2.3 h1:MFq9zyL+rIVpsvLX4vDPLojgN7qODzWsrnftNX2Qh60= -github.com/Antonboom/testifylint v0.2.3/go.mod h1:IYaXaOX9NbfAyO+Y04nfjGI8wDemC1rUyM/cYolz018= +github.com/Antonboom/testifylint v1.1.2 h1:IdLRermiLRogxY5AumBL4sP0A+qKHQM/AP1Xd7XOTKc= +github.com/Antonboom/testifylint v1.1.2/go.mod h1:9PFi+vWa8zzl4/B/kqmFJcw85ZUv8ReyBzuQCd30+WI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.1.0 h1:3ZBs7LAezy8gh0uECsA6CGU43FF3zsx5f4eah5FxTMA= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.1.0/go.mod h1:rZLTje5A9kFBe0pzhpe2TdhRniBF++PRHQuRpR8esVc= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 h1:sATXp1x6/axKxz2Gjxv8MALP0bXaNRfQinEwyfMcx8c= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0/go.mod h1:Nl76DrGNJTA1KJ0LePKBw/vznBX1EHbAZX8mwjR82nI= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/OpenPeeDeeP/depguard/v2 v2.1.0 h1:aQl70G173h/GZYhWf36aE5H0KaujXfVMnn/f1kSDVYY= -github.com/OpenPeeDeeP/depguard/v2 v2.1.0/go.mod h1:PUBgk35fX4i7JDmwzlJwJ+GMe6NfO1723wmJMgPThNQ= +github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= +github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ= github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= -github.com/alecthomas/go-check-sumtype v0.1.3 h1:M+tqMxB68hcgccRXBMVCPI4UJ+QUfdSx0xdbypKCqA8= -github.com/alecthomas/go-check-sumtype v0.1.3/go.mod h1:WyYPfhfkdhyrdaligV6svFopZV8Lqdzn5pyVBaV6jhQ= +github.com/alecthomas/go-check-sumtype v0.1.4 h1:WCvlB3l5Vq5dZQTFmodqL2g68uHiSwwlWcT5a2FGK0c= +github.com/alecthomas/go-check-sumtype v0.1.4/go.mod h1:WyYPfhfkdhyrdaligV6svFopZV8Lqdzn5pyVBaV6jhQ= github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -93,20 +88,20 @@ github.com/bkielbasa/cyclop v1.2.1 h1:AeF71HZDob1P2/pRm1so9cd1alZnrpyc4q2uP2l0gJ github.com/bkielbasa/cyclop v1.2.1/go.mod h1:K/dT/M0FPAiYjBgQGau7tz+3TMh4FWAEqlMhzFWCrgM= github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= -github.com/bombsimon/wsl/v3 v3.4.0 h1:RkSxjT3tmlptwfgEgTgU+KYKLI35p/tviNXNXiL2aNU= -github.com/bombsimon/wsl/v3 v3.4.0/go.mod h1:KkIB+TXkqy6MvK9BDZVbZxKNYsE1/oLRJbIFtf14qqo= +github.com/bombsimon/wsl/v4 v4.2.1 h1:Cxg6u+XDWff75SIFFmNsqnIOgob+Q9hG6y/ioKbRFiM= +github.com/bombsimon/wsl/v4 v4.2.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo= github.com/breml/bidichk v0.2.7 h1:dAkKQPLl/Qrk7hnP6P+E0xOodrq8Us7+U0o4UBOAlQY= github.com/breml/bidichk v0.2.7/go.mod h1:YodjipAGI9fGcYM7II6wFvGhdMYsC5pHDlGzqvEW3tQ= github.com/breml/errchkjson v0.3.6 h1:VLhVkqSBH96AvXEyclMR37rZslRrY2kcyq+31HCsVrA= github.com/breml/errchkjson v0.3.6/go.mod h1:jhSDoFheAF2RSDOlCfhHO9KqhZgAYLyvHe7bRCX8f/U= -github.com/butuzov/ireturn v0.2.2 h1:jWI36dxXwVrI+RnXDwux2IZOewpmfv930OuIRfaBUJ0= -github.com/butuzov/ireturn v0.2.2/go.mod h1:RfGHUvvAuFFxoHKf4Z8Yxuh6OjlCw1KvR2zM1NFHeBk= +github.com/butuzov/ireturn v0.3.0 h1:hTjMqWw3y5JC3kpnC5vXmFJAWI/m31jaCYQqzkS6PL0= +github.com/butuzov/ireturn v0.3.0/go.mod h1:A09nIiwiqzN/IoVo9ogpa0Hzi9fex1kd9PSD6edP5ZA= github.com/butuzov/mirror v1.1.0 h1:ZqX54gBVMXu78QLoiqdwpl2mgmoOJTk7s4p4o+0avZI= github.com/butuzov/mirror v1.1.0/go.mod h1:8Q0BdQU6rC6WILDiBM60DBfvV78OLJmMmixe7GF45AE= -github.com/catenacyber/perfsprint v0.2.0 h1:azOocHLscPjqXVJ7Mf14Zjlkn4uNua0+Hcg1wTR6vUo= -github.com/catenacyber/perfsprint v0.2.0/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= -github.com/ccojocar/zxcvbn-go v1.0.1 h1:+sxrANSCj6CdadkcMnvde/GWU1vZiiXRbqYSCalV4/4= -github.com/ccojocar/zxcvbn-go v1.0.1/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= +github.com/catenacyber/perfsprint v0.6.0 h1:VSv95RRkk5+BxrU/YTPcnxuMEWar1iMK5Vyh3fWcBfs= +github.com/catenacyber/perfsprint v0.6.0/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= +github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= +github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= @@ -120,13 +115,11 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= -github.com/daixiang0/gci v0.11.2 h1:Oji+oPsp3bQ6bNNgX30NBAVT18P4uBH4sRZnlOlTj7Y= -github.com/daixiang0/gci v0.11.2/go.mod h1:xtHP9N7AHdNvtRNfcx9gwTDfw7FRJx4bZUsiEfiNNAI= +github.com/daixiang0/gci v0.12.1 h1:ugsG+KRYny1VK4oqrX4Vtj70bo4akYKa0tgT1DXMYiY= +github.com/daixiang0/gci v0.12.1/go.mod h1:xtHP9N7AHdNvtRNfcx9gwTDfw7FRJx4bZUsiEfiNNAI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -135,29 +128,27 @@ github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYB 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= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/esimonov/ifshort v1.0.4 h1:6SID4yGWfRae/M7hkVDVVyppy8q/v9OuxNdmjLQStBA= github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0= -github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= -github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= +github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y= github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI= -github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= -github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/ghostiam/protogetter v0.2.3 h1:qdv2pzo3BpLqezwqfGDLZ+nHEYmc5bUpIdsMbBVwMjw= -github.com/ghostiam/protogetter v0.2.3/go.mod h1:KmNLOsy1v04hKbvZs8EfGI1fk39AgTdRDxWNYPfXVc4= -github.com/go-critic/go-critic v0.9.0 h1:Pmys9qvU3pSML/3GEQ2Xd9RZ/ip+aXHKILuxczKGV/U= -github.com/go-critic/go-critic v0.9.0/go.mod h1:5P8tdXL7m/6qnyG6oRAlYLORvoXH0WDypYgAEmagT40= +github.com/ghostiam/protogetter v0.3.4 h1:5SZ+lZSNmNkSbGVSF9hUHhv/b7ELF9Rwchoq7btYo6c= +github.com/ghostiam/protogetter v0.3.4/go.mod h1:A0JgIhs0fgVnotGinjQiKaFVG3waItLJNwPmcMzDnvk= +github.com/go-critic/go-critic v0.11.1 h1:/zBseUSUMytnRqxjlsYNbDDxpu3R2yH8oLXo/FOE8b8= +github.com/go-critic/go-critic v0.11.1/go.mod h1:aZVQR7+gazH6aDEQx4356SD7d8ez8MipYjXbEl5JAKA= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -167,8 +158,8 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= @@ -177,8 +168,9 @@ github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4 github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw= github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= -github.com/go-toolsmith/astequal v1.1.0 h1:kHKm1AWqClYn15R0K1KKE4RG614D46n+nqUQ06E1dTw= github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ= +github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw= +github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY= github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco= github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4= github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= @@ -190,6 +182,8 @@ github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQi github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= +github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c= +github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U= github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= @@ -223,8 +217,9 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 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 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 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/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0= github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= @@ -233,8 +228,8 @@ github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe h1:6RGUuS7EGotKx6 github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe/go.mod h1:gjqyPShc/m8pEMpk0a3SeagVb0kaqvhscv+i9jI5ZhQ= github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e h1:ULcKCDV1LOZPFxGZaA6TlQbiM3J2GCPnkx/bGF6sX/g= github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e/go.mod h1:Pm5KhLPA8gSnQwrQ6ukebRcapGb/BG9iUkdaiCcGHJM= -github.com/golangci/golangci-lint v1.55.2 h1:yllEIsSJ7MtlDBwDJ9IMBkyEUz2fYE0b5B8IUgO1oP8= -github.com/golangci/golangci-lint v1.55.2/go.mod h1:H60CZ0fuqoTwlTvnbyjhpZPWp7KmsjwV2yupIMiMXbM= +github.com/golangci/golangci-lint v1.56.2 h1:dgQzlWHgNbCqJjuxRJhFEnHDVrrjuTGQHJ3RIZMpp/o= +github.com/golangci/golangci-lint v1.56.2/go.mod h1:7CfNO675+EY7j84jihO4iAqDQ80s3HCjcc5M6B7SlZQ= github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA= github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA= @@ -264,7 +259,6 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -272,18 +266,13 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601 h1:mrEEilTAUmaAORhssPPkxj84TsHrPMLBGW2Z4SoTxm8= -github.com/gordonklaus/ineffassign v0.0.0-20230610083614-0e73809eb601/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= +github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= +github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= @@ -296,10 +285,6 @@ github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= github.com/gostaticanalysis/testutil v0.4.0 h1:nhdCmubdmDF6VEatUNjgUZBJKWRqugoISdUv3PPQgHY= github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= @@ -310,15 +295,16 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jgautheron/goconst v1.6.0 h1:gbMLWKRMkzAc6kYsQL6/TxaoBUg3Jm9LSF/Ih1ADWGA= -github.com/jgautheron/goconst v1.6.0/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= +github.com/jgautheron/goconst v1.7.0 h1:cEqH+YBKLsECnRSd4F4TK5ri8t/aXtt/qoL0Ft252B0= +github.com/jgautheron/goconst v1.7.0/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af h1:KA9BjwUk7KlCh6S9EAGWBt1oExIUv9WyNCiRz5amv48= github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= +github.com/jjti/go-spancheck v0.5.2 h1:WXTZG3efY/ji1Vi8mkH+23O3bLeKR6hp3tI3YB7XwKk= +github.com/jjti/go-spancheck v0.5.2/go.mod h1:ARPNI1JRG1V2Rjnd6/2f2NEfghjSVDZGVmruNKlnXU0= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -330,15 +316,14 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= -github.com/kisielk/errcheck v1.6.3 h1:dEKh+GLHcWm2oN34nMvDzn1sqI0i0WxPvrgiJA5JuM8= -github.com/kisielk/errcheck v1.6.3/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw= +github.com/kisielk/errcheck v1.7.0 h1:+SbscKmWJ5mOK/bO1zS60F5I9WwZDWOfRsC4RwfwRV0= +github.com/kisielk/errcheck v1.7.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkHAIKE/contextcheck v1.1.4 h1:B6zAaLhOEEcjvUgIYEqystmnFk1Oemn8bvJhbt0GMb8= github.com/kkHAIKE/contextcheck v1.1.4/go.mod h1:1+i/gWqokIa+dm31mqGLZhZJ7Uh44DJGZVmr6QRBNJg= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -349,8 +334,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= -github.com/kunwardeep/paralleltest v1.0.8 h1:Ul2KsqtzFxTlSU7IP0JusWlLiNqQaloB9vguyjbE558= -github.com/kunwardeep/paralleltest v1.0.8/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= +github.com/kunwardeep/paralleltest v1.0.9 h1:3Sr2IfFNcsMmlqPk1cjTUbJ4zofKPGyHxenwPebgTug= +github.com/kunwardeep/paralleltest v1.0.9/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= github.com/kyoh86/exportloopref v0.1.11 h1:1Z0bcmTypkL3Q4k+IDHMWTcnCliEZcaPiIe0/ymEyhQ= github.com/kyoh86/exportloopref v0.1.11/go.mod h1:qkV4UF1zGl6EkF1ox8L5t9SwyeBAZ3qLMd6up458uqA= github.com/ldez/gomoddirectives v0.2.3 h1:y7MBaisZVDYmKvt9/l1mjNCiSA1BVn34U0ObUcJwlhA= @@ -361,8 +346,8 @@ github.com/leonklingele/grouper v1.1.1 h1:suWXRU57D4/Enn6pXR0QVqqWWrnJ9Osrz+5rjt github.com/leonklingele/grouper v1.1.1/go.mod h1:uk3I3uDfi9B6PeUjsCKi6ndcf63Uy7snXgR4yDYQVDY= github.com/lufeee/execinquery v1.2.1 h1:hf0Ems4SHcUGBxpGN7Jz78z1ppVkP/837ZlETPCEtOM= github.com/lufeee/execinquery v1.2.1/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM= -github.com/macabu/inamedparam v0.1.2 h1:RR5cnayM6Q7cDhQol32DE2BGAPGMnffJ31LFE+UklaU= -github.com/macabu/inamedparam v0.1.2/go.mod h1:Xg25QvY7IBRl1KLPV9Rbml8JOMZtF/iAkNkmV7eQgjw= +github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk= +github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= @@ -376,16 +361,16 @@ github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwM github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mbilski/exhaustivestruct v1.2.0 h1:wCBmUnSYufAHO6J4AVWY6ff+oxWxsVFrwgOdMUQePUo= github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc= -github.com/mgechev/revive v1.3.4 h1:k/tO3XTaWY4DEHal9tWBkkUMJYO/dLDVyMmAQxmIMDc= -github.com/mgechev/revive v1.3.4/go.mod h1:W+pZCMu9qj8Uhfs1iJMQsEFLRozUfvwFwqVvRbSNLVw= +github.com/mgechev/revive v1.3.7 h1:502QY0vQGe9KtYJ9FpxMz9rL+Fc/P13CI5POL4uHCcE= +github.com/mgechev/revive v1.3.7/go.mod h1:RJ16jUbF0OWC3co/+XTxmFNgEpUPwnnA0BRllX2aDNA= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -403,21 +388,21 @@ github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81 github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nishanths/exhaustive v0.11.0 h1:T3I8nUGhl/Cwu5Z2hfc92l0e04D2GEW6e0l8pzda2l0= -github.com/nishanths/exhaustive v0.11.0/go.mod h1:RqwDsZ1xY0dNdqHho2z6X+bgzizwbLYOWnZbbl2wLB4= +github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= +github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/nunnatsa/ginkgolinter v0.14.1 h1:khx0CqR5U4ghsscjJ+lZVthp3zjIFytRXPTaQ/TMiyA= -github.com/nunnatsa/ginkgolinter v0.14.1/go.mod h1:nY0pafUSst7v7F637e7fymaMlQqI9c0Wka2fGsDkzWg= +github.com/nunnatsa/ginkgolinter v0.15.2 h1:N2ORxUxPU56R9gsfLIlVVvCv/V/VVou5qVI1oBKBNHg= +github.com/nunnatsa/ginkgolinter v0.15.2/go.mod h1:oYxE7dt1vZI8cK2rZOs3RgTaBN2vggkqnENmoJ8kVvc= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= -github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= -github.com/onsi/gomega v1.28.1 h1:MijcGUbfYuznzK/5R4CPNoUP/9Xvuo20sXfEm6XxoTA= -github.com/onsi/gomega v1.28.1/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= +github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= +github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo= +github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= -github.com/otiai10/copy v1.11.0 h1:OKBD80J/mLBrwnzXqGtFCzprFSGioo30JcmR4APsNwc= -github.com/otiai10/copy v1.11.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww= +github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= +github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= @@ -430,11 +415,10 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polyfloyd/go-errorlint v1.4.5 h1:70YWmMy4FgRHehGNOUask3HtSFSOLKgmDn7ryNe7LqI= -github.com/polyfloyd/go-errorlint v1.4.5/go.mod h1:sIZEbFoDOCnTYYZoVkjc4hTnM459tuWA9H/EkdXwsKk= +github.com/polyfloyd/go-errorlint v1.4.8 h1:jiEjKDH33ouFktyez7sckv6pHWif9B7SuS8cutDXFHw= +github.com/polyfloyd/go-errorlint v1.4.8/go.mod h1:NNCxFcFjZcw3xNjVdCchERkEM6Oz7wta2XJVxRftwO4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= @@ -466,8 +450,8 @@ github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:r github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.3.0 h1:q15RT/pd6UggBXVBuLps8BXRvl5GPBcwVA7BJHMLuTw= github.com/ryancurrah/gomodguard v1.3.0/go.mod h1:ggBxb3luypPEzqVtq33ee7YSN35V28XeGnid8dnni50= @@ -477,10 +461,10 @@ github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/ github.com/sanposhiho/wastedassign/v2 v2.0.7/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= -github.com/sashamelentyev/usestdlibvars v1.24.0 h1:MKNzmXtGh5N0y74Z/CIaJh4GlB364l0K1RUT08WSWAc= -github.com/sashamelentyev/usestdlibvars v1.24.0/go.mod h1:9cYkq+gYJ+a5W2RPdhfaSCnTVUC1OQP/bSiiBhq3OZE= -github.com/securego/gosec/v2 v2.18.2 h1:DkDt3wCiOtAHf1XkiXZBhQ6m6mK/b9T/wD257R3/c+I= -github.com/securego/gosec/v2 v2.18.2/go.mod h1:xUuqSF6i0So56Y2wwohWAmB07EdBkUN6crbLlHwbyJs= +github.com/sashamelentyev/usestdlibvars v1.25.0 h1:IK8SI2QyFzy/2OD2PYnhy84dpfNo9qADrRt6LH8vSzU= +github.com/sashamelentyev/usestdlibvars v1.25.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= +github.com/securego/gosec/v2 v2.19.0 h1:gl5xMkOI0/E6Hxx0XCY2XujA3V7SNSefA8sC+3f1gnk= +github.com/securego/gosec/v2 v2.19.0/go.mod h1:hOkDcHz9J/XIgIlPDXalxjeVYsHxoWUc5zJSHxcB8YM= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= @@ -500,8 +484,8 @@ github.com/sonatard/noctx v0.0.2 h1:L7Dz4De2zDQhW8S0t+KUjY0MAQJd6SgVwhzNIc4ok00= github.com/sonatard/noctx v0.0.2/go.mod h1:kzFz+CzWSjQ2OzIm46uJZoXuBpa2+0y3T36U18dWqIo= github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= -github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= -github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= @@ -540,8 +524,8 @@ github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= -github.com/tetafro/godot v1.4.15 h1:QzdIs+XB8q+U1WmQEWKHQbKmCw06QuQM7gLx/dky2RM= -github.com/tetafro/godot v1.4.15/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= +github.com/tetafro/godot v1.4.16 h1:4ChfhveiNLk4NveAZ9Pu2AN8QZ2nkUGFuadM9lrr5D0= +github.com/tetafro/godot v1.4.16/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 h1:quvGphlmUVU+nhpFa4gg4yJyTRJ13reZMDHrKwYw53M= github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966/go.mod h1:27bSVNWSBOHm+qRp1T9qzaIpsWEP6TbUnei/43HK+PQ= github.com/timonwong/loggercheck v0.9.4 h1:HKKhqrjcVj8sxL7K77beXh0adEm6DLjV/QOGeMXEVi4= @@ -552,8 +536,8 @@ github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+ github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= github.com/ultraware/funlen v0.1.0 h1:BuqclbkY6pO+cvxoq7OsktIXZpgBSkYTQtmwhAK81vI= github.com/ultraware/funlen v0.1.0/go.mod h1:XJqmOQja6DpxarLj6Jj1U7JuoS8PvL4nEqDaQhy22p4= -github.com/ultraware/whitespace v0.0.5 h1:hh+/cpIcopyMYbZNVov9iSxvJU3OYQg78Sfaqzi/CzI= -github.com/ultraware/whitespace v0.0.5/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= +github.com/ultraware/whitespace v0.1.0 h1:O1HKYoh0kIeqE8sFqZf1o0qbORXUCOQFrlaQyZsczZw= +github.com/ultraware/whitespace v0.1.0/go.mod h1:/se4r3beMFNmewJ4Xmz0nMQ941GJt+qmSHGP9emHYe0= github.com/uudashr/gocognit v1.1.2 h1:l6BAEKJqQH2UpKAPKdMfZf5kE4W/2xk8pfU1OVLvniI= github.com/uudashr/gocognit v1.1.2/go.mod h1:aAVdLURqcanke8h3vg35BC++eseDm66Z7KmchI5et4k= github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= @@ -562,8 +546,8 @@ github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= github.com/yeya24/promlinter v0.2.0 h1:xFKDQ82orCU5jQujdaD8stOHiv8UN68BSdn2a8u8Y3o= github.com/yeya24/promlinter v0.2.0/go.mod h1:u54lkmBOZrpEbQQ6gox2zWKKLKu2SGe+2KOiextY+IA= -github.com/ykadowak/zerologlint v0.1.3 h1:TLy1dTW3Nuc+YE3bYRPToG1Q9Ej78b5UUN6bjbGdxPE= -github.com/ykadowak/zerologlint v0.1.3/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= +github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= +github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -573,18 +557,17 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= gitlab.com/bosi/decorder v0.4.1 h1:VdsdfxhstabyhZovHafFw+9eJ6eU0d2CkFNJcZz/NU4= gitlab.com/bosi/decorder v0.4.1/go.mod h1:jecSqWUew6Yle1pCr2eLWTensJMmsxHsBwt+PVbkAqA= -go-simpler.org/assert v0.6.0 h1:QxSrXa4oRuo/1eHMXSBFHKvJIpWABayzKldqZyugG7E= -go-simpler.org/assert v0.6.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= -go-simpler.org/sloglint v0.1.2 h1:IjdhF8NPxyn0Ckn2+fuIof7ntSnVUAqBFcQRrnG9AiM= -go-simpler.org/sloglint v0.1.2/go.mod h1:2LL+QImPfTslD5muNPydAEYmpXIj6o/WYcqnJjLi4o4= +go-simpler.org/assert v0.7.0 h1:OzWWZqfNxt8cLS+MlUp6Tgk1HjPkmgdKBq9qvy8lZsA= +go-simpler.org/assert v0.7.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= +go-simpler.org/musttag v0.8.0 h1:DR4UTgetNNhPRNo02rkK1hwDTRzAPotN+ZqYpdtEwWc= +go-simpler.org/musttag v0.8.0/go.mod h1:fiNdCkXt2S6je9Eblma3okjnlva9NT1Eg/WUt19rWu8= +go-simpler.org/sloglint v0.4.0 h1:UVJuUJo63iNQNFEOtZ6o1xAgagVg/giVLLvG9nNLobI= +go-simpler.org/sloglint v0.4.0/go.mod h1:v6zJ++j/thFPhefs2wEXoCKwT10yo5nkBDYRCXyqgNQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.tmz.dev/musttag v0.7.2 h1:1J6S9ipDbalBSODNT5jCep8dhZyMr4ttnjQagmGYR5s= -go.tmz.dev/musttag v0.7.2/go.mod h1:m6q5NiiSKMnQYokefa2xGoyoXnrswCbJ0AWYzf4Zs28= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= @@ -599,9 +582,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -613,12 +594,12 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4= -golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= +golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20230307190834-24139beb5833 h1:jWGQJV4niP+CCmFW9ekjA9Zx8vYORzOUH2/Nl5WPuLQ= -golang.org/x/exp/typeparams v0.0.0-20230307190834-24139beb5833/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20231219180239-dc181d75b848 h1:UhRVJ0i7bF9n/Hd8YjW3eKjlPVBHzbQdxrBgjbSKl64= +golang.org/x/exp/typeparams v0.0.0-20231219180239-dc181d75b848/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -631,7 +612,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -640,7 +620,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= @@ -649,8 +628,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -680,9 +659,6 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -692,17 +668,13 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 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= @@ -717,8 +689,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -750,17 +722,12 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -777,8 +744,9 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -790,14 +758,13 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.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.6.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.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -848,14 +815,8 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201001104356-43ebab892c4c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= @@ -869,8 +830,8 @@ golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -891,16 +852,12 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= 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.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -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-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -930,13 +887,6 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -949,10 +899,6 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -965,8 +911,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj 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.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +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/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -995,14 +941,14 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.4.6 h1:oFEHCKeID7to/3autwsWfnuv69j3NsfcXbvJKuIcep8= honnef.co/go/tools v0.4.6/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= -mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E= -mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js= +mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo= +mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= -mvdan.cc/unparam v0.0.0-20221223090309-7455f1af531d h1:3rvTIIM22r9pvXk+q3swxUQAQOxksVMGK7sml4nG57w= -mvdan.cc/unparam v0.0.0-20221223090309-7455f1af531d/go.mod h1:IeHQjmn6TOD+e4Z3RFiZMMsLVL+A96Nvptar8Fj71is= +mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14 h1:zCr3iRRgdk5eIikZNDphGcM6KGVTx3Yu+/Uu9Es254w= +mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14/go.mod h1:ZzZjEpJDOmx8TdVU6umamY3Xy0UAQUI2DHbf05USVbI= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From d82823d404bce72cbc292df81d3afcec182f3691 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Mar 2024 11:04:49 +0000 Subject: [PATCH 0018/1670] deps(ci): bump codecov/codecov-action from 3 to 4 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v3...v4) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5385268b46..aab5fb236d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,6 +53,6 @@ jobs: go test -race ./... - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: file: /tmp/coverage.out.filtered From 28ae0f88537bcf139786490d570aba62c5abc3a1 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 12 Mar 2024 07:15:58 -0400 Subject: [PATCH 0019/1670] Remove uneeded nolint --- cmd/oidc-broker/daemon/daemon_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/oidc-broker/daemon/daemon_test.go b/cmd/oidc-broker/daemon/daemon_test.go index 28fe128829..86a0628d39 100644 --- a/cmd/oidc-broker/daemon/daemon_test.go +++ b/cmd/oidc-broker/daemon/daemon_test.go @@ -132,7 +132,6 @@ func TestAppRunFailsOnComponentsCreationAndQuit(t *testing.T) { tmpDir := t.TempDir() cachePath := filepath.Join(tmpDir, "cache") - //nolint:govet // This is fixed with Go 1.22.0 and is a false positive (https://github.com/securego/gosec/pull/1108) switch tc.cachePathBehavior { case dirIsFile: err := os.WriteFile(cachePath, []byte("file"), 0600) From c1132a03c622714d8ca4ce9988959dc2bc57197f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 08:41:53 +0000 Subject: [PATCH 0020/1670] deps(go-tools): bump github.com/golangci/golangci-lint in /tools Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.56.2 to 1.57.2. - [Release notes](https://github.com/golangci/golangci-lint/releases) - [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md) - [Commits](https://github.com/golangci/golangci-lint/compare/v1.56.2...v1.57.2) --- updated-dependencies: - dependency-name: github.com/golangci/golangci-lint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- tools/go.mod | 75 ++++++++++++------------- tools/go.sum | 154 +++++++++++++++++++++++++-------------------------- 2 files changed, 109 insertions(+), 120 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index d3b342dc46..9dd27714b5 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -4,23 +4,23 @@ go 1.22.0 toolchain go1.22.1 -require github.com/golangci/golangci-lint v1.56.2 +require github.com/golangci/golangci-lint v1.57.2 require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 4d63.com/gochecknoglobals v0.2.1 // indirect github.com/4meepo/tagalign v1.3.3 // indirect - github.com/Abirdcfly/dupword v0.0.13 // indirect + github.com/Abirdcfly/dupword v0.0.14 // indirect github.com/Antonboom/errname v0.1.12 // indirect github.com/Antonboom/nilnil v0.1.7 // indirect - github.com/Antonboom/testifylint v1.1.2 // indirect + github.com/Antonboom/testifylint v1.2.0 // indirect github.com/BurntSushi/toml v1.3.2 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect github.com/alecthomas/go-check-sumtype v0.1.4 // indirect - github.com/alexkohler/nakedret/v2 v2.0.2 // indirect + github.com/alexkohler/nakedret/v2 v2.0.4 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect github.com/ashanbrown/forbidigo v1.6.0 // indirect @@ -33,24 +33,24 @@ require ( github.com/breml/errchkjson v0.3.6 // indirect github.com/butuzov/ireturn v0.3.0 // indirect github.com/butuzov/mirror v1.1.0 // indirect - github.com/catenacyber/perfsprint v0.6.0 // indirect + github.com/catenacyber/perfsprint v0.7.1 // indirect github.com/ccojocar/zxcvbn-go v1.0.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/charithe/durationcheck v0.0.10 // indirect github.com/chavacava/garif v0.1.0 // indirect + github.com/ckaznocha/intrange v0.1.1 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect - github.com/daixiang0/gci v0.12.1 // indirect + github.com/daixiang0/gci v0.12.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/denis-tingaikin/go-header v0.4.3 // indirect - github.com/esimonov/ifshort v1.0.4 // indirect + github.com/denis-tingaikin/go-header v0.5.0 // indirect github.com/ettle/strcase v0.2.0 // indirect github.com/fatih/color v1.16.0 // indirect github.com/fatih/structtag v1.2.0 // indirect github.com/firefart/nonamedreturns v1.0.4 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/ghostiam/protogetter v0.3.4 // indirect - github.com/go-critic/go-critic v0.11.1 // indirect + github.com/ghostiam/protogetter v0.3.5 // indirect + github.com/go-critic/go-critic v0.11.2 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect github.com/go-toolsmith/astequal v1.2.0 // indirect @@ -63,15 +63,12 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 // indirect github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect - github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe // indirect github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e // indirect - github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 // indirect - github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca // indirect github.com/golangci/misspell v0.4.1 // indirect + github.com/golangci/plugin-module-register v0.1.1 // indirect github.com/golangci/revgrep v0.5.2 // indirect - github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect + github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/gordonklaus/ineffassign v0.1.0 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect @@ -82,18 +79,18 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jgautheron/goconst v1.7.0 // indirect + github.com/jgautheron/goconst v1.7.1 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect - github.com/jjti/go-spancheck v0.5.2 // indirect + github.com/jjti/go-spancheck v0.5.3 // indirect github.com/julz/importas v0.1.0 // indirect + github.com/karamaru-alpha/copyloopvar v1.0.10 // indirect github.com/kisielk/errcheck v1.7.0 // indirect - github.com/kisielk/gotool v1.0.0 // indirect - github.com/kkHAIKE/contextcheck v1.1.4 // indirect + github.com/kkHAIKE/contextcheck v1.1.5 // indirect github.com/kulti/thelper v0.6.3 // indirect - github.com/kunwardeep/paralleltest v1.0.9 // indirect + github.com/kunwardeep/paralleltest v1.0.10 // indirect github.com/kyoh86/exportloopref v0.1.11 // indirect - github.com/ldez/gomoddirectives v0.2.3 // indirect + github.com/ldez/gomoddirectives v0.2.4 // indirect github.com/ldez/tagliatelle v0.5.0 // indirect github.com/leonklingele/grouper v1.1.1 // indirect github.com/lufeee/execinquery v1.2.1 // indirect @@ -106,7 +103,6 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mbilski/exhaustivestruct v1.2.0 // indirect github.com/mgechev/revive v1.3.7 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect @@ -115,30 +111,30 @@ require ( github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect - github.com/nunnatsa/ginkgolinter v0.15.2 // indirect + github.com/nunnatsa/ginkgolinter v0.16.2 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/pelletier/go-toml/v2 v2.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polyfloyd/go-errorlint v1.4.8 // indirect github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect - github.com/quasilyte/go-ruleguard v0.4.0 // indirect + github.com/quasilyte/go-ruleguard v0.4.2 // indirect github.com/quasilyte/gogrep v0.5.0 // indirect github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect - github.com/ryancurrah/gomodguard v1.3.0 // indirect + github.com/ryancurrah/gomodguard v1.3.1 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect + github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.25.0 // indirect github.com/securego/gosec/v2 v2.19.0 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect - github.com/sivchari/nosnakecase v1.7.0 // indirect github.com/sivchari/tenv v1.7.1 // indirect github.com/sonatard/noctx v0.0.2 // indirect github.com/sourcegraph/go-diff v0.7.0 // indirect @@ -150,15 +146,15 @@ require ( github.com/spf13/viper v1.12.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect - github.com/stretchr/objx v0.5.0 // indirect - github.com/stretchr/testify v1.8.4 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/stretchr/testify v1.9.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c // indirect github.com/tdakkota/asciicheck v0.2.0 // indirect github.com/tetafro/godot v1.4.16 // indirect github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect github.com/timonwong/loggercheck v0.9.4 // indirect - github.com/tomarrell/wrapcheck/v2 v2.8.1 // indirect + github.com/tomarrell/wrapcheck/v2 v2.8.3 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect github.com/ultraware/funlen v0.1.0 // indirect github.com/ultraware/whitespace v0.1.0 // indirect @@ -168,26 +164,25 @@ require ( github.com/yeya24/promlinter v0.2.0 // indirect github.com/ykadowak/zerologlint v0.1.5 // indirect gitlab.com/bosi/decorder v0.4.1 // indirect - go-simpler.org/musttag v0.8.0 // indirect - go-simpler.org/sloglint v0.4.0 // indirect + go-simpler.org/musttag v0.9.0 // indirect + go-simpler.org/sloglint v0.5.0 // indirect go.uber.org/atomic v1.7.0 // indirect + go.uber.org/automaxprocs v1.5.3 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect - golang.org/x/exp/typeparams v0.0.0-20231219180239-dc181d75b848 // indirect - golang.org/x/mod v0.15.0 // indirect + golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect + golang.org/x/mod v0.16.0 // indirect golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.18.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect + golang.org/x/tools v0.19.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - honnef.co/go/tools v0.4.6 // indirect + honnef.co/go/tools v0.4.7 // indirect mvdan.cc/gofumpt v0.6.0 // indirect - mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect - mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14 // indirect ) diff --git a/tools/go.sum b/tools/go.sum index d73330ba3a..74ce9606aa 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -37,14 +37,14 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/4meepo/tagalign v1.3.3 h1:ZsOxcwGD/jP4U/aw7qeWu58i7dwYemfy5Y+IF1ACoNw= github.com/4meepo/tagalign v1.3.3/go.mod h1:Q9c1rYMZJc9dPRkbQPpcBNCLEmY2njbAsXhQOZFE2dE= -github.com/Abirdcfly/dupword v0.0.13 h1:SMS17YXypwP000fA7Lr+kfyBQyW14tTT+nRv9ASwUUo= -github.com/Abirdcfly/dupword v0.0.13/go.mod h1:Ut6Ue2KgF/kCOawpW4LnExT+xZLQviJPE4klBPMK/5Y= +github.com/Abirdcfly/dupword v0.0.14 h1:3U4ulkc8EUo+CaT105/GJ1BQwtgyj6+VaBVbAX11Ba8= +github.com/Abirdcfly/dupword v0.0.14/go.mod h1:VKDAbxdY8YbKUByLGg8EETzYSuC4crm9WwI6Y3S0cLI= github.com/Antonboom/errname v0.1.12 h1:oh9ak2zUtsLp5oaEd/erjB4GPu9w19NyoIskZClDcQY= github.com/Antonboom/errname v0.1.12/go.mod h1:bK7todrzvlaZoQagP1orKzWXv59X/x0W0Io2XT1Ssro= github.com/Antonboom/nilnil v0.1.7 h1:ofgL+BA7vlA1K2wNQOsHzLJ2Pw5B5DpWRLdDAVvvTow= github.com/Antonboom/nilnil v0.1.7/go.mod h1:TP+ScQWVEq0eSIxqU8CbdT5DFWoHp0MbP+KMUO1BKYQ= -github.com/Antonboom/testifylint v1.1.2 h1:IdLRermiLRogxY5AumBL4sP0A+qKHQM/AP1Xd7XOTKc= -github.com/Antonboom/testifylint v1.1.2/go.mod h1:9PFi+vWa8zzl4/B/kqmFJcw85ZUv8ReyBzuQCd30+WI= +github.com/Antonboom/testifylint v1.2.0 h1:015bxD8zc5iY8QwTp4+RG9I4kIbqwvGX9TrBbb7jGdM= +github.com/Antonboom/testifylint v1.2.0/go.mod h1:rkmEqjqVnHDRNsinyN6fPSLnoajzFwsCcguJgwADBkw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= @@ -68,8 +68,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alexkohler/nakedret/v2 v2.0.2 h1:qnXuZNvv3/AxkAb22q/sEsEpcA99YxLFACDtEw9TPxE= -github.com/alexkohler/nakedret/v2 v2.0.2/go.mod h1:2b8Gkk0GsOrqQv/gPWjNLDSKwG8I5moSXG1K4VIBcTQ= +github.com/alexkohler/nakedret/v2 v2.0.4 h1:yZuKmjqGi0pSmjGpOC016LtPJysIL0WEUiaXW5SUnNg= +github.com/alexkohler/nakedret/v2 v2.0.4/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU= github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= @@ -98,8 +98,8 @@ github.com/butuzov/ireturn v0.3.0 h1:hTjMqWw3y5JC3kpnC5vXmFJAWI/m31jaCYQqzkS6PL0 github.com/butuzov/ireturn v0.3.0/go.mod h1:A09nIiwiqzN/IoVo9ogpa0Hzi9fex1kd9PSD6edP5ZA= github.com/butuzov/mirror v1.1.0 h1:ZqX54gBVMXu78QLoiqdwpl2mgmoOJTk7s4p4o+0avZI= github.com/butuzov/mirror v1.1.0/go.mod h1:8Q0BdQU6rC6WILDiBM60DBfvV78OLJmMmixe7GF45AE= -github.com/catenacyber/perfsprint v0.6.0 h1:VSv95RRkk5+BxrU/YTPcnxuMEWar1iMK5Vyh3fWcBfs= -github.com/catenacyber/perfsprint v0.6.0/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= +github.com/catenacyber/perfsprint v0.7.1 h1:PGW5G/Kxn+YrN04cRAZKC+ZuvlVwolYMrIyyTJ/rMmc= +github.com/catenacyber/perfsprint v0.7.1/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -113,24 +113,24 @@ github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+U github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/ckaznocha/intrange v0.1.1 h1:gHe4LfqCspWkh8KpJFs20fJz3XRHFBFUV9yI7Itu83Q= +github.com/ckaznocha/intrange v0.1.1/go.mod h1:RWffCw/vKBwHeOEwWdCikAtY0q4gGt8VhJZEEA5n+RE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= -github.com/daixiang0/gci v0.12.1 h1:ugsG+KRYny1VK4oqrX4Vtj70bo4akYKa0tgT1DXMYiY= -github.com/daixiang0/gci v0.12.1/go.mod h1:xtHP9N7AHdNvtRNfcx9gwTDfw7FRJx4bZUsiEfiNNAI= +github.com/daixiang0/gci v0.12.3 h1:yOZI7VAxAGPQmkb1eqt5g/11SUlwoat1fSblGLmdiQc= +github.com/daixiang0/gci v0.12.3/go.mod h1:xtHP9N7AHdNvtRNfcx9gwTDfw7FRJx4bZUsiEfiNNAI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denis-tingaikin/go-header v0.4.3 h1:tEaZKAlqql6SKCY++utLmkPLd6K8IBM20Ha7UVm+mtU= -github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYBAC2Mra5RassOIQ2/c= +github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= +github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= 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= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/esimonov/ifshort v1.0.4 h1:6SID4yGWfRae/M7hkVDVVyppy8q/v9OuxNdmjLQStBA= -github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0= github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= @@ -145,10 +145,10 @@ github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwV github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/ghostiam/protogetter v0.3.4 h1:5SZ+lZSNmNkSbGVSF9hUHhv/b7ELF9Rwchoq7btYo6c= -github.com/ghostiam/protogetter v0.3.4/go.mod h1:A0JgIhs0fgVnotGinjQiKaFVG3waItLJNwPmcMzDnvk= -github.com/go-critic/go-critic v0.11.1 h1:/zBseUSUMytnRqxjlsYNbDDxpu3R2yH8oLXo/FOE8b8= -github.com/go-critic/go-critic v0.11.1/go.mod h1:aZVQR7+gazH6aDEQx4356SD7d8ez8MipYjXbEl5JAKA= +github.com/ghostiam/protogetter v0.3.5 h1:+f7UiF8XNd4w3a//4DnusQ2SZjPkUjxkMEfjbxOK4Ug= +github.com/ghostiam/protogetter v0.3.5/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw= +github.com/go-critic/go-critic v0.11.2 h1:81xH/2muBphEgPtcwH1p6QD+KzXl2tMSi3hXjBSxDnM= +github.com/go-critic/go-critic v0.11.2/go.mod h1:OePaicfjsf+KPy33yq4gzv6CO7TEQ9Rom6ns1KsJnl8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -220,26 +220,20 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS 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/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0= -github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= -github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe h1:6RGUuS7EGotKx6J5HIP8ZtyMdiDscjMLfRBSPuzVVeo= -github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe/go.mod h1:gjqyPShc/m8pEMpk0a3SeagVb0kaqvhscv+i9jI5ZhQ= github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e h1:ULcKCDV1LOZPFxGZaA6TlQbiM3J2GCPnkx/bGF6sX/g= github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e/go.mod h1:Pm5KhLPA8gSnQwrQ6ukebRcapGb/BG9iUkdaiCcGHJM= -github.com/golangci/golangci-lint v1.56.2 h1:dgQzlWHgNbCqJjuxRJhFEnHDVrrjuTGQHJ3RIZMpp/o= -github.com/golangci/golangci-lint v1.56.2/go.mod h1:7CfNO675+EY7j84jihO4iAqDQ80s3HCjcc5M6B7SlZQ= -github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA= -github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= -github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA= -github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= +github.com/golangci/golangci-lint v1.57.2 h1:NNhxfZyL5He1WWDrIvl1a4n5bvWZBcgAqBwlJAAgLTw= +github.com/golangci/golangci-lint v1.57.2/go.mod h1:ApiG3S3Ca23QyfGp5BmsorTiVxJpr5jGiNS0BkdSidg= github.com/golangci/misspell v0.4.1 h1:+y73iSicVy2PqyX7kmUefHusENlrP9YwuHZHPLGQj/g= github.com/golangci/misspell v0.4.1/go.mod h1:9mAN1quEo3DlpbaIKKyEvRxK1pwqR9s/Sea1bJCtlNI= +github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= +github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= github.com/golangci/revgrep v0.5.2 h1:EndcWoRhcnfj2NHQ+28hyuXpLMF+dQmCN+YaeeIl4FU= github.com/golangci/revgrep v0.5.2/go.mod h1:bjAMA+Sh/QUfTDcHzxfyHxr4xKvllVr/0sCv2e7jJHA= -github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys= -github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= +github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed h1:IURFTjxeTfNFP0hTEi1YKjB/ub8zkpaOqFFMApi2EAs= +github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed/go.mod h1:XLXN8bNw4CGRPaqgl3bv/lhz7bsGPh4/xSaMTbo2vkQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -297,14 +291,14 @@ github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSo github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jgautheron/goconst v1.7.0 h1:cEqH+YBKLsECnRSd4F4TK5ri8t/aXtt/qoL0Ft252B0= -github.com/jgautheron/goconst v1.7.0/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= +github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk= +github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af h1:KA9BjwUk7KlCh6S9EAGWBt1oExIUv9WyNCiRz5amv48= github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= -github.com/jjti/go-spancheck v0.5.2 h1:WXTZG3efY/ji1Vi8mkH+23O3bLeKR6hp3tI3YB7XwKk= -github.com/jjti/go-spancheck v0.5.2/go.mod h1:ARPNI1JRG1V2Rjnd6/2f2NEfghjSVDZGVmruNKlnXU0= +github.com/jjti/go-spancheck v0.5.3 h1:vfq4s2IB8T3HvbpiwDTYgVPj1Ze/ZSXrTtaZRTc7CuM= +github.com/jjti/go-spancheck v0.5.3/go.mod h1:eQdOX1k3T+nAKvZDyLC3Eby0La4dZ+I19iOl5NzSPFE= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -316,12 +310,13 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= +github.com/karamaru-alpha/copyloopvar v1.0.10 h1:8HYDy6KQYqTmD7JuhZMWS1nwPru9889XI24ROd/+WXI= +github.com/karamaru-alpha/copyloopvar v1.0.10/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= github.com/kisielk/errcheck v1.7.0 h1:+SbscKmWJ5mOK/bO1zS60F5I9WwZDWOfRsC4RwfwRV0= github.com/kisielk/errcheck v1.7.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= -github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kkHAIKE/contextcheck v1.1.4 h1:B6zAaLhOEEcjvUgIYEqystmnFk1Oemn8bvJhbt0GMb8= -github.com/kkHAIKE/contextcheck v1.1.4/go.mod h1:1+i/gWqokIa+dm31mqGLZhZJ7Uh44DJGZVmr6QRBNJg= +github.com/kkHAIKE/contextcheck v1.1.5 h1:CdnJh63tcDe53vG+RebdpdXJTc9atMgGqdx8LXxiilg= +github.com/kkHAIKE/contextcheck v1.1.5/go.mod h1:O930cpht4xb1YQpK+1+AgoM3mFsvxr7uyFptcnWTYUA= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -334,12 +329,12 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= -github.com/kunwardeep/paralleltest v1.0.9 h1:3Sr2IfFNcsMmlqPk1cjTUbJ4zofKPGyHxenwPebgTug= -github.com/kunwardeep/paralleltest v1.0.9/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= +github.com/kunwardeep/paralleltest v1.0.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCTdvWJ/lDDs= +github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= github.com/kyoh86/exportloopref v0.1.11 h1:1Z0bcmTypkL3Q4k+IDHMWTcnCliEZcaPiIe0/ymEyhQ= github.com/kyoh86/exportloopref v0.1.11/go.mod h1:qkV4UF1zGl6EkF1ox8L5t9SwyeBAZ3qLMd6up458uqA= -github.com/ldez/gomoddirectives v0.2.3 h1:y7MBaisZVDYmKvt9/l1mjNCiSA1BVn34U0ObUcJwlhA= -github.com/ldez/gomoddirectives v0.2.3/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0= +github.com/ldez/gomoddirectives v0.2.4 h1:j3YjBIjEBbqZ0NKtBNzr8rtMHTOrLPeiwTkfUJZ3alg= +github.com/ldez/gomoddirectives v0.2.4/go.mod h1:oWu9i62VcQDYp9EQ0ONTfqLNh+mDLWWDO+SO0qSQw5g= github.com/ldez/tagliatelle v0.5.0 h1:epgfuYt9v0CG3fms0pEgIMNPuFf/LpPIfjk4kyqSioo= github.com/ldez/tagliatelle v0.5.0/go.mod h1:rj1HmWiL1MiKQuOONhd09iySTEkUuE/8+5jtPYz9xa4= github.com/leonklingele/grouper v1.1.1 h1:suWXRU57D4/Enn6pXR0QVqqWWrnJ9Osrz+5rjt8ivzU= @@ -367,8 +362,6 @@ github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/Qd github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mbilski/exhaustivestruct v1.2.0 h1:wCBmUnSYufAHO6J4AVWY6ff+oxWxsVFrwgOdMUQePUo= -github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc= github.com/mgechev/revive v1.3.7 h1:502QY0vQGe9KtYJ9FpxMz9rL+Fc/P13CI5POL4uHCcE= github.com/mgechev/revive v1.3.7/go.mod h1:RJ16jUbF0OWC3co/+XTxmFNgEpUPwnnA0BRllX2aDNA= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -392,8 +385,8 @@ github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhK github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/nunnatsa/ginkgolinter v0.15.2 h1:N2ORxUxPU56R9gsfLIlVVvCv/V/VVou5qVI1oBKBNHg= -github.com/nunnatsa/ginkgolinter v0.15.2/go.mod h1:oYxE7dt1vZI8cK2rZOs3RgTaBN2vggkqnENmoJ8kVvc= +github.com/nunnatsa/ginkgolinter v0.16.2 h1:8iLqHIZvN4fTLDC0Ke9tbSZVcyVHoBs0HIbnVSxfHJk= +github.com/nunnatsa/ginkgolinter v0.16.2/go.mod h1:4tWRinDN1FeJgU+iJANW/kz7xKN5nYRAOfJDQUS9dOQ= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= @@ -409,8 +402,8 @@ github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT9 github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= -github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= +github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -419,6 +412,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polyfloyd/go-errorlint v1.4.8 h1:jiEjKDH33ouFktyez7sckv6pHWif9B7SuS8cutDXFHw= github.com/polyfloyd/go-errorlint v1.4.8/go.mod h1:NNCxFcFjZcw3xNjVdCchERkEM6Oz7wta2XJVxRftwO4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= @@ -441,8 +436,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/quasilyte/go-ruleguard v0.4.0 h1:DyM6r+TKL+xbKB4Nm7Afd1IQh9kEUKQs2pboWGKtvQo= -github.com/quasilyte/go-ruleguard v0.4.0/go.mod h1:Eu76Z/R8IXtViWUIHkE3p8gdH3/PKk1eh3YGfaEof10= +github.com/quasilyte/go-ruleguard v0.4.2 h1:htXcXDK6/rO12kiTHKfHuqR4kr3Y4M0J0rOL6CH/BYs= +github.com/quasilyte/go-ruleguard v0.4.2/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= @@ -453,12 +448,14 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryancurrah/gomodguard v1.3.0 h1:q15RT/pd6UggBXVBuLps8BXRvl5GPBcwVA7BJHMLuTw= -github.com/ryancurrah/gomodguard v1.3.0/go.mod h1:ggBxb3luypPEzqVtq33ee7YSN35V28XeGnid8dnni50= +github.com/ryancurrah/gomodguard v1.3.1 h1:fH+fUg+ngsQO0ruZXXHnA/2aNllWA1whly4a6UvyzGE= +github.com/ryancurrah/gomodguard v1.3.1/go.mod h1:DGFHzEhi6iJ0oIDfMuo3TgrS+L9gZvrEfmjjuelnRU0= github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/NsJFwFK7Uxc= github.com/sanposhiho/wastedassign/v2 v2.0.7/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= +github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.25.0 h1:IK8SI2QyFzy/2OD2PYnhy84dpfNo9qADrRt6LH8vSzU= @@ -476,8 +473,6 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= -github.com/sivchari/nosnakecase v1.7.0 h1:7QkpWIRMe8x25gckkFd2A5Pi6Ymo0qgr4JrhGt95do8= -github.com/sivchari/nosnakecase v1.7.0/go.mod h1:CwDzrzPea40/GB6uynrNLiorAlgFRvRbFSgJx2Gs+QY= github.com/sivchari/tenv v1.7.1 h1:PSpuD4bu6fSmtWMxSGWcvqUUgIn7k3yOJhOIzVWn8Ak= github.com/sivchari/tenv v1.7.1/go.mod h1:64yStXKSOxDfX47NlhVwND4dHwfZDdbp2Lyl018Icvg= github.com/sonatard/noctx v0.0.2 h1:L7Dz4De2zDQhW8S0t+KUjY0MAQJd6SgVwhzNIc4ok00= @@ -503,8 +498,9 @@ github.com/stbenjam/no-sprintf-host-port v0.1.1/go.mod h1:TLhvtIvONRzdmkFiio4O8L 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 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +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= @@ -512,8 +508,9 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 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/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c h1:+aPplBwWcHBo6q9xrfWdMrT9o4kltkmmvpemgIjep/8= @@ -530,8 +527,8 @@ github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 h1:quvGphlmUVU+n github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966/go.mod h1:27bSVNWSBOHm+qRp1T9qzaIpsWEP6TbUnei/43HK+PQ= github.com/timonwong/loggercheck v0.9.4 h1:HKKhqrjcVj8sxL7K77beXh0adEm6DLjV/QOGeMXEVi4= github.com/timonwong/loggercheck v0.9.4/go.mod h1:caz4zlPcgvpEkXgVnAJGowHAMW2NwHaNlpS8xDbVhTg= -github.com/tomarrell/wrapcheck/v2 v2.8.1 h1:HxSqDSN0sAt0yJYsrcYVoEeyM4aI9yAm3KQpIXDJRhQ= -github.com/tomarrell/wrapcheck/v2 v2.8.1/go.mod h1:/n2Q3NZ4XFT50ho6Hbxg+RV1uyo2Uow/Vdm9NQcl5SE= +github.com/tomarrell/wrapcheck/v2 v2.8.3 h1:5ov+Cbhlgi7s/a42BprYoxsr73CbdMUTzE3bRDFASUs= +github.com/tomarrell/wrapcheck/v2 v2.8.3/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= github.com/ultraware/funlen v0.1.0 h1:BuqclbkY6pO+cvxoq7OsktIXZpgBSkYTQtmwhAK81vI= @@ -559,10 +556,10 @@ gitlab.com/bosi/decorder v0.4.1 h1:VdsdfxhstabyhZovHafFw+9eJ6eU0d2CkFNJcZz/NU4= gitlab.com/bosi/decorder v0.4.1/go.mod h1:jecSqWUew6Yle1pCr2eLWTensJMmsxHsBwt+PVbkAqA= go-simpler.org/assert v0.7.0 h1:OzWWZqfNxt8cLS+MlUp6Tgk1HjPkmgdKBq9qvy8lZsA= go-simpler.org/assert v0.7.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= -go-simpler.org/musttag v0.8.0 h1:DR4UTgetNNhPRNo02rkK1hwDTRzAPotN+ZqYpdtEwWc= -go-simpler.org/musttag v0.8.0/go.mod h1:fiNdCkXt2S6je9Eblma3okjnlva9NT1Eg/WUt19rWu8= -go-simpler.org/sloglint v0.4.0 h1:UVJuUJo63iNQNFEOtZ6o1xAgagVg/giVLLvG9nNLobI= -go-simpler.org/sloglint v0.4.0/go.mod h1:v6zJ++j/thFPhefs2wEXoCKwT10yo5nkBDYRCXyqgNQ= +go-simpler.org/musttag v0.9.0 h1:Dzt6/tyP9ONr5g9h9P3cnYWCxeBFRkd0uJL/w+1Mxos= +go-simpler.org/musttag v0.9.0/go.mod h1:gA9nThnalvNSKpEoyp3Ko4/vCX2xTpqKoUtNqXOnVR4= +go-simpler.org/sloglint v0.5.0 h1:2YCcd+YMuYpuqthCgubcF5lBSjb6berc5VMOYUHKrpY= +go-simpler.org/sloglint v0.5.0/go.mod h1:EUknX5s8iXqf18KQxKnaBHUPVriiPnOrPjjJcsaTcSQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -570,6 +567,8 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= +go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= @@ -598,8 +597,8 @@ golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcH golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20231219180239-dc181d75b848 h1:UhRVJ0i7bF9n/Hd8YjW3eKjlPVBHzbQdxrBgjbSKl64= -golang.org/x/exp/typeparams v0.0.0-20231219180239-dc181d75b848/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8= +golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -628,8 +627,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= -golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -668,8 +667,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -745,8 +744,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -815,7 +814,6 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20201001104356-43ebab892c4c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= @@ -830,8 +828,8 @@ golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= -golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= +golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= +golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -911,8 +909,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj 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= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -939,14 +937,10 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.4.6 h1:oFEHCKeID7to/3autwsWfnuv69j3NsfcXbvJKuIcep8= -honnef.co/go/tools v0.4.6/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= +honnef.co/go/tools v0.4.7 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs= +honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo= mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA= -mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= -mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= -mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= -mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14 h1:zCr3iRRgdk5eIikZNDphGcM6KGVTx3Yu+/Uu9Es254w= mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14/go.mod h1:ZzZjEpJDOmx8TdVU6umamY3Xy0UAQUI2DHbf05USVbI= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= From c20e3c08271014f5983120d529cb77ced66a27c3 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Wed, 10 Apr 2024 12:02:01 -0400 Subject: [PATCH 0021/1670] Bump Go toolchain version to 1.22.2 There was a vulnerability in net/http on 1.22.1 and we rely on this package for the oidc broker, so we need to bump the toolchain. --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 8d66bd1506..a78118cff7 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/ubuntu/oidc-broker go 1.22.0 -toolchain go1.22.1 +toolchain go1.22.2 require ( github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf From 7318fee943076dd011943feb1dcc5b66bd3cebed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 08:27:28 +0000 Subject: [PATCH 0022/1670] deps(go-tools): bump github.com/golangci/golangci-lint in /tools Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.57.2 to 1.58.0. - [Release notes](https://github.com/golangci/golangci-lint/releases) - [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md) - [Commits](https://github.com/golangci/golangci-lint/compare/v1.57.2...v1.58.0) --- updated-dependencies: - dependency-name: github.com/golangci/golangci-lint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- tools/go.mod | 58 +++++++++++++------------ tools/go.sum | 120 +++++++++++++++++++++++++++------------------------ 2 files changed, 95 insertions(+), 83 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index 9dd27714b5..e07eefee9f 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -4,20 +4,21 @@ go 1.22.0 toolchain go1.22.1 -require github.com/golangci/golangci-lint v1.57.2 +require github.com/golangci/golangci-lint v1.58.0 require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 4d63.com/gochecknoglobals v0.2.1 // indirect github.com/4meepo/tagalign v1.3.3 // indirect github.com/Abirdcfly/dupword v0.0.14 // indirect - github.com/Antonboom/errname v0.1.12 // indirect - github.com/Antonboom/nilnil v0.1.7 // indirect + github.com/Antonboom/errname v0.1.13 // indirect + github.com/Antonboom/nilnil v0.1.8 // indirect github.com/Antonboom/testifylint v1.2.0 // indirect github.com/BurntSushi/toml v1.3.2 // indirect + github.com/Crocmagnon/fatcontext v0.2.2 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 // indirect - github.com/Masterminds/semver v1.5.0 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect github.com/alecthomas/go-check-sumtype v0.1.4 // indirect github.com/alexkohler/nakedret/v2 v2.0.4 // indirect @@ -32,25 +33,25 @@ require ( github.com/breml/bidichk v0.2.7 // indirect github.com/breml/errchkjson v0.3.6 // indirect github.com/butuzov/ireturn v0.3.0 // indirect - github.com/butuzov/mirror v1.1.0 // indirect + github.com/butuzov/mirror v1.2.0 // indirect github.com/catenacyber/perfsprint v0.7.1 // indirect github.com/ccojocar/zxcvbn-go v1.0.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/charithe/durationcheck v0.0.10 // indirect github.com/chavacava/garif v0.1.0 // indirect - github.com/ckaznocha/intrange v0.1.1 // indirect + github.com/ckaznocha/intrange v0.1.2 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect - github.com/daixiang0/gci v0.12.3 // indirect + github.com/daixiang0/gci v0.13.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect github.com/ettle/strcase v0.2.0 // indirect github.com/fatih/color v1.16.0 // indirect github.com/fatih/structtag v1.2.0 // indirect - github.com/firefart/nonamedreturns v1.0.4 // indirect + github.com/firefart/nonamedreturns v1.0.5 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect github.com/ghostiam/protogetter v0.3.5 // indirect - github.com/go-critic/go-critic v0.11.2 // indirect + github.com/go-critic/go-critic v0.11.3 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect github.com/go-toolsmith/astequal v1.2.0 // indirect @@ -65,9 +66,10 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e // indirect - github.com/golangci/misspell v0.4.1 // indirect + github.com/golangci/misspell v0.5.1 // indirect + github.com/golangci/modinfo v0.3.4 // indirect github.com/golangci/plugin-module-register v0.1.1 // indirect - github.com/golangci/revgrep v0.5.2 // indirect + github.com/golangci/revgrep v0.5.3 // indirect github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/gordonklaus/ineffassign v0.1.0 // indirect @@ -82,17 +84,18 @@ require ( github.com/jgautheron/goconst v1.7.1 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect - github.com/jjti/go-spancheck v0.5.3 // indirect + github.com/jjti/go-spancheck v0.6.1 // indirect github.com/julz/importas v0.1.0 // indirect - github.com/karamaru-alpha/copyloopvar v1.0.10 // indirect + github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect github.com/kisielk/errcheck v1.7.0 // indirect github.com/kkHAIKE/contextcheck v1.1.5 // indirect github.com/kulti/thelper v0.6.3 // indirect github.com/kunwardeep/paralleltest v1.0.10 // indirect github.com/kyoh86/exportloopref v0.1.11 // indirect + github.com/lasiar/canonicalheader v1.0.6 // indirect github.com/ldez/gomoddirectives v0.2.4 // indirect github.com/ldez/tagliatelle v0.5.0 // indirect - github.com/leonklingele/grouper v1.1.1 // indirect + github.com/leonklingele/grouper v1.1.2 // indirect github.com/lufeee/execinquery v1.2.1 // indirect github.com/macabu/inamedparam v0.1.3 // indirect github.com/magiconair/properties v1.8.6 // indirect @@ -114,18 +117,19 @@ require ( github.com/nunnatsa/ginkgolinter v0.16.2 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.2.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/polyfloyd/go-errorlint v1.4.8 // indirect + github.com/polyfloyd/go-errorlint v1.5.1 // indirect github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect github.com/quasilyte/go-ruleguard v0.4.2 // indirect + github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect github.com/quasilyte/gogrep v0.5.0 // indirect github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect - github.com/ryancurrah/gomodguard v1.3.1 // indirect + github.com/ryancurrah/gomodguard v1.3.2 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect @@ -157,26 +161,26 @@ require ( github.com/tomarrell/wrapcheck/v2 v2.8.3 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect github.com/ultraware/funlen v0.1.0 // indirect - github.com/ultraware/whitespace v0.1.0 // indirect + github.com/ultraware/whitespace v0.1.1 // indirect github.com/uudashr/gocognit v1.1.2 // indirect github.com/xen0n/gosmopolitan v1.2.2 // indirect github.com/yagipy/maintidx v1.0.0 // indirect - github.com/yeya24/promlinter v0.2.0 // indirect + github.com/yeya24/promlinter v0.3.0 // indirect github.com/ykadowak/zerologlint v0.1.5 // indirect - gitlab.com/bosi/decorder v0.4.1 // indirect - go-simpler.org/musttag v0.9.0 // indirect - go-simpler.org/sloglint v0.5.0 // indirect + gitlab.com/bosi/decorder v0.4.2 // indirect + go-simpler.org/musttag v0.12.1 // indirect + go-simpler.org/sloglint v0.6.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/automaxprocs v1.5.3 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect - golang.org/x/mod v0.16.0 // indirect - golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.19.0 // indirect + golang.org/x/tools v0.20.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/ini.v1 v1.67.0 // indirect @@ -184,5 +188,5 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect honnef.co/go/tools v0.4.7 // indirect mvdan.cc/gofumpt v0.6.0 // indirect - mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14 // indirect + mvdan.cc/unparam v0.0.0-20240427195214-063aff900ca1 // indirect ) diff --git a/tools/go.sum b/tools/go.sum index 74ce9606aa..e0f706803b 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -39,22 +39,24 @@ github.com/4meepo/tagalign v1.3.3 h1:ZsOxcwGD/jP4U/aw7qeWu58i7dwYemfy5Y+IF1ACoNw github.com/4meepo/tagalign v1.3.3/go.mod h1:Q9c1rYMZJc9dPRkbQPpcBNCLEmY2njbAsXhQOZFE2dE= github.com/Abirdcfly/dupword v0.0.14 h1:3U4ulkc8EUo+CaT105/GJ1BQwtgyj6+VaBVbAX11Ba8= github.com/Abirdcfly/dupword v0.0.14/go.mod h1:VKDAbxdY8YbKUByLGg8EETzYSuC4crm9WwI6Y3S0cLI= -github.com/Antonboom/errname v0.1.12 h1:oh9ak2zUtsLp5oaEd/erjB4GPu9w19NyoIskZClDcQY= -github.com/Antonboom/errname v0.1.12/go.mod h1:bK7todrzvlaZoQagP1orKzWXv59X/x0W0Io2XT1Ssro= -github.com/Antonboom/nilnil v0.1.7 h1:ofgL+BA7vlA1K2wNQOsHzLJ2Pw5B5DpWRLdDAVvvTow= -github.com/Antonboom/nilnil v0.1.7/go.mod h1:TP+ScQWVEq0eSIxqU8CbdT5DFWoHp0MbP+KMUO1BKYQ= +github.com/Antonboom/errname v0.1.13 h1:JHICqsewj/fNckzrfVSe+T33svwQxmjC+1ntDsHOVvM= +github.com/Antonboom/errname v0.1.13/go.mod h1:uWyefRYRN54lBg6HseYCFhs6Qjcy41Y3Jl/dVhA87Ns= +github.com/Antonboom/nilnil v0.1.8 h1:97QG7xrLq4TBK2U9aFq/I8Mcgz67pwMIiswnTA9gIn0= +github.com/Antonboom/nilnil v0.1.8/go.mod h1:iGe2rYwCq5/Me1khrysB4nwI7swQvjclR8/YRPl5ihQ= github.com/Antonboom/testifylint v1.2.0 h1:015bxD8zc5iY8QwTp4+RG9I4kIbqwvGX9TrBbb7jGdM= github.com/Antonboom/testifylint v1.2.0/go.mod h1:rkmEqjqVnHDRNsinyN6fPSLnoajzFwsCcguJgwADBkw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Crocmagnon/fatcontext v0.2.2 h1:OrFlsDdOj9hW/oBEJBNSuH7QWf+E9WPVHw+x52bXVbk= +github.com/Crocmagnon/fatcontext v0.2.2/go.mod h1:WSn/c/+MMNiD8Pri0ahRj0o9jVpeowzavOQplBJw6u0= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 h1:sATXp1x6/axKxz2Gjxv8MALP0bXaNRfQinEwyfMcx8c= github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0/go.mod h1:Nl76DrGNJTA1KJ0LePKBw/vznBX1EHbAZX8mwjR82nI= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ= github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= @@ -96,8 +98,8 @@ github.com/breml/errchkjson v0.3.6 h1:VLhVkqSBH96AvXEyclMR37rZslRrY2kcyq+31HCsVr github.com/breml/errchkjson v0.3.6/go.mod h1:jhSDoFheAF2RSDOlCfhHO9KqhZgAYLyvHe7bRCX8f/U= github.com/butuzov/ireturn v0.3.0 h1:hTjMqWw3y5JC3kpnC5vXmFJAWI/m31jaCYQqzkS6PL0= github.com/butuzov/ireturn v0.3.0/go.mod h1:A09nIiwiqzN/IoVo9ogpa0Hzi9fex1kd9PSD6edP5ZA= -github.com/butuzov/mirror v1.1.0 h1:ZqX54gBVMXu78QLoiqdwpl2mgmoOJTk7s4p4o+0avZI= -github.com/butuzov/mirror v1.1.0/go.mod h1:8Q0BdQU6rC6WILDiBM60DBfvV78OLJmMmixe7GF45AE= +github.com/butuzov/mirror v1.2.0 h1:9YVK1qIjNspaqWutSv8gsge2e/Xpq1eqEkslEUHy5cs= +github.com/butuzov/mirror v1.2.0/go.mod h1:DqZZDtzm42wIAIyHXeN8W/qb1EPlb9Qn/if9icBOpdQ= github.com/catenacyber/perfsprint v0.7.1 h1:PGW5G/Kxn+YrN04cRAZKC+ZuvlVwolYMrIyyTJ/rMmc= github.com/catenacyber/perfsprint v0.7.1/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= @@ -113,15 +115,15 @@ github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+U github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/ckaznocha/intrange v0.1.1 h1:gHe4LfqCspWkh8KpJFs20fJz3XRHFBFUV9yI7Itu83Q= -github.com/ckaznocha/intrange v0.1.1/go.mod h1:RWffCw/vKBwHeOEwWdCikAtY0q4gGt8VhJZEEA5n+RE= +github.com/ckaznocha/intrange v0.1.2 h1:3Y4JAxcMntgb/wABQ6e8Q8leMd26JbX2790lIss9MTI= +github.com/ckaznocha/intrange v0.1.2/go.mod h1:RWffCw/vKBwHeOEwWdCikAtY0q4gGt8VhJZEEA5n+RE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= -github.com/daixiang0/gci v0.12.3 h1:yOZI7VAxAGPQmkb1eqt5g/11SUlwoat1fSblGLmdiQc= -github.com/daixiang0/gci v0.12.3/go.mod h1:xtHP9N7AHdNvtRNfcx9gwTDfw7FRJx4bZUsiEfiNNAI= +github.com/daixiang0/gci v0.13.4 h1:61UGkmpoAcxHM2hhNkZEf5SzwQtWJXTSws7jaPyqwlw= +github.com/daixiang0/gci v0.13.4/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -137,8 +139,8 @@ github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= -github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y= -github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI= +github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA= +github.com/firefart/nonamedreturns v1.0.5/go.mod h1:gHJjDqhGM4WyPt639SOZs+G89Ko7QKH5R5BhnO6xJhw= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= @@ -147,8 +149,8 @@ github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= github.com/ghostiam/protogetter v0.3.5 h1:+f7UiF8XNd4w3a//4DnusQ2SZjPkUjxkMEfjbxOK4Ug= github.com/ghostiam/protogetter v0.3.5/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw= -github.com/go-critic/go-critic v0.11.2 h1:81xH/2muBphEgPtcwH1p6QD+KzXl2tMSi3hXjBSxDnM= -github.com/go-critic/go-critic v0.11.2/go.mod h1:OePaicfjsf+KPy33yq4gzv6CO7TEQ9Rom6ns1KsJnl8= +github.com/go-critic/go-critic v0.11.3 h1:SJbYD/egY1noYjTMNTlhGaYlfQ77rQmrNH7h+gtn0N0= +github.com/go-critic/go-critic v0.11.3/go.mod h1:Je0h5Obm1rR5hAGA9mP2PDiOOk53W+n7pyvXErFKIgI= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -224,14 +226,16 @@ github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9 github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e h1:ULcKCDV1LOZPFxGZaA6TlQbiM3J2GCPnkx/bGF6sX/g= github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e/go.mod h1:Pm5KhLPA8gSnQwrQ6ukebRcapGb/BG9iUkdaiCcGHJM= -github.com/golangci/golangci-lint v1.57.2 h1:NNhxfZyL5He1WWDrIvl1a4n5bvWZBcgAqBwlJAAgLTw= -github.com/golangci/golangci-lint v1.57.2/go.mod h1:ApiG3S3Ca23QyfGp5BmsorTiVxJpr5jGiNS0BkdSidg= -github.com/golangci/misspell v0.4.1 h1:+y73iSicVy2PqyX7kmUefHusENlrP9YwuHZHPLGQj/g= -github.com/golangci/misspell v0.4.1/go.mod h1:9mAN1quEo3DlpbaIKKyEvRxK1pwqR9s/Sea1bJCtlNI= +github.com/golangci/golangci-lint v1.58.0 h1:r8duFARMJ0VdSM9tDXAdt2+f57dfZQmagvYX6kmkUKQ= +github.com/golangci/golangci-lint v1.58.0/go.mod h1:WAY3BnSLvTUEv41Q0v3ZFzNybLRF+a7Vd9Da8Jx9Eqo= +github.com/golangci/misspell v0.5.1 h1:/SjR1clj5uDjNLwYzCahHwIOPmQgoH04AyQIiWGbhCM= +github.com/golangci/misspell v0.5.1/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= +github.com/golangci/modinfo v0.3.4 h1:oU5huX3fbxqQXdfspamej74DFX0kyGLkw1ppvXoJ8GA= +github.com/golangci/modinfo v0.3.4/go.mod h1:wytF1M5xl9u0ij8YSvhkEVPP3M5Mc7XLl1pxH3B2aUM= github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= -github.com/golangci/revgrep v0.5.2 h1:EndcWoRhcnfj2NHQ+28hyuXpLMF+dQmCN+YaeeIl4FU= -github.com/golangci/revgrep v0.5.2/go.mod h1:bjAMA+Sh/QUfTDcHzxfyHxr4xKvllVr/0sCv2e7jJHA= +github.com/golangci/revgrep v0.5.3 h1:3tL7c1XBMtWHHqVpS5ChmiAAoe4PF/d5+ULzV9sLAzs= +github.com/golangci/revgrep v0.5.3/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed h1:IURFTjxeTfNFP0hTEi1YKjB/ub8zkpaOqFFMApi2EAs= github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed/go.mod h1:XLXN8bNw4CGRPaqgl3bv/lhz7bsGPh4/xSaMTbo2vkQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -297,8 +301,8 @@ github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjz github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af h1:KA9BjwUk7KlCh6S9EAGWBt1oExIUv9WyNCiRz5amv48= github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= -github.com/jjti/go-spancheck v0.5.3 h1:vfq4s2IB8T3HvbpiwDTYgVPj1Ze/ZSXrTtaZRTc7CuM= -github.com/jjti/go-spancheck v0.5.3/go.mod h1:eQdOX1k3T+nAKvZDyLC3Eby0La4dZ+I19iOl5NzSPFE= +github.com/jjti/go-spancheck v0.6.1 h1:ZK/wE5Kyi1VX3PJpUO2oEgeoI4FWOUm7Shb2Gbv5obI= +github.com/jjti/go-spancheck v0.6.1/go.mod h1:vF1QkOO159prdo6mHRxak2CpzDpHAfKiPUDP/NeRnX8= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -310,8 +314,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= -github.com/karamaru-alpha/copyloopvar v1.0.10 h1:8HYDy6KQYqTmD7JuhZMWS1nwPru9889XI24ROd/+WXI= -github.com/karamaru-alpha/copyloopvar v1.0.10/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= +github.com/karamaru-alpha/copyloopvar v1.1.0 h1:x7gNyKcC2vRBO1H2Mks5u1VxQtYvFiym7fCjIP8RPos= +github.com/karamaru-alpha/copyloopvar v1.1.0/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= github.com/kisielk/errcheck v1.7.0 h1:+SbscKmWJ5mOK/bO1zS60F5I9WwZDWOfRsC4RwfwRV0= github.com/kisielk/errcheck v1.7.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -333,12 +337,14 @@ github.com/kunwardeep/paralleltest v1.0.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCT github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= github.com/kyoh86/exportloopref v0.1.11 h1:1Z0bcmTypkL3Q4k+IDHMWTcnCliEZcaPiIe0/ymEyhQ= github.com/kyoh86/exportloopref v0.1.11/go.mod h1:qkV4UF1zGl6EkF1ox8L5t9SwyeBAZ3qLMd6up458uqA= +github.com/lasiar/canonicalheader v1.0.6 h1:LJiiZ/MzkqibXOL2v+J8+WZM21pM0ivrBY/jbm9f5fo= +github.com/lasiar/canonicalheader v1.0.6/go.mod h1:GfXTLQb3O1qF5qcSTyXTnfNUggUNyzbkOSpzZ0dpUJo= github.com/ldez/gomoddirectives v0.2.4 h1:j3YjBIjEBbqZ0NKtBNzr8rtMHTOrLPeiwTkfUJZ3alg= github.com/ldez/gomoddirectives v0.2.4/go.mod h1:oWu9i62VcQDYp9EQ0ONTfqLNh+mDLWWDO+SO0qSQw5g= github.com/ldez/tagliatelle v0.5.0 h1:epgfuYt9v0CG3fms0pEgIMNPuFf/LpPIfjk4kyqSioo= github.com/ldez/tagliatelle v0.5.0/go.mod h1:rj1HmWiL1MiKQuOONhd09iySTEkUuE/8+5jtPYz9xa4= -github.com/leonklingele/grouper v1.1.1 h1:suWXRU57D4/Enn6pXR0QVqqWWrnJ9Osrz+5rjt8ivzU= -github.com/leonklingele/grouper v1.1.1/go.mod h1:uk3I3uDfi9B6PeUjsCKi6ndcf63Uy7snXgR4yDYQVDY= +github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= +github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= github.com/lufeee/execinquery v1.2.1 h1:hf0Ems4SHcUGBxpGN7Jz78z1ppVkP/837ZlETPCEtOM= github.com/lufeee/execinquery v1.2.1/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM= github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk= @@ -402,16 +408,16 @@ github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT9 github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= -github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polyfloyd/go-errorlint v1.4.8 h1:jiEjKDH33ouFktyez7sckv6pHWif9B7SuS8cutDXFHw= -github.com/polyfloyd/go-errorlint v1.4.8/go.mod h1:NNCxFcFjZcw3xNjVdCchERkEM6Oz7wta2XJVxRftwO4= +github.com/polyfloyd/go-errorlint v1.5.1 h1:5gHxDjLyyWij7fhfrjYNNlHsUNQeyx0LFQKUelO3RBo= +github.com/polyfloyd/go-errorlint v1.5.1/go.mod h1:sH1QC1pxxi0fFecsVIzBmxtrgd9IF/SkJpA6wqyKAJs= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -438,6 +444,8 @@ github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/quasilyte/go-ruleguard v0.4.2 h1:htXcXDK6/rO12kiTHKfHuqR4kr3Y4M0J0rOL6CH/BYs= github.com/quasilyte/go-ruleguard v0.4.2/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= +github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= +github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= @@ -448,8 +456,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryancurrah/gomodguard v1.3.1 h1:fH+fUg+ngsQO0ruZXXHnA/2aNllWA1whly4a6UvyzGE= -github.com/ryancurrah/gomodguard v1.3.1/go.mod h1:DGFHzEhi6iJ0oIDfMuo3TgrS+L9gZvrEfmjjuelnRU0= +github.com/ryancurrah/gomodguard v1.3.2 h1:CuG27ulzEB1Gu5Dk5gP8PFxSOZ3ptSdP5iI/3IXxM18= +github.com/ryancurrah/gomodguard v1.3.2/go.mod h1:LqdemiFomEjcxOqirbQCb3JFvSxH2JUYMerTFd3sF2o= github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/NsJFwFK7Uxc= @@ -533,16 +541,16 @@ github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+ github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= github.com/ultraware/funlen v0.1.0 h1:BuqclbkY6pO+cvxoq7OsktIXZpgBSkYTQtmwhAK81vI= github.com/ultraware/funlen v0.1.0/go.mod h1:XJqmOQja6DpxarLj6Jj1U7JuoS8PvL4nEqDaQhy22p4= -github.com/ultraware/whitespace v0.1.0 h1:O1HKYoh0kIeqE8sFqZf1o0qbORXUCOQFrlaQyZsczZw= -github.com/ultraware/whitespace v0.1.0/go.mod h1:/se4r3beMFNmewJ4Xmz0nMQ941GJt+qmSHGP9emHYe0= +github.com/ultraware/whitespace v0.1.1 h1:bTPOGejYFulW3PkcrqkeQwOd6NKOOXvmGD9bo/Gk8VQ= +github.com/ultraware/whitespace v0.1.1/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= github.com/uudashr/gocognit v1.1.2 h1:l6BAEKJqQH2UpKAPKdMfZf5kE4W/2xk8pfU1OVLvniI= github.com/uudashr/gocognit v1.1.2/go.mod h1:aAVdLURqcanke8h3vg35BC++eseDm66Z7KmchI5et4k= github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= -github.com/yeya24/promlinter v0.2.0 h1:xFKDQ82orCU5jQujdaD8stOHiv8UN68BSdn2a8u8Y3o= -github.com/yeya24/promlinter v0.2.0/go.mod h1:u54lkmBOZrpEbQQ6gox2zWKKLKu2SGe+2KOiextY+IA= +github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs= +github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4= github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -552,14 +560,14 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -gitlab.com/bosi/decorder v0.4.1 h1:VdsdfxhstabyhZovHafFw+9eJ6eU0d2CkFNJcZz/NU4= -gitlab.com/bosi/decorder v0.4.1/go.mod h1:jecSqWUew6Yle1pCr2eLWTensJMmsxHsBwt+PVbkAqA= +gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= +gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= go-simpler.org/assert v0.7.0 h1:OzWWZqfNxt8cLS+MlUp6Tgk1HjPkmgdKBq9qvy8lZsA= go-simpler.org/assert v0.7.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= -go-simpler.org/musttag v0.9.0 h1:Dzt6/tyP9ONr5g9h9P3cnYWCxeBFRkd0uJL/w+1Mxos= -go-simpler.org/musttag v0.9.0/go.mod h1:gA9nThnalvNSKpEoyp3Ko4/vCX2xTpqKoUtNqXOnVR4= -go-simpler.org/sloglint v0.5.0 h1:2YCcd+YMuYpuqthCgubcF5lBSjb6berc5VMOYUHKrpY= -go-simpler.org/sloglint v0.5.0/go.mod h1:EUknX5s8iXqf18KQxKnaBHUPVriiPnOrPjjJcsaTcSQ= +go-simpler.org/musttag v0.12.1 h1:yaMcjl/uyVnd1z6GqIhBiFH/PoqNN9f2IgtU7bp7W/0= +go-simpler.org/musttag v0.12.1/go.mod h1:46HKu04A3Am9Lne5kKP0ssgwY3AeIlqsDzz3UxKROpY= +go-simpler.org/sloglint v0.6.0 h1:0YcqSVG7LI9EVBfRPhgPec79BH6X6mwjFuUR5Mr7j1M= +go-simpler.org/sloglint v0.6.0/go.mod h1:+kJJtebtPePWyG5boFwY46COydAggADDOHM22zOvzBk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -627,8 +635,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -667,8 +675,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= 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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -688,8 +696,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -744,8 +752,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -828,8 +836,8 @@ golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= -golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= +golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= +golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -941,8 +949,8 @@ honnef.co/go/tools v0.4.7 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs= honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo= mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA= -mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14 h1:zCr3iRRgdk5eIikZNDphGcM6KGVTx3Yu+/Uu9Es254w= -mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14/go.mod h1:ZzZjEpJDOmx8TdVU6umamY3Xy0UAQUI2DHbf05USVbI= +mvdan.cc/unparam v0.0.0-20240427195214-063aff900ca1 h1:Nykk7fggxChwLK4rUPYESzeIwqsuxXXlFEAh5YhaMRo= +mvdan.cc/unparam v0.0.0-20240427195214-063aff900ca1/go.mod h1:ZzZjEpJDOmx8TdVU6umamY3Xy0UAQUI2DHbf05USVbI= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From f593b47fe074dff06037485bedc5c9e53e7d4a1e Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Fri, 10 May 2024 08:17:27 -0400 Subject: [PATCH 0023/1670] Bump Go toolchain to 1.22.3 There is a vulnerability in the net pkg on go 1.22.2, updating to 1.22.3 should fix it. --- go.mod | 2 +- tools/go.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index a78118cff7..a51e1042d9 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/ubuntu/oidc-broker go 1.22.0 -toolchain go1.22.2 +toolchain go1.22.3 require ( github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf diff --git a/tools/go.mod b/tools/go.mod index e07eefee9f..80e53aab29 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -2,7 +2,7 @@ module github.com/ubuntu/oidc-broker/tools go 1.22.0 -toolchain go1.22.1 +toolchain go1.22.3 require github.com/golangci/golangci-lint v1.58.0 From 2d46efbf8a40b2efd932f82e19c51c5c4f9726d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 May 2024 11:44:38 +0000 Subject: [PATCH 0024/1670] deps(go-tools): bump github.com/golangci/golangci-lint in /tools Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.58.0 to 1.58.1. - [Release notes](https://github.com/golangci/golangci-lint/releases) - [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md) - [Commits](https://github.com/golangci/golangci-lint/compare/v1.58.0...v1.58.1) --- updated-dependencies: - dependency-name: github.com/golangci/golangci-lint dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- tools/go.mod | 10 +++++----- tools/go.sum | 24 ++++++++++++------------ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index 80e53aab29..408260750c 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -4,12 +4,12 @@ go 1.22.0 toolchain go1.22.3 -require github.com/golangci/golangci-lint v1.58.0 +require github.com/golangci/golangci-lint v1.58.1 require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 4d63.com/gochecknoglobals v0.2.1 // indirect - github.com/4meepo/tagalign v1.3.3 // indirect + github.com/4meepo/tagalign v1.3.4 // indirect github.com/Abirdcfly/dupword v0.0.14 // indirect github.com/Antonboom/errname v0.1.13 // indirect github.com/Antonboom/nilnil v0.1.8 // indirect @@ -50,7 +50,7 @@ require ( github.com/firefart/nonamedreturns v1.0.5 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/ghostiam/protogetter v0.3.5 // indirect + github.com/ghostiam/protogetter v0.3.6 // indirect github.com/go-critic/go-critic v0.11.3 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect @@ -178,9 +178,9 @@ require ( golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.19.0 // indirect + golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.20.0 // indirect + golang.org/x/tools v0.21.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/tools/go.sum b/tools/go.sum index e0f706803b..ad72e8906a 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -35,8 +35,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/4meepo/tagalign v1.3.3 h1:ZsOxcwGD/jP4U/aw7qeWu58i7dwYemfy5Y+IF1ACoNw= -github.com/4meepo/tagalign v1.3.3/go.mod h1:Q9c1rYMZJc9dPRkbQPpcBNCLEmY2njbAsXhQOZFE2dE= +github.com/4meepo/tagalign v1.3.4 h1:P51VcvBnf04YkHzjfclN6BbsopfJR5rxs1n+5zHt+w8= +github.com/4meepo/tagalign v1.3.4/go.mod h1:M+pnkHH2vG8+qhE5bVc/zeP7HS/j910Fwa9TUSyZVI0= github.com/Abirdcfly/dupword v0.0.14 h1:3U4ulkc8EUo+CaT105/GJ1BQwtgyj6+VaBVbAX11Ba8= github.com/Abirdcfly/dupword v0.0.14/go.mod h1:VKDAbxdY8YbKUByLGg8EETzYSuC4crm9WwI6Y3S0cLI= github.com/Antonboom/errname v0.1.13 h1:JHICqsewj/fNckzrfVSe+T33svwQxmjC+1ntDsHOVvM= @@ -147,8 +147,8 @@ github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwV github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/ghostiam/protogetter v0.3.5 h1:+f7UiF8XNd4w3a//4DnusQ2SZjPkUjxkMEfjbxOK4Ug= -github.com/ghostiam/protogetter v0.3.5/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw= +github.com/ghostiam/protogetter v0.3.6 h1:R7qEWaSgFCsy20yYHNIJsU9ZOb8TziSRRxuAOTVKeOk= +github.com/ghostiam/protogetter v0.3.6/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw= github.com/go-critic/go-critic v0.11.3 h1:SJbYD/egY1noYjTMNTlhGaYlfQ77rQmrNH7h+gtn0N0= github.com/go-critic/go-critic v0.11.3/go.mod h1:Je0h5Obm1rR5hAGA9mP2PDiOOk53W+n7pyvXErFKIgI= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -226,8 +226,8 @@ github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9 github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e h1:ULcKCDV1LOZPFxGZaA6TlQbiM3J2GCPnkx/bGF6sX/g= github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e/go.mod h1:Pm5KhLPA8gSnQwrQ6ukebRcapGb/BG9iUkdaiCcGHJM= -github.com/golangci/golangci-lint v1.58.0 h1:r8duFARMJ0VdSM9tDXAdt2+f57dfZQmagvYX6kmkUKQ= -github.com/golangci/golangci-lint v1.58.0/go.mod h1:WAY3BnSLvTUEv41Q0v3ZFzNybLRF+a7Vd9Da8Jx9Eqo= +github.com/golangci/golangci-lint v1.58.1 h1:IYKjkt7nofq/mYXiDUyJiBZQi5kxD0jPCjBy6VXxjz8= +github.com/golangci/golangci-lint v1.58.1/go.mod h1:IX9uSbhwDDOVTcceKZWmshlally+fOQYv1pZhIJCMNw= github.com/golangci/misspell v0.5.1 h1:/SjR1clj5uDjNLwYzCahHwIOPmQgoH04AyQIiWGbhCM= github.com/golangci/misspell v0.5.1/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= github.com/golangci/modinfo v0.3.4 h1:oU5huX3fbxqQXdfspamej74DFX0kyGLkw1ppvXoJ8GA= @@ -675,8 +675,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -752,8 +752,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -836,8 +836,8 @@ golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= -golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= +golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= +golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 453684b701e7316ea9c5fe2e5458efed23333241 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 08:49:52 +0000 Subject: [PATCH 0025/1670] deps(go-tools): bump github.com/golangci/golangci-lint in /tools Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.58.1 to 1.58.2. - [Release notes](https://github.com/golangci/golangci-lint/releases) - [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md) - [Commits](https://github.com/golangci/golangci-lint/compare/v1.58.1...v1.58.2) --- updated-dependencies: - dependency-name: github.com/golangci/golangci-lint dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- tools/go.mod | 14 +++++++------- tools/go.sum | 52 ++++++++++++++++++++++++++-------------------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index 408260750c..bf573f9553 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -4,7 +4,7 @@ go 1.22.0 toolchain go1.22.3 -require github.com/golangci/golangci-lint v1.58.1 +require github.com/golangci/golangci-lint v1.58.2 require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect @@ -12,7 +12,7 @@ require ( github.com/4meepo/tagalign v1.3.4 // indirect github.com/Abirdcfly/dupword v0.0.14 // indirect github.com/Antonboom/errname v0.1.13 // indirect - github.com/Antonboom/nilnil v0.1.8 // indirect + github.com/Antonboom/nilnil v0.1.9 // indirect github.com/Antonboom/testifylint v1.2.0 // indirect github.com/BurntSushi/toml v1.3.2 // indirect github.com/Crocmagnon/fatcontext v0.2.2 // indirect @@ -45,7 +45,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect github.com/ettle/strcase v0.2.0 // indirect - github.com/fatih/color v1.16.0 // indirect + github.com/fatih/color v1.17.0 // indirect github.com/fatih/structtag v1.2.0 // indirect github.com/firefart/nonamedreturns v1.0.5 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect @@ -92,7 +92,7 @@ require ( github.com/kulti/thelper v0.6.3 // indirect github.com/kunwardeep/paralleltest v1.0.10 // indirect github.com/kyoh86/exportloopref v0.1.11 // indirect - github.com/lasiar/canonicalheader v1.0.6 // indirect + github.com/lasiar/canonicalheader v1.1.1 // indirect github.com/ldez/gomoddirectives v0.2.4 // indirect github.com/ldez/tagliatelle v0.5.0 // indirect github.com/leonklingele/grouper v1.1.2 // indirect @@ -135,7 +135,7 @@ require ( github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.25.0 // indirect - github.com/securego/gosec/v2 v2.19.0 // indirect + github.com/securego/gosec/v2 v2.20.0 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect @@ -168,7 +168,7 @@ require ( github.com/yeya24/promlinter v0.3.0 // indirect github.com/ykadowak/zerologlint v0.1.5 // indirect gitlab.com/bosi/decorder v0.4.2 // indirect - go-simpler.org/musttag v0.12.1 // indirect + go-simpler.org/musttag v0.12.2 // indirect go-simpler.org/sloglint v0.6.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/automaxprocs v1.5.3 // indirect @@ -179,7 +179,7 @@ require ( golang.org/x/mod v0.17.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/text v0.15.0 // indirect golang.org/x/tools v0.21.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect diff --git a/tools/go.sum b/tools/go.sum index ad72e8906a..d3868a0993 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -41,8 +41,8 @@ github.com/Abirdcfly/dupword v0.0.14 h1:3U4ulkc8EUo+CaT105/GJ1BQwtgyj6+VaBVbAX11 github.com/Abirdcfly/dupword v0.0.14/go.mod h1:VKDAbxdY8YbKUByLGg8EETzYSuC4crm9WwI6Y3S0cLI= github.com/Antonboom/errname v0.1.13 h1:JHICqsewj/fNckzrfVSe+T33svwQxmjC+1ntDsHOVvM= github.com/Antonboom/errname v0.1.13/go.mod h1:uWyefRYRN54lBg6HseYCFhs6Qjcy41Y3Jl/dVhA87Ns= -github.com/Antonboom/nilnil v0.1.8 h1:97QG7xrLq4TBK2U9aFq/I8Mcgz67pwMIiswnTA9gIn0= -github.com/Antonboom/nilnil v0.1.8/go.mod h1:iGe2rYwCq5/Me1khrysB4nwI7swQvjclR8/YRPl5ihQ= +github.com/Antonboom/nilnil v0.1.9 h1:eKFMejSxPSA9eLSensFmjW2XTgTwJMjZ8hUHtV4s/SQ= +github.com/Antonboom/nilnil v0.1.9/go.mod h1:iGe2rYwCq5/Me1khrysB4nwI7swQvjclR8/YRPl5ihQ= github.com/Antonboom/testifylint v1.2.0 h1:015bxD8zc5iY8QwTp4+RG9I4kIbqwvGX9TrBbb7jGdM= github.com/Antonboom/testifylint v1.2.0/go.mod h1:rkmEqjqVnHDRNsinyN6fPSLnoajzFwsCcguJgwADBkw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -135,8 +135,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA= @@ -160,11 +160,11 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= @@ -226,8 +226,8 @@ github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9 github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e h1:ULcKCDV1LOZPFxGZaA6TlQbiM3J2GCPnkx/bGF6sX/g= github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e/go.mod h1:Pm5KhLPA8gSnQwrQ6ukebRcapGb/BG9iUkdaiCcGHJM= -github.com/golangci/golangci-lint v1.58.1 h1:IYKjkt7nofq/mYXiDUyJiBZQi5kxD0jPCjBy6VXxjz8= -github.com/golangci/golangci-lint v1.58.1/go.mod h1:IX9uSbhwDDOVTcceKZWmshlally+fOQYv1pZhIJCMNw= +github.com/golangci/golangci-lint v1.58.2 h1:YHfceEW3CmHmJTPkmVNlqEa4xcjIulYIaVIiB5sNbC4= +github.com/golangci/golangci-lint v1.58.2/go.mod h1:QH/aRLQIdNuOqEiki+4En6/k2SmeOsRJU/oiYffM1Ow= github.com/golangci/misspell v0.5.1 h1:/SjR1clj5uDjNLwYzCahHwIOPmQgoH04AyQIiWGbhCM= github.com/golangci/misspell v0.5.1/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= github.com/golangci/modinfo v0.3.4 h1:oU5huX3fbxqQXdfspamej74DFX0kyGLkw1ppvXoJ8GA= @@ -264,8 +264,8 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -337,8 +337,8 @@ github.com/kunwardeep/paralleltest v1.0.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCT github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= github.com/kyoh86/exportloopref v0.1.11 h1:1Z0bcmTypkL3Q4k+IDHMWTcnCliEZcaPiIe0/ymEyhQ= github.com/kyoh86/exportloopref v0.1.11/go.mod h1:qkV4UF1zGl6EkF1ox8L5t9SwyeBAZ3qLMd6up458uqA= -github.com/lasiar/canonicalheader v1.0.6 h1:LJiiZ/MzkqibXOL2v+J8+WZM21pM0ivrBY/jbm9f5fo= -github.com/lasiar/canonicalheader v1.0.6/go.mod h1:GfXTLQb3O1qF5qcSTyXTnfNUggUNyzbkOSpzZ0dpUJo= +github.com/lasiar/canonicalheader v1.1.1 h1:wC+dY9ZfiqiPwAexUApFush/csSPXeIi4QqyxXmng8I= +github.com/lasiar/canonicalheader v1.1.1/go.mod h1:cXkb3Dlk6XXy+8MVQnF23CYKWlyA7kfQhSw2CcZtZb0= github.com/ldez/gomoddirectives v0.2.4 h1:j3YjBIjEBbqZ0NKtBNzr8rtMHTOrLPeiwTkfUJZ3alg= github.com/ldez/gomoddirectives v0.2.4/go.mod h1:oWu9i62VcQDYp9EQ0ONTfqLNh+mDLWWDO+SO0qSQw5g= github.com/ldez/tagliatelle v0.5.0 h1:epgfuYt9v0CG3fms0pEgIMNPuFf/LpPIfjk4kyqSioo= @@ -395,10 +395,10 @@ github.com/nunnatsa/ginkgolinter v0.16.2 h1:8iLqHIZvN4fTLDC0Ke9tbSZVcyVHoBs0HIbn github.com/nunnatsa/ginkgolinter v0.16.2/go.mod h1:4tWRinDN1FeJgU+iJANW/kz7xKN5nYRAOfJDQUS9dOQ= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= -github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= -github.com/onsi/gomega v1.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo= -github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0= +github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g= +github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= @@ -468,8 +468,8 @@ github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tM github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.25.0 h1:IK8SI2QyFzy/2OD2PYnhy84dpfNo9qADrRt6LH8vSzU= github.com/sashamelentyev/usestdlibvars v1.25.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= -github.com/securego/gosec/v2 v2.19.0 h1:gl5xMkOI0/E6Hxx0XCY2XujA3V7SNSefA8sC+3f1gnk= -github.com/securego/gosec/v2 v2.19.0/go.mod h1:hOkDcHz9J/XIgIlPDXalxjeVYsHxoWUc5zJSHxcB8YM= +github.com/securego/gosec/v2 v2.20.0 h1:z/d5qp1niWa2avgFyUIglYTYYuGq2LrJwNj1HRVXsqc= +github.com/securego/gosec/v2 v2.20.0/go.mod h1:hkiArbBZLwK1cehBcg3oFWUlYPWTBffPwwJVWChu83o= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= @@ -562,10 +562,10 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= -go-simpler.org/assert v0.7.0 h1:OzWWZqfNxt8cLS+MlUp6Tgk1HjPkmgdKBq9qvy8lZsA= -go-simpler.org/assert v0.7.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= -go-simpler.org/musttag v0.12.1 h1:yaMcjl/uyVnd1z6GqIhBiFH/PoqNN9f2IgtU7bp7W/0= -go-simpler.org/musttag v0.12.1/go.mod h1:46HKu04A3Am9Lne5kKP0ssgwY3AeIlqsDzz3UxKROpY= +go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= +go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= +go-simpler.org/musttag v0.12.2 h1:J7lRc2ysXOq7eM8rwaTYnNrHd5JwjppzB6mScysB2Cs= +go-simpler.org/musttag v0.12.2/go.mod h1:uN1DVIasMTQKk6XSik7yrJoEysGtR2GRqvWnI9S7TYM= go-simpler.org/sloglint v0.6.0 h1:0YcqSVG7LI9EVBfRPhgPec79BH6X6mwjFuUR5Mr7j1M= go-simpler.org/sloglint v0.6.0/go.mod h1:+kJJtebtPePWyG5boFwY46COydAggADDOHM22zOvzBk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -770,8 +770,8 @@ 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.6.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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -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-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 54450d815c87ca370f2ad7a0b119d18e00e00ca9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 May 2024 08:43:40 +0000 Subject: [PATCH 0026/1670] deps(go-tools): bump github.com/golangci/golangci-lint in /tools Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.58.2 to 1.59.0. - [Release notes](https://github.com/golangci/golangci-lint/releases) - [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md) - [Commits](https://github.com/golangci/golangci-lint/compare/v1.58.2...v1.59.0) --- updated-dependencies: - dependency-name: github.com/golangci/golangci-lint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- tools/go.mod | 14 +++++++------- tools/go.sum | 32 ++++++++++++++++---------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index bf573f9553..d167681402 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -4,7 +4,7 @@ go 1.22.0 toolchain go1.22.3 -require github.com/golangci/golangci-lint v1.58.2 +require github.com/golangci/golangci-lint v1.59.0 require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect @@ -13,8 +13,8 @@ require ( github.com/Abirdcfly/dupword v0.0.14 // indirect github.com/Antonboom/errname v0.1.13 // indirect github.com/Antonboom/nilnil v0.1.9 // indirect - github.com/Antonboom/testifylint v1.2.0 // indirect - github.com/BurntSushi/toml v1.3.2 // indirect + github.com/Antonboom/testifylint v1.3.0 // indirect + github.com/BurntSushi/toml v1.4.0 // indirect github.com/Crocmagnon/fatcontext v0.2.2 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 // indirect @@ -51,7 +51,7 @@ require ( github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect github.com/ghostiam/protogetter v0.3.6 // indirect - github.com/go-critic/go-critic v0.11.3 // indirect + github.com/go-critic/go-critic v0.11.4 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect github.com/go-toolsmith/astequal v1.2.0 // indirect @@ -77,7 +77,7 @@ require ( github.com/gostaticanalysis/comment v1.4.2 // indirect github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect github.com/gostaticanalysis/nilerr v0.1.1 // indirect - github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -135,7 +135,7 @@ require ( github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.25.0 // indirect - github.com/securego/gosec/v2 v2.20.0 // indirect + github.com/securego/gosec/v2 v2.20.1-0.20240525090044-5f0084eb01a9 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect @@ -169,7 +169,7 @@ require ( github.com/ykadowak/zerologlint v0.1.5 // indirect gitlab.com/bosi/decorder v0.4.2 // indirect go-simpler.org/musttag v0.12.2 // indirect - go-simpler.org/sloglint v0.6.0 // indirect + go-simpler.org/sloglint v0.7.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/automaxprocs v1.5.3 // indirect go.uber.org/multierr v1.6.0 // indirect diff --git a/tools/go.sum b/tools/go.sum index d3868a0993..be04ca737e 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -43,11 +43,11 @@ github.com/Antonboom/errname v0.1.13 h1:JHICqsewj/fNckzrfVSe+T33svwQxmjC+1ntDsHO github.com/Antonboom/errname v0.1.13/go.mod h1:uWyefRYRN54lBg6HseYCFhs6Qjcy41Y3Jl/dVhA87Ns= github.com/Antonboom/nilnil v0.1.9 h1:eKFMejSxPSA9eLSensFmjW2XTgTwJMjZ8hUHtV4s/SQ= github.com/Antonboom/nilnil v0.1.9/go.mod h1:iGe2rYwCq5/Me1khrysB4nwI7swQvjclR8/YRPl5ihQ= -github.com/Antonboom/testifylint v1.2.0 h1:015bxD8zc5iY8QwTp4+RG9I4kIbqwvGX9TrBbb7jGdM= -github.com/Antonboom/testifylint v1.2.0/go.mod h1:rkmEqjqVnHDRNsinyN6fPSLnoajzFwsCcguJgwADBkw= +github.com/Antonboom/testifylint v1.3.0 h1:UiqrddKs1W3YK8R0TUuWwrVKlVAnS07DTUVWWs9c+y4= +github.com/Antonboom/testifylint v1.3.0/go.mod h1:NV0hTlteCkViPW9mSR4wEMfwp+Hs1T3dY60bkvSfhpM= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Crocmagnon/fatcontext v0.2.2 h1:OrFlsDdOj9hW/oBEJBNSuH7QWf+E9WPVHw+x52bXVbk= github.com/Crocmagnon/fatcontext v0.2.2/go.mod h1:WSn/c/+MMNiD8Pri0ahRj0o9jVpeowzavOQplBJw6u0= @@ -149,8 +149,8 @@ github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= github.com/ghostiam/protogetter v0.3.6 h1:R7qEWaSgFCsy20yYHNIJsU9ZOb8TziSRRxuAOTVKeOk= github.com/ghostiam/protogetter v0.3.6/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw= -github.com/go-critic/go-critic v0.11.3 h1:SJbYD/egY1noYjTMNTlhGaYlfQ77rQmrNH7h+gtn0N0= -github.com/go-critic/go-critic v0.11.3/go.mod h1:Je0h5Obm1rR5hAGA9mP2PDiOOk53W+n7pyvXErFKIgI= +github.com/go-critic/go-critic v0.11.4 h1:O7kGOCx0NDIni4czrkRIXTnit0mkyKOCePh3My6OyEU= +github.com/go-critic/go-critic v0.11.4/go.mod h1:2QAdo4iuLik5S9YG0rT4wcZ8QxwHYkrr6/2MWAiv/vc= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -226,8 +226,8 @@ github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9 github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e h1:ULcKCDV1LOZPFxGZaA6TlQbiM3J2GCPnkx/bGF6sX/g= github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e/go.mod h1:Pm5KhLPA8gSnQwrQ6ukebRcapGb/BG9iUkdaiCcGHJM= -github.com/golangci/golangci-lint v1.58.2 h1:YHfceEW3CmHmJTPkmVNlqEa4xcjIulYIaVIiB5sNbC4= -github.com/golangci/golangci-lint v1.58.2/go.mod h1:QH/aRLQIdNuOqEiki+4En6/k2SmeOsRJU/oiYffM1Ow= +github.com/golangci/golangci-lint v1.59.0 h1:st69YDnAH/v2QXDcgUaZ0seQajHScPALBVkyitYLXEk= +github.com/golangci/golangci-lint v1.59.0/go.mod h1:QNA32UWdUdHXnu+Ap5/ZU4WVwyp2tL94UxEXrSErjg0= github.com/golangci/misspell v0.5.1 h1:/SjR1clj5uDjNLwYzCahHwIOPmQgoH04AyQIiWGbhCM= github.com/golangci/misspell v0.5.1/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= github.com/golangci/modinfo v0.3.4 h1:oU5huX3fbxqQXdfspamej74DFX0kyGLkw1ppvXoJ8GA= @@ -284,8 +284,8 @@ github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod github.com/gostaticanalysis/testutil v0.4.0 h1:nhdCmubdmDF6VEatUNjgUZBJKWRqugoISdUv3PPQgHY= github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -395,8 +395,8 @@ github.com/nunnatsa/ginkgolinter v0.16.2 h1:8iLqHIZvN4fTLDC0Ke9tbSZVcyVHoBs0HIbn github.com/nunnatsa/ginkgolinter v0.16.2/go.mod h1:4tWRinDN1FeJgU+iJANW/kz7xKN5nYRAOfJDQUS9dOQ= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g= -github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= +github.com/onsi/ginkgo/v2 v2.17.3 h1:oJcvKpIb7/8uLpDDtnQuf18xVnwKp8DTD7DQ6gTd/MU= +github.com/onsi/ginkgo/v2 v2.17.3/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= @@ -468,8 +468,8 @@ github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tM github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.25.0 h1:IK8SI2QyFzy/2OD2PYnhy84dpfNo9qADrRt6LH8vSzU= github.com/sashamelentyev/usestdlibvars v1.25.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= -github.com/securego/gosec/v2 v2.20.0 h1:z/d5qp1niWa2avgFyUIglYTYYuGq2LrJwNj1HRVXsqc= -github.com/securego/gosec/v2 v2.20.0/go.mod h1:hkiArbBZLwK1cehBcg3oFWUlYPWTBffPwwJVWChu83o= +github.com/securego/gosec/v2 v2.20.1-0.20240525090044-5f0084eb01a9 h1:rnO6Zp1YMQwv8AyxzuwsVohljJgp4L0ZqiCgtACsPsc= +github.com/securego/gosec/v2 v2.20.1-0.20240525090044-5f0084eb01a9/go.mod h1:dg7lPlu/xK/Ut9SedURCoZbVCR4yC7fM65DtH9/CDHs= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= @@ -566,8 +566,8 @@ go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= go-simpler.org/musttag v0.12.2 h1:J7lRc2ysXOq7eM8rwaTYnNrHd5JwjppzB6mScysB2Cs= go-simpler.org/musttag v0.12.2/go.mod h1:uN1DVIasMTQKk6XSik7yrJoEysGtR2GRqvWnI9S7TYM= -go-simpler.org/sloglint v0.6.0 h1:0YcqSVG7LI9EVBfRPhgPec79BH6X6mwjFuUR5Mr7j1M= -go-simpler.org/sloglint v0.6.0/go.mod h1:+kJJtebtPePWyG5boFwY46COydAggADDOHM22zOvzBk= +go-simpler.org/sloglint v0.7.0 h1:rMZRxD9MbaGoRFobIOicMxZzum7AXNFDlez6xxJs5V4= +go-simpler.org/sloglint v0.7.0/go.mod h1:g9SXiSWY0JJh4LS39/Q0GxzP/QX2cVcbTOYhDpXrJEs= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= From c55c8f6b16d3106a0ddb11ae6e06c7dbf07c858a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 08:42:27 +0000 Subject: [PATCH 0027/1670] deps(go): bump github.com/spf13/viper from 1.18.2 to 1.19.0 Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.18.2 to 1.19.0. - [Release notes](https://github.com/spf13/viper/releases) - [Commits](https://github.com/spf13/viper/compare/v1.18.2...v1.19.0) --- updated-dependencies: - dependency-name: github.com/spf13/viper dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index a51e1042d9..8e73dbf244 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/godbus/dbus/v5 v5.1.0 github.com/spf13/cobra v1.8.0 - github.com/spf13/viper v1.18.2 + github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 @@ -23,7 +23,7 @@ require ( github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml/v2 v2.1.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect diff --git a/go.sum b/go.sum index 6d4410ccf1..b19977a358 100644 --- a/go.sum +++ b/go.sum @@ -27,8 +27,8 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= -github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -51,11 +51,12 @@ github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= -github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/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.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= From 6890ad59ab6095c2f3a47683f5156bfc41a7de3b Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 9 Apr 2024 07:43:44 -0400 Subject: [PATCH 0028/1670] Implement missing methods on the service There were some missing dbus methods in brokerservice (and the NewSession method definition was also missing the input argument for the session mode) --- internal/brokerservice/brokerservice.go | 1 + internal/brokerservice/methods.go | 42 +++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/internal/brokerservice/brokerservice.go b/internal/brokerservice/brokerservice.go index 5f311ce8d2..a5651897fa 100644 --- a/internal/brokerservice/brokerservice.go +++ b/internal/brokerservice/brokerservice.go @@ -18,6 +18,7 @@ const intro = ` + diff --git a/internal/brokerservice/methods.go b/internal/brokerservice/methods.go index 15ecf55c8d..c236e592b9 100644 --- a/internal/brokerservice/methods.go +++ b/internal/brokerservice/methods.go @@ -4,6 +4,33 @@ import ( "github.com/godbus/dbus/v5" ) +// NewSession is the method through which the broker and the daemon will communicate once dbusInterface.NewSession is called. +func (s *Service) NewSession(username, lang, mode string) (sessionID, encryptionKey string, dbusErr *dbus.Error) { + sessionID, encryptionKey, err := s.broker.NewSession(username, lang, mode) + if err != nil { + return "", "", dbus.MakeFailedError(err) + } + return sessionID, encryptionKey, nil +} + +// GetAuthenticationModes is the method through which the broker and the daemon will communicate once dbusInterface.GetAuthenticationModes is called. +func (s *Service) GetAuthenticationModes(sessionID string, supportedUILayouts []map[string]string) (authenticationModes []map[string]string, dbusErr *dbus.Error) { + authenticationModes, err := s.broker.GetAuthenticationModes(sessionID, supportedUILayouts) + if err != nil { + return nil, dbus.MakeFailedError(err) + } + return authenticationModes, nil +} + +// SelectAuthenticationMode is the method through which the broker and the daemon will communicate once dbusInterface.SelectAuthenticationMode is called. +func (s *Service) SelectAuthenticationMode(sessionID, authenticationModeName string) (uiLayoutInfo map[string]string, dbusErr *dbus.Error) { + uiLayoutInfo, err := s.broker.SelectAuthenticationMode(sessionID, authenticationModeName) + if err != nil { + return nil, dbus.MakeFailedError(err) + } + return uiLayoutInfo, nil +} + // IsAuthenticated is the method through which the broker and the daemon will communicate once dbusInterface.IsAuthenticated is called. func (s *Service) IsAuthenticated(sessionID, authenticationData string) (access, data string, dbusErr *dbus.Error) { access, data, err := s.broker.IsAuthenticated(sessionID, authenticationData) @@ -12,3 +39,18 @@ func (s *Service) IsAuthenticated(sessionID, authenticationData string) (access, } return access, data, nil } + +// EndSession is the method through which the broker and the daemon will communicate once dbusInterface.EndSession is called. +func (s *Service) EndSession(sessionID string) (dbusErr *dbus.Error) { + err := s.broker.EndSession(sessionID) + if err != nil { + return dbus.MakeFailedError(err) + } + return nil +} + +// CancelIsAuthenticated is the method through which the broker and the daemon will communicate once dbusInterface.CancelIsAuthenticated is called. +func (s *Service) CancelIsAuthenticated(sessionID string) (dbusErr *dbus.Error) { + s.broker.CancelIsAuthenticated(sessionID) + return nil +} From 7087102aee0ce9b61b2d059fe416eb2e8ee7cbef Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 9 Apr 2024 07:45:13 -0400 Subject: [PATCH 0029/1670] Parse the config file when creating the broker We need to check the configuration file in order to gather required values for the broker creation. This is best done when creating the brokerservice. --- config/oidc-broker.broker | 11 +++++ internal/brokerservice/brokerservice.go | 57 ++++++++++++++++++++----- internal/brokerservice/consts.go | 22 ++++++++++ 3 files changed, 80 insertions(+), 10 deletions(-) create mode 100644 internal/brokerservice/consts.go diff --git a/config/oidc-broker.broker b/config/oidc-broker.broker index d372a35b25..d811e543d8 100644 --- a/config/oidc-broker.broker +++ b/config/oidc-broker.broker @@ -3,3 +3,14 @@ name = OIDC Broker brand_icon = broker_icon.png dbus_name = com.ubuntu.authd.oidc_broker dbus_object = /com/ubuntu/authd/oidc_broker + +[oidc] +issuer = https://{issuer_url} +client_id = client_id + +# The amount of days the user will be allowed to authenticate without a network connection. +offline_expiration = 180 + +# The directory where the user's home directory will be created. +# The user home directory will be created in the format of $homedir_path/$username +home_base_dir = /home diff --git a/internal/brokerservice/brokerservice.go b/internal/brokerservice/brokerservice.go index a5651897fa..54e81a9261 100644 --- a/internal/brokerservice/brokerservice.go +++ b/internal/brokerservice/brokerservice.go @@ -3,13 +3,13 @@ package brokerservice import ( "context" + "errors" "fmt" - "strings" "github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5/introspect" - "github.com/ubuntu/decorate" "github.com/ubuntu/oidc-broker/internal/broker" + "gopkg.in/ini.v1" ) const intro = ` @@ -49,24 +49,44 @@ const intro = ` // Service is the handler exposing our broker methods on the system bus. type Service struct { name string - broker broker.Broker + broker *broker.Broker serve chan struct{} disconnect func() } // New returns a new dbus service after exporting to the system bus our name. -func New(_ context.Context, serviceName string) (s *Service, err error) { - serviceName = strings.ReplaceAll(serviceName, "-", "_") - defer decorate.OnError(&err, "cannot create dbus service %q", serviceName) +func New(_ context.Context, cfgPath, cachePath string) (s *Service, err error) { + cfg, err := parseConfig(cfgPath) + if err != nil { + return nil, err + } - name := fmt.Sprintf("com.ubuntu.oidc.%s", serviceName) iface := "com.ubuntu.authd.Broker" - object := dbus.ObjectPath(fmt.Sprintf("/com/ubuntu/oidc/%s", serviceName)) + name := cfg[authdSection][dbusNameKey] + object := dbus.ObjectPath(cfg[authdSection][dbusObjectKey]) + if name == "" { + return nil, errors.New("missing required name for dbus service") + } + if object == "" { + return nil, errors.New("missing required object path for dbus service") + } + + bCfg := broker.Config{ + IssuerURL: cfg[oidcSection][issuerKey], + ClientID: cfg[oidcSection][clientIDKey], + OfflineExpiration: cfg[oidcSection][offlineExpirationKey], + HomeBaseDir: cfg[oidcSection][homeDirKey], + CachePath: cachePath, + } + b, err := broker.New(bCfg) + if err != nil { + return nil, err + } s = &Service{ name: name, - broker: broker.Broker{}, + broker: b, serve: make(chan struct{}), } @@ -95,8 +115,25 @@ func New(_ context.Context, serviceName string) (s *Service, err error) { return s, nil } +// parseConfig parses the config file and returns a map with the configuration keys and values. +func parseConfig(cfgPath string) (map[string]map[string]string, error) { + iniCfg, err := ini.Load(cfgPath) + if err != nil { + return nil, err + } + + cfg := make(map[string]map[string]string) + for _, section := range iniCfg.Sections() { + cfg[section.Name()] = make(map[string]string) + for _, key := range section.Keys() { + cfg[section.Name()][key.Name()] = key.String() + } + } + return cfg, nil +} + // Addr returns the address of the service. -func (s Service) Addr() string { +func (s *Service) Addr() string { return s.name } diff --git a/internal/brokerservice/consts.go b/internal/brokerservice/consts.go new file mode 100644 index 0000000000..ad07fa0b6a --- /dev/null +++ b/internal/brokerservice/consts.go @@ -0,0 +1,22 @@ +package brokerservice + +// Configuration sections and keys. +const ( + // authdSection is the section name in the config file for the authentication daemon specific configuration. + authdSection = "authd" + // dbusNameKey is the key in the config file for the dbus name of the authentication daemon. + dbusNameKey = "dbus_name" + // dbusObjectKey is the key in the config file for the dbus object of the authentication daemon. + dbusObjectKey = "dbus_object" + + // oidcSection is the section name in the config file for the OIDC specific configuration. + oidcSection = "oidc" + // issuerKey is the key in the config file for the issuer. + issuerKey = "issuer" + // clientIDKey is the key in the config file for the client ID. + clientIDKey = "client_id" + // offlineExpirationKey is the key in the config file for the offline expiration. + offlineExpirationKey = "offline_expiration" + // homeDirKey is the key in the config file for the home directory prefix. + homeDirKey = "home_base_dir" +) From 66cbbc61e4290f3a5c7a570ef13cce9799ece189 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 4 Jun 2024 12:10:42 -0400 Subject: [PATCH 0030/1670] Rename brokerservice pkg to dbusservice --- cmd/oidc-broker/daemon/daemon.go | 4 ++-- internal/{brokerservice => dbusservice}/consts.go | 2 +- .../brokerservice.go => dbusservice/dbusservice.go} | 4 ++-- internal/{brokerservice => dbusservice}/localbus.go | 2 +- internal/{brokerservice => dbusservice}/methods.go | 2 +- internal/{brokerservice => dbusservice}/systembus.go | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) rename internal/{brokerservice => dbusservice}/consts.go (97%) rename internal/{brokerservice/brokerservice.go => dbusservice/dbusservice.go} (97%) rename internal/{brokerservice => dbusservice}/localbus.go (96%) rename internal/{brokerservice => dbusservice}/methods.go (99%) rename internal/{brokerservice => dbusservice}/systembus.go (93%) diff --git a/cmd/oidc-broker/daemon/daemon.go b/cmd/oidc-broker/daemon/daemon.go index 000dab16ee..46a6741d75 100644 --- a/cmd/oidc-broker/daemon/daemon.go +++ b/cmd/oidc-broker/daemon/daemon.go @@ -11,9 +11,9 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/ubuntu/oidc-broker/internal/brokerservice" "github.com/ubuntu/oidc-broker/internal/consts" "github.com/ubuntu/oidc-broker/internal/daemon" + "github.com/ubuntu/oidc-broker/internal/dbusservice" ) // App encapsulate commands and options of the daemon, which can be controlled by env variables and config files. @@ -105,7 +105,7 @@ func (a *App) serve(config daemonConfig) error { return fmt.Errorf("error initializing users cache directory at %q: %v", config.Paths.Cache, err) } - s, err := brokerservice.New(ctx, a.name) + s, err := dbusservice.New(ctx, config.Paths.BrokerConf, config.Paths.Cache) if err != nil { close(a.ready) return err diff --git a/internal/brokerservice/consts.go b/internal/dbusservice/consts.go similarity index 97% rename from internal/brokerservice/consts.go rename to internal/dbusservice/consts.go index ad07fa0b6a..d0629cf4fc 100644 --- a/internal/brokerservice/consts.go +++ b/internal/dbusservice/consts.go @@ -1,4 +1,4 @@ -package brokerservice +package dbusservice // Configuration sections and keys. const ( diff --git a/internal/brokerservice/brokerservice.go b/internal/dbusservice/dbusservice.go similarity index 97% rename from internal/brokerservice/brokerservice.go rename to internal/dbusservice/dbusservice.go index 54e81a9261..a714a69ab6 100644 --- a/internal/brokerservice/brokerservice.go +++ b/internal/dbusservice/dbusservice.go @@ -1,5 +1,5 @@ -// Package brokerservice is the dbus service implementation delegating its functional call to brokers. -package brokerservice +// Package dbusservice is the dbus service implementation delegating its functional call to brokers. +package dbusservice import ( "context" diff --git a/internal/brokerservice/localbus.go b/internal/dbusservice/localbus.go similarity index 96% rename from internal/brokerservice/localbus.go rename to internal/dbusservice/localbus.go index 97957ac363..e8ff09adad 100644 --- a/internal/brokerservice/localbus.go +++ b/internal/dbusservice/localbus.go @@ -1,6 +1,6 @@ //go:build withlocalbus -package brokerservice +package dbusservice import ( "fmt" diff --git a/internal/brokerservice/methods.go b/internal/dbusservice/methods.go similarity index 99% rename from internal/brokerservice/methods.go rename to internal/dbusservice/methods.go index c236e592b9..584c47ea40 100644 --- a/internal/brokerservice/methods.go +++ b/internal/dbusservice/methods.go @@ -1,4 +1,4 @@ -package brokerservice +package dbusservice import ( "github.com/godbus/dbus/v5" diff --git a/internal/brokerservice/systembus.go b/internal/dbusservice/systembus.go similarity index 93% rename from internal/brokerservice/systembus.go rename to internal/dbusservice/systembus.go index 4eaa276866..7ac7a6a0dd 100644 --- a/internal/brokerservice/systembus.go +++ b/internal/dbusservice/systembus.go @@ -1,6 +1,6 @@ //go:build !withlocalbus -package brokerservice +package dbusservice import ( "github.com/godbus/dbus/v5" From 8f69c27ff62d70f9e0457a8d6b75ea310a948942 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 9 Apr 2024 07:47:41 -0400 Subject: [PATCH 0031/1670] Add mock for an oidc provider --- internal/testutils/provider.go | 236 +++++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 internal/testutils/provider.go diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go new file mode 100644 index 0000000000..771fcfe537 --- /dev/null +++ b/internal/testutils/provider.go @@ -0,0 +1,236 @@ +package testutils + +import ( + "context" + "encoding/base64" + "errors" + "fmt" + "net/http" + "net/http/httptest" + "time" + + "github.com/coreos/go-oidc/v3/oidc" + "github.com/ubuntu/oidc-broker/internal/providers/group" + "golang.org/x/oauth2" +) + +// ProviderHandler is a function that handles a request to the mock provider. +type ProviderHandler func(http.ResponseWriter, *http.Request) + +type optionProvider struct { + handlers map[string]ProviderHandler +} + +// OptionProvider is a function that allows to override default options of the mock provider. +type OptionProvider func(*optionProvider) + +// WithHandler specifies a handler to the requested path in the mock provider. +func WithHandler(path string, handler func(http.ResponseWriter, *http.Request)) OptionProvider { + return func(o *optionProvider) { + o.handlers[path] = handler + } +} + +// StartMockProvider starts a new HTTP server to be used as an OpenID Connect provider for tests. +func StartMockProvider(args ...OptionProvider) (*httptest.Server, func()) { + servMux := http.NewServeMux() + server := httptest.NewServer(servMux) + + opts := optionProvider{ + handlers: map[string]ProviderHandler{ + "/.well-known/openid-configuration": DefaultOpenIDHandler(server.URL), + "/device_auth": DefaultDeviceAuthHandler(), + "/token": DefaultTokenHandler(server.URL), + }, + } + for _, arg := range args { + arg(&opts) + } + + for path, handler := range opts.handlers { + if handler == nil { + continue + } + servMux.HandleFunc(path, handler) + } + + return server, func() { + server.Close() + } +} + +// DefaultOpenIDHandler returns a handler that returns a default OpenID Connect configuration. +func DefaultOpenIDHandler(serverURL string) ProviderHandler { + return func(w http.ResponseWriter, _ *http.Request) { + wellKnown := fmt.Sprintf(`{ + "issuer": "%[1]s", + "authorization_endpoint": "%[1]s/auth", + "device_authorization_endpoint": "%[1]s/device_auth", + "token_endpoint": "%[1]s/token", + "jwks_uri": "%[1]s/keys", + "id_token_signing_alg_values_supported": ["RS256"] + }`, serverURL) + + w.Header().Add("Content-Type", "application/json") + _, err := w.Write([]byte(wellKnown)) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + } + } +} + +// DefaultDeviceAuthHandler returns a handler that returns a default device auth response. +func DefaultDeviceAuthHandler() ProviderHandler { + return func(w http.ResponseWriter, _ *http.Request) { + response := `{ + "device_code": "device_code", + "user_code": "user_code", + "verification_uri": "https://verification_uri.com" + }` + + w.Header().Add("Content-Type", "application/json") + _, err := w.Write([]byte(response)) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + } + } +} + +// DefaultTokenHandler returns a handler that returns a default token response. +func DefaultTokenHandler(serverURL string) ProviderHandler { + return func(w http.ResponseWriter, r *http.Request) { + // Mimics user going through auth process + time.Sleep(3 * time.Second) + + idToken := fmt.Sprintf(`{ + "iss": "%s", + "sub": "test-user-id", + "aud": "test-client-id", + "exp": 9999999999, + "name": "test-user", + "preferred_username": "User Test", + "email": "test-user@email.com", + "email_verified": true + }`, serverURL) + + // The token must be JWT formatted, even though we ignore the validation in the broker during the tests. + rawToken := fmt.Sprintf(".%s.", base64.RawURLEncoding.EncodeToString([]byte(idToken))) + + response := fmt.Sprintf(`{ + "access_token": "accesstoken", + "refresh_token": "refreshtoken", + "token_type": "Bearer", + "scope": "offline_access openid profile", + "expires_in": 3600, + "id_token": "%s" + }`, rawToken) + + w.Header().Add("Content-Type", "application/json") + _, err := w.Write([]byte(response)) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + } + } +} + +// UnavailableHandler returns a handler that returns a 503 Service Unavailable response. +func UnavailableHandler() ProviderHandler { + return func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + } +} + +// BadRequestHandler returns a handler that returns a 400 Bad Request response. +func BadRequestHandler() ProviderHandler { + return func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusBadRequest) + } +} + +// CustomResponseHandler returns a handler that returns a custom token response. +func CustomResponseHandler(response string) ProviderHandler { + return func(w http.ResponseWriter, _ *http.Request) { + w.Header().Add("Content-Type", "application/json") + _, err := w.Write([]byte(response)) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + } + } +} + +// HangingHandler returns a handler that hangs the request until the context is done. +func HangingHandler(ctx context.Context) ProviderHandler { + return func(w http.ResponseWriter, r *http.Request) { + <-ctx.Done() + + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusRequestTimeout) + } +} + +// MockProviderInfoer is a mock that implements the ProviderInfoer interface. +type MockProviderInfoer struct { + Scopes []string + Options []oauth2.AuthCodeOption + Groups []group.Info + GroupsErr bool +} + +// AdditionalScopes returns the additional scopes required by the provider. +func (p *MockProviderInfoer) AdditionalScopes() []string { + if p.Scopes != nil { + return p.Scopes + } + return []string{oidc.ScopeOfflineAccess} +} + +// AuthOptions returns the additional options required by the provider. +func (p *MockProviderInfoer) AuthOptions() []oauth2.AuthCodeOption { + if p.Options != nil { + return p.Options + } + return []oauth2.AuthCodeOption{} +} + +// GetGroups returns the groups the user is a member of. +func (p *MockProviderInfoer) GetGroups(*oauth2.Token) ([]group.Info, error) { + if p.GroupsErr { + return nil, errors.New("error requested in the mock") + } + if p.Groups != nil { + return p.Groups, nil + } + return nil, nil +} + +// CurrentAuthenticationModesOffered returns the authentication modes supported by the provider. +func (p *MockProviderInfoer) CurrentAuthenticationModesOffered(sessionMode string, supportedAuthModes map[string]string, tokenExists bool, currentAuthStep int) ([]string, error) { + var offeredModes []string + switch sessionMode { + case "passwd": + if !tokenExists { + return nil, errors.New("user has no cached token") + } + offeredModes = []string{"password"} + if currentAuthStep > 0 { + offeredModes = []string{"newpassword"} + } + + default: // auth mode + offeredModes = []string{"qrcode"} + if tokenExists { + offeredModes = []string{"password", "qrcode"} + } + if currentAuthStep > 0 { + offeredModes = []string{"newpassword"} + } + } + + for _, mode := range offeredModes { + if _, ok := supportedAuthModes[mode]; !ok { + return nil, fmt.Errorf("auth mode %q required by the provider, but is not supported locally", mode) + } + } + + return offeredModes, nil +} From a615bf33c208b13d3c19d2d632d5e09ba99d54f1 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 9 Apr 2024 08:20:46 -0400 Subject: [PATCH 0032/1670] Update cmd tests with brokerservice changes --- cmd/oidc-broker/daemon/daemon_test.go | 30 +++++++++++++++++---------- cmd/oidc-broker/daemon/export_test.go | 26 ++++++++++++++++++++--- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/cmd/oidc-broker/daemon/daemon_test.go b/cmd/oidc-broker/daemon/daemon_test.go index 86a0628d39..a5b8b06424 100644 --- a/cmd/oidc-broker/daemon/daemon_test.go +++ b/cmd/oidc-broker/daemon/daemon_test.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "io" + "net/http/httptest" "os" "path/filepath" "strings" @@ -17,8 +18,10 @@ import ( "github.com/ubuntu/oidc-broker/internal/testutils" ) +var mockProvider *httptest.Server + func TestHelp(t *testing.T) { - a := daemon.NewForTests(t, nil, "--help") + a := daemon.NewForTests(t, nil, mockProvider.URL, "--help") getStdout := captureStdout(t) @@ -27,7 +30,7 @@ func TestHelp(t *testing.T) { } func TestCompletion(t *testing.T) { - a := daemon.NewForTests(t, nil, "completion", "bash") + a := daemon.NewForTests(t, nil, mockProvider.URL, "completion", "bash") getStdout := captureStdout(t) @@ -36,7 +39,7 @@ func TestCompletion(t *testing.T) { } func TestVersion(t *testing.T) { - a := daemon.NewForTests(t, nil, "version") + a := daemon.NewForTests(t, nil, mockProvider.URL, "version") getStdout := captureStdout(t) @@ -53,7 +56,7 @@ func TestVersion(t *testing.T) { } func TestNoUsageError(t *testing.T) { - a := daemon.NewForTests(t, nil, "completion", "bash") + a := daemon.NewForTests(t, nil, mockProvider.URL, "completion", "bash") getStdout := captureStdout(t) err := a.Run() @@ -66,7 +69,7 @@ func TestNoUsageError(t *testing.T) { func TestUsageError(t *testing.T) { t.Parallel() - a := daemon.NewForTests(t, nil, "doesnotexist") + a := daemon.NewForTests(t, nil, mockProvider.URL, "doesnotexist") err := a.Run() require.Error(t, err, "Run should return an error, stdout: %v") @@ -99,7 +102,7 @@ func TestAppCanQuitWithoutExecute(t *testing.T) { t.Parallel() - a := daemon.NewForTests(t, nil) + a := daemon.NewForTests(t, nil, mockProvider.URL) requireGoroutineStarted(t, a.Quit) err := a.Run() @@ -150,7 +153,7 @@ func TestAppRunFailsOnComponentsCreationAndQuit(t *testing.T) { }, } - a := daemon.NewForTests(t, &config) + a := daemon.NewForTests(t, &config, mockProvider.URL) err := a.Run() require.Error(t, err, "Run should return an error") }) @@ -206,7 +209,7 @@ func TestAppCanSigHupWithoutExecute(t *testing.T) { r, w, err := os.Pipe() require.NoError(t, err, "Setup: pipe shouldn't fail") - a := daemon.NewForTests(t, nil) + a := daemon.NewForTests(t, nil, mockProvider.URL) orig := os.Stdout os.Stdout = w @@ -225,7 +228,7 @@ func TestAppCanSigHupWithoutExecute(t *testing.T) { func TestAppGetRootCmd(t *testing.T) { t.Parallel() - a := daemon.NewForTests(t, nil) + a := daemon.NewForTests(t, nil, mockProvider.URL) require.NotNil(t, a.RootCmd(), "Returns root command") } @@ -258,7 +261,7 @@ func TestAutoDetectConfig(t *testing.T) { }, } - configPath := daemon.GenerateTestConfig(t, &config) + configPath := daemon.GenerateTestConfig(t, &config, mockProvider.URL) configNextToBinaryPath := filepath.Join(filepath.Dir(os.Args[0]), t.Name()+".yaml") err := os.Rename(configPath, configNextToBinaryPath) require.NoError(t, err, "Could not relocate authd configuration file in the binary directory") @@ -324,7 +327,7 @@ func requireGoroutineStarted(t *testing.T, f func()) { func startDaemon(t *testing.T, conf *daemon.DaemonConfig) (app *daemon.App, done func()) { t.Helper() - a := daemon.NewForTests(t, conf) + a := daemon.NewForTests(t, conf, mockProvider.URL) wg := sync.WaitGroup{} wg.Add(1) @@ -382,5 +385,10 @@ func TestMain(m *testing.M) { } defer cleanup() + // Start provider mock + providerServer, cleanup := testutils.StartMockProvider() + defer cleanup() + mockProvider = providerServer + m.Run() } diff --git a/cmd/oidc-broker/daemon/export_test.go b/cmd/oidc-broker/daemon/export_test.go index 27abc1abcd..2f33bbf1fb 100644 --- a/cmd/oidc-broker/daemon/export_test.go +++ b/cmd/oidc-broker/daemon/export_test.go @@ -1,8 +1,10 @@ package daemon import ( + "fmt" "os" "path/filepath" + "strings" "testing" "github.com/stretchr/testify/require" @@ -14,10 +16,10 @@ type ( SystemPaths = systemPaths ) -func NewForTests(t *testing.T, conf *DaemonConfig, args ...string) *App { +func NewForTests(t *testing.T, conf *DaemonConfig, providerURL string, args ...string) *App { t.Helper() - p := GenerateTestConfig(t, conf) + p := GenerateTestConfig(t, conf, providerURL) argsWithConf := []string{"--config", p} argsWithConf = append(argsWithConf, args...) @@ -26,7 +28,7 @@ func NewForTests(t *testing.T, conf *DaemonConfig, args ...string) *App { return a } -func GenerateTestConfig(t *testing.T, origConf *daemonConfig) string { +func GenerateTestConfig(t *testing.T, origConf *daemonConfig, providerURL string) string { t.Helper() var conf daemonConfig @@ -44,6 +46,24 @@ func GenerateTestConfig(t *testing.T, origConf *daemonConfig) string { err := os.Chmod(conf.Paths.Cache, 0700) require.NoError(t, err, "Setup: could not change permission on cache directory for tests") } + if conf.Paths.BrokerConf == "" { + conf.Paths.BrokerConf = filepath.Join(t.TempDir(), strings.ReplaceAll(t.Name(), "/", "_")+".yaml") + } + + brokerCfg := fmt.Sprintf(` +[authd] +name = %[1]s +brand_icon = broker_icon.png +dbus_name = com.ubuntu.authd.%[1]s +dbus_object = /com/ubuntu/authd/%[1]s + +[oidc] +issuer = %[2]s +client_id = client_id +`, strings.ReplaceAll(t.Name(), "/", "_"), providerURL) + err := os.WriteFile(conf.Paths.BrokerConf, []byte(brokerCfg), 0600) + require.NoError(t, err, "Setup: could not create broker configuration for tests") + d, err := yaml.Marshal(conf) require.NoError(t, err, "Setup: could not marshal configuration for tests") From 1d3f4c1c4a25b7bfebb2d689837e6bf9b9e8d9d1 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 9 Apr 2024 08:24:11 -0400 Subject: [PATCH 0033/1670] Refactor provider logic used by the broker Instead of having the providers handled by the internal/broker package, they now have their own separate package to manage them and their respective functionalities. This helps a lot when mocking them for the tests and also make the code scale better. --- internal/broker/microsoft-entra-id.go | 11 ---- internal/broker/noprovider.go | 11 ---- internal/providers/default.go | 12 ++++ internal/providers/group/info.go | 8 +++ internal/providers/noprovider/noprovider.go | 61 +++++++++++++++++++++ internal/providers/providers.go | 15 +++++ 6 files changed, 96 insertions(+), 22 deletions(-) delete mode 100644 internal/broker/microsoft-entra-id.go delete mode 100644 internal/broker/noprovider.go create mode 100644 internal/providers/default.go create mode 100644 internal/providers/group/info.go create mode 100644 internal/providers/noprovider/noprovider.go create mode 100644 internal/providers/providers.go diff --git a/internal/broker/microsoft-entra-id.go b/internal/broker/microsoft-entra-id.go deleted file mode 100644 index 676a97d6d6..0000000000 --- a/internal/broker/microsoft-entra-id.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build withmsentraid - -// This is the Microsoft Entra ID specific extension. - -package broker - -import "fmt" - -func getGroups() { - fmt.Println("Microsoft Entra ID getGroups") -} diff --git a/internal/broker/noprovider.go b/internal/broker/noprovider.go deleted file mode 100644 index 76ac0dcc42..0000000000 --- a/internal/broker/noprovider.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build !withmsentraid - -// This is the generic oidc extension. It’s a no-op for most of the calls. - -package broker - -import "fmt" - -func getGroups() { - fmt.Println("No provider getGroups") -} diff --git a/internal/providers/default.go b/internal/providers/default.go new file mode 100644 index 0000000000..f16f8f1438 --- /dev/null +++ b/internal/providers/default.go @@ -0,0 +1,12 @@ +//go:build !withmsentraid + +package providers + +import ( + "github.com/ubuntu/oidc-broker/internal/providers/noprovider" +) + +// CurrentProviderInfo returns a generic oidc provider implementation. +func CurrentProviderInfo() ProviderInfoer { + return noprovider.NoProvider{} +} diff --git a/internal/providers/group/info.go b/internal/providers/group/info.go new file mode 100644 index 0000000000..f6673d6fc9 --- /dev/null +++ b/internal/providers/group/info.go @@ -0,0 +1,8 @@ +// Package group defines group-related types used by the broker. +package group + +// Info represents the group information that is fetched by the broker. +type Info struct { + Name string + UGID string +} diff --git a/internal/providers/noprovider/noprovider.go b/internal/providers/noprovider/noprovider.go new file mode 100644 index 0000000000..aefe7873d8 --- /dev/null +++ b/internal/providers/noprovider/noprovider.go @@ -0,0 +1,61 @@ +// Package noprovider is the generic oidc extension. +package noprovider + +import ( + "errors" + "fmt" + + "github.com/coreos/go-oidc/v3/oidc" + "github.com/ubuntu/oidc-broker/internal/providers/group" + "golang.org/x/oauth2" +) + +// NoProvider is a generic OIDC provider. +type NoProvider struct{} + +// AdditionalScopes returns the generic scopes required by the provider. +func (p NoProvider) AdditionalScopes() []string { + return []string{oidc.ScopeOfflineAccess} +} + +// AuthOptions is a no-op when no specific provider is in use. +func (p NoProvider) AuthOptions() []oauth2.AuthCodeOption { + return []oauth2.AuthCodeOption{} +} + +// GetGroups is a no-op when no specific provider is in use. +func (p NoProvider) GetGroups(_ *oauth2.Token) ([]group.Info, error) { + return nil, nil +} + +// CurrentAuthenticationModesOffered returns the generic authentication modes supported by the provider. +func (p NoProvider) CurrentAuthenticationModesOffered(sessionMode string, supportedAuthModes map[string]string, tokenExists bool, currentAuthStep int) ([]string, error) { + var offeredModes []string + switch sessionMode { + case "passwd": + if !tokenExists { + return nil, errors.New("user has no cached token") + } + offeredModes = []string{"password"} + if currentAuthStep > 0 { + offeredModes = []string{"newpassword"} + } + + default: // auth mode + offeredModes = []string{"qrcode"} + if tokenExists { + offeredModes = []string{"password", "qrcode"} + } + if currentAuthStep > 0 { + offeredModes = []string{"newpassword"} + } + } + + for _, mode := range offeredModes { + if _, ok := supportedAuthModes[mode]; !ok { + return nil, fmt.Errorf("auth mode %q required by the provider, but is not supported locally", mode) + } + } + + return offeredModes, nil +} diff --git a/internal/providers/providers.go b/internal/providers/providers.go new file mode 100644 index 0000000000..25f6d356a9 --- /dev/null +++ b/internal/providers/providers.go @@ -0,0 +1,15 @@ +// Package providers define provider-specific configurations and functions to be used by the OIDC broker. +package providers + +import ( + "github.com/ubuntu/oidc-broker/internal/providers/group" + "golang.org/x/oauth2" +) + +// ProviderInfoer defines provider-specific methods to be used by the broker. +type ProviderInfoer interface { + AdditionalScopes() []string + AuthOptions() []oauth2.AuthCodeOption + CurrentAuthenticationModesOffered(sessionMode string, supportedAuthModes map[string]string, tokenExists bool, currentAuthStep int) ([]string, error) + GetGroups(*oauth2.Token) ([]group.Info, error) +} From cad35ac813857f8723a2414f8c2100384b3d92ed Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 9 Apr 2024 08:26:20 -0400 Subject: [PATCH 0034/1670] Implement EntraID user groups logic --- .../microsoft_entra_id/microsoft-entra-id.go | 137 ++++++++++++++++++ internal/providers/withmsentraid.go | 12 ++ 2 files changed, 149 insertions(+) create mode 100644 internal/providers/microsoft_entra_id/microsoft-entra-id.go create mode 100644 internal/providers/withmsentraid.go diff --git a/internal/providers/microsoft_entra_id/microsoft-entra-id.go b/internal/providers/microsoft_entra_id/microsoft-entra-id.go new file mode 100644 index 0000000000..e550c29c69 --- /dev/null +++ b/internal/providers/microsoft_entra_id/microsoft-entra-id.go @@ -0,0 +1,137 @@ +//go:build withmsentraid + +// Package microsoft_entra_id is the Microsoft Entra ID specific extension. +package microsoft_entra_id + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/coreos/go-oidc/v3/oidc" + msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go" + msauth "github.com/microsoftgraph/msgraph-sdk-go-core/authentication" + "github.com/ubuntu/oidc-broker/internal/providers/group" + "golang.org/x/oauth2" +) + +const localGroupPrefix = "linux-" + +// MSEntraIDProvider is the Microsoft Entra ID provider implementation. +type MSEntraIDProvider struct{} + +// AdditionalScopes returns the generic scopes required by the EntraID provider. +func (p MSEntraIDProvider) AdditionalScopes() []string { + return []string{oidc.ScopeOfflineAccess} +} + +// AuthOptions returns the generic auth options required by the EntraID provider. +func (p MSEntraIDProvider) AuthOptions() []oauth2.AuthCodeOption { + return []oauth2.AuthCodeOption{} +} + +// GetGroups access the Microsoft Graph API to get the groups the user is a member of. +func (p MSEntraIDProvider) GetGroups(token *oauth2.Token) ([]group.Info, error) { + cred := azureTokenCredential{token: token} + auth, err := msauth.NewAzureIdentityAuthenticationProvider(cred) + if err != nil { + return nil, err + } + + adapter, err := msgraphsdk.NewGraphRequestAdapter(auth) + if err != nil { + return nil, err + } + + client := msgraphsdk.NewGraphServiceClient(adapter) + + m, err := client.Me().MemberOf().Get(context.Background(), nil) + if err != nil { + return nil, err + } + + var ok bool + var name, id *string + var groups []group.Info + for _, obj := range m.GetValue() { + v, err := obj.GetBackingStore().Get("displayName") + if err != nil { + return nil, err + } + name, ok = v.(*string) + if !ok || name == nil { + return nil, errors.New("could not parse group name") + } + + groupName := strings.ToLower(*name) + + // Local group + if strings.HasPrefix(groupName, localGroupPrefix) { + groupName = strings.TrimPrefix(groupName, localGroupPrefix) + groups = append(groups, group.Info{Name: groupName}) + continue + } + + v, err = obj.GetBackingStore().Get("id") + if err != nil { + return nil, err + } + id, ok = v.(*string) + if !ok || id == nil { + return nil, errors.New("could not parse group id") + } + + groups = append(groups, group.Info{Name: groupName, UGID: *id}) + } + + return groups, nil +} + +// CurrentAuthenticationModesOffered returns the generic authentication modes supported by the provider. +// +// Token validity is not considered, only the presence of a token. +func (p MSEntraIDProvider) CurrentAuthenticationModesOffered(sessionMode string, supportedAuthModes map[string]string, tokenExists bool, currentAuthStep int) ([]string, error) { + var offeredModes []string + switch sessionMode { + case "passwd": + if !tokenExists { + return nil, errors.New("user has no cached token") + } + offeredModes = []string{"password"} + if currentAuthStep > 0 { + offeredModes = []string{"newpassword"} + } + + default: // auth mode + offeredModes = []string{"qrcode"} + if tokenExists { + offeredModes = []string{"password", "qrcode"} + } + if currentAuthStep > 0 { + offeredModes = []string{"newpassword"} + } + } + + for _, mode := range offeredModes { + if _, ok := supportedAuthModes[mode]; !ok { + return nil, fmt.Errorf("auth mode %q required by the provider, but is not supported locally", mode) + } + } + + return offeredModes, nil +} + +type azureTokenCredential struct { + token *oauth2.Token +} + +// GetToken creates an azcore.AccessToken from an oauth2.Token. +func (c azureTokenCredential) GetToken(_ context.Context, _ policy.TokenRequestOptions) (azcore.AccessToken, error) { + return azcore.AccessToken{ + Token: c.token.AccessToken, + ExpiresOn: c.token.Expiry, + }, nil +} diff --git a/internal/providers/withmsentraid.go b/internal/providers/withmsentraid.go new file mode 100644 index 0000000000..cbc1619dd4 --- /dev/null +++ b/internal/providers/withmsentraid.go @@ -0,0 +1,12 @@ +//go:build withmsentraid + +package providers + +import ( + "github.com/ubuntu/oidc-broker/internal/providers/microsoft_entra_id" +) + +// CurrentProviderInfo returns a Microsoft Entra ID provider implementation. +func CurrentProviderInfo() ProviderInfoer { + return microsoft_entra_id.MSEntraIDProvider{} +} From cdfa7bc29ed91356f9e194966c5973a7287a7a2e Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 9 Apr 2024 08:27:34 -0400 Subject: [PATCH 0035/1670] Add already-known test utilities Common test utilities such as LoadAndUpdateFromGolden --- internal/testutils/golden.go | 263 +++++++++++++++++++++++++++++++++++ internal/testutils/path.go | 37 +++++ 2 files changed, 300 insertions(+) create mode 100644 internal/testutils/golden.go create mode 100644 internal/testutils/path.go diff --git a/internal/testutils/golden.go b/internal/testutils/golden.go new file mode 100644 index 0000000000..78ea94dcb2 --- /dev/null +++ b/internal/testutils/golden.go @@ -0,0 +1,263 @@ +package testutils + +import ( + "bytes" + "errors" + "flag" + "io/fs" + "os" + "path/filepath" + "strings" + "testing" + + cp "github.com/otiai10/copy" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +var update bool + +type goldenOptions struct { + goldenPath string +} + +// GoldenOption is a supported option reference to change the golden files comparison. +type GoldenOption func(*goldenOptions) + +// WithGoldenPath overrides the default path for golden files used. +func WithGoldenPath(path string) GoldenOption { + return func(o *goldenOptions) { + if path != "" { + o.goldenPath = path + } + } +} + +// LoadWithUpdateFromGolden loads the element from a plaintext golden file. +// It will update the file if the update flag is used prior to loading it. +func LoadWithUpdateFromGolden(t *testing.T, data string, opts ...GoldenOption) string { + t.Helper() + + o := goldenOptions{ + goldenPath: GoldenPath(t), + } + + for _, opt := range opts { + opt(&o) + } + + if update { + t.Logf("updating golden file %s", o.goldenPath) + err := os.MkdirAll(filepath.Dir(o.goldenPath), 0750) + require.NoError(t, err, "Cannot create directory for updating golden files") + err = os.WriteFile(o.goldenPath, []byte(data), 0600) + require.NoError(t, err, "Cannot write golden file") + } + + want, err := os.ReadFile(o.goldenPath) + require.NoError(t, err, "Cannot load golden file") + + return string(want) +} + +// LoadWithUpdateFromGoldenYAML load the generic element from a YAML serialized golden file. +// It will update the file if the update flag is used prior to deserializing it. +func LoadWithUpdateFromGoldenYAML[E any](t *testing.T, got E, opts ...GoldenOption) E { + t.Helper() + + t.Logf("Serializing object for golden file") + data, err := yaml.Marshal(got) + require.NoError(t, err, "Cannot serialize provided object") + want := LoadWithUpdateFromGolden(t, string(data), opts...) + + var wantDeserialized E + err = yaml.Unmarshal([]byte(want), &wantDeserialized) + require.NoError(t, err, "Cannot create expanded policy objects from golden file") + + return wantDeserialized +} + +// NormalizeGoldenName returns the name of the golden file with illegal Windows +// characters replaced or removed. +func NormalizeGoldenName(t *testing.T, name string) string { + t.Helper() + + name = strings.ReplaceAll(name, `\`, "_") + name = strings.ReplaceAll(name, ":", "") + name = strings.ToLower(name) + return name +} + +// TestFamilyPath returns the path of the dir for storing fixtures and other files related to the test. +func TestFamilyPath(t *testing.T) string { + t.Helper() + + // Ensures that only the name of the parent test is used. + super, _, _ := strings.Cut(t.Name(), "/") + + return filepath.Join("testdata", super) +} + +// GoldenPath returns the golden path for the provided test. +func GoldenPath(t *testing.T) string { + t.Helper() + + path := filepath.Join(TestFamilyPath(t), "golden") + _, sub, found := strings.Cut(t.Name(), "/") + if found { + path = filepath.Join(path, NormalizeGoldenName(t, sub)) + } + + return path +} + +// CompareTreesWithFiltering allows comparing a goldPath directory to p. Those can be updated via the dedicated flag. +// It will filter dconf database and not commit it in the new golden directory. +func CompareTreesWithFiltering(t *testing.T, p, goldPath string, update bool) { + t.Helper() + + // Update golden file + if update { + t.Logf("updating golden file %s", goldPath) + require.NoError(t, os.RemoveAll(goldPath), "Cannot remove target golden directory") + + // check the source directory exists before trying to copy it + info, err := os.Stat(p) + if errors.Is(err, fs.ErrNotExist) { + return + } + require.NoErrorf(t, err, "Error on checking %q", p) + + if !info.IsDir() { + // copy file + data, err := os.ReadFile(p) + require.NoError(t, err, "Cannot read new generated file file %s", p) + require.NoError(t, os.WriteFile(goldPath, data, info.Mode()), "Cannot write golden file") + } else { + err := addEmptyMarker(p) + require.NoError(t, err, "Cannot add empty marker to directory %s", p) + + err = cp.Copy(p, goldPath) + require.NoError(t, err, "Can’t update golden directory") + } + } + + var gotContent map[string]treeAttrs + if _, err := os.Stat(p); err == nil { + gotContent, err = treeContentAndAttrs(t, p, nil) + if err != nil { + t.Fatalf("No generated content: %v", err) + } + } + + var goldContent map[string]treeAttrs + if _, err := os.Stat(goldPath); err == nil { + goldContent, err = treeContentAndAttrs(t, goldPath, nil) + if err != nil { + t.Fatalf("No golden directory found: %v", err) + } + } + + // Maps are not ordered, so we need to compare the contents through keys + for key, value := range goldContent { + require.Equal(t, value, gotContent[key], "got and expected content differs") + delete(gotContent, key) + } + require.Empty(t, gotContent, "Some files are missing in the golden directory") + + // No more verification on p if it doesn’t exists + if _, err := os.Stat(p); errors.Is(err, fs.ErrNotExist) { + return + } +} + +// treeAttrs are the attributes to take into consideration when comparing each file. +type treeAttrs struct { + content string + path string + executable bool +} + +const fileForEmptyDir = ".empty" + +// treeContentAndAttrs builds a recursive file list of dir with their content and other attributes. +// It can ignore files starting with ignoreHeaders. +func treeContentAndAttrs(t *testing.T, dir string, ignoreHeaders []byte) (map[string]treeAttrs, error) { + t.Helper() + + r := make(map[string]treeAttrs) + + err := filepath.WalkDir(dir, func(path string, de fs.DirEntry, err error) error { + if err != nil { + return err + } + + // Ignore markers for empty directories + if filepath.Base(path) == fileForEmptyDir { + return nil + } + + content := "" + info, err := os.Stat(path) + require.NoError(t, err, "Cannot stat %s", path) + if !de.IsDir() { + d, err := os.ReadFile(path) + if err != nil { + return err + } + // ignore given header + if ignoreHeaders != nil && bytes.HasPrefix(d, ignoreHeaders) { + return nil + } + content = string(d) + } + trimmedPath := strings.TrimPrefix(path, dir) + r[trimmedPath] = treeAttrs{content, strings.TrimPrefix(path, dir), info.Mode()&0111 != 0} + return nil + }) + if err != nil { + return nil, err + } + + return r, nil +} + +// addEmptyMarker adds to any empty directory, fileForEmptyDir to it. +// That allows git to commit it. +func addEmptyMarker(p string) error { + err := filepath.WalkDir(p, func(path string, de fs.DirEntry, err error) error { + if err != nil { + return err + } + + if !de.IsDir() { + return nil + } + + entries, err := os.ReadDir(path) + if err != nil { + return err + } + if len(entries) == 0 { + f, err := os.Create(filepath.Join(path, fileForEmptyDir)) + if err != nil { + return err + } + f.Close() + } + return nil + }) + + return err +} + +// InstallUpdateFlag install an update flag referenced in this package. +// The flags need to be parsed before running the tests. +func InstallUpdateFlag() { + flag.BoolVar(&update, "update", false, "update golden files") +} + +// Update returns true if the update flag was set, false otherwise. +func Update() bool { + return update +} diff --git a/internal/testutils/path.go b/internal/testutils/path.go new file mode 100644 index 0000000000..d6d2cfd09c --- /dev/null +++ b/internal/testutils/path.go @@ -0,0 +1,37 @@ +package testutils + +import ( + "errors" + "io/fs" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +// MakeReadOnly makes dest read only and restore permission on cleanup. +func MakeReadOnly(t *testing.T, dest string) func() { + t.Helper() + + // Get current dest permissions + fi, err := os.Stat(dest) + require.NoError(t, err, "Cannot stat %s", dest) + mode := fi.Mode() + + var perms fs.FileMode = 0444 + if fi.IsDir() { + perms = 0555 + } + err = os.Chmod(dest, perms) + require.NoError(t, err) + + return func() { + _, err := os.Stat(dest) + if errors.Is(err, os.ErrNotExist) { + return + } + + err = os.Chmod(dest, mode) + require.NoError(t, err) + } +} From e2f9a1f184858d2e57d96cbefcaa3cfcd2b08e11 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 9 Apr 2024 08:29:24 -0400 Subject: [PATCH 0036/1670] Implementation of the OIDC broker --- internal/broker/broker.go | 730 ++++++++++++++++++++++++++++++++++++- internal/broker/consts.go | 18 + internal/broker/encrypt.go | 101 +++++ 3 files changed, 844 insertions(+), 5 deletions(-) create mode 100644 internal/broker/consts.go create mode 100644 internal/broker/encrypt.go diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 2f506c481c..2af589c48c 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -2,15 +2,735 @@ package broker import ( + "bytes" + "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/json" + "errors" "fmt" + "net/http" + "os" + "path/filepath" + "slices" + "strconv" + "strings" + "sync" + "text/template" + "time" + + "github.com/coreos/go-oidc/v3/oidc" + "github.com/google/uuid" + "github.com/ubuntu/decorate" + "github.com/ubuntu/oidc-broker/internal/providers" + "github.com/ubuntu/oidc-broker/internal/providers/group" + "golang.org/x/exp/slog" + "golang.org/x/oauth2" ) +const maxAuthAttempts = 3 + +// Config is the configuration for the broker. +type Config struct { + IssuerURL string + ClientID string + CachePath string + OfflineExpiration string + HomeBaseDir string +} + // Broker is the real implementation of the broker to track sessions and process oidc calls. -type Broker struct{} +type Broker struct { + providerInfo providers.ProviderInfoer + auth authConfig + offlineExpiration time.Duration + homeDirPath string + + currentSessions map[string]sessionInfo + currentSessionsMu sync.RWMutex + + privateKey *rsa.PrivateKey +} + +type authConfig struct { + cachePath string + + provider *oidc.Provider + providerURL string + + oidcCfg oidc.Config + oauthCfg oauth2.Config +} + +type sessionInfo struct { + username string + lang string + mode string + + selectedMode string + firstSelectedMode string + supportedModes map[string]string + attemptsPerMode map[string]int + + authInfo map[string]any + cachePath string + + currentAuthStep int + + isAuthenticating *isAuthenticatedCtx +} + +type isAuthenticatedCtx struct { + ctx context.Context + cancelFunc context.CancelFunc +} + +type option struct { + // skipJWTSignatureCheck is used to skip the JWT validation done by the oidc web server. + skipJWTSignatureCheck bool + providerInfo providers.ProviderInfoer +} + +// Option is a func that allows to override some of the broker default settings. +type Option func(*option) + +// New returns a new oidc Broker with the providers listed in the configuration file. +func New(cfg Config, args ...Option) (b *Broker, err error) { + defer decorate.OnError(&err, "could not create broker with provided issuer and client ID") + + opts := option{ + // This is to avoid too much complexity in the tests. + skipJWTSignatureCheck: false, + providerInfo: providers.CurrentProviderInfo(), + } + for _, arg := range args { + arg(&opts) + } + + if cfg.CachePath == "" { + return &Broker{}, errors.New("cache path must be provided") + } + + clientID := cfg.ClientID + issuerURL := cfg.IssuerURL + if issuerURL == "" || clientID == "" { + return &Broker{}, errors.New("issuer and client ID must be provided") + } + + // Set offline expiration + var offlineExpiration time.Duration + if cfg.OfflineExpiration != "" { + intValue, err := strconv.Atoi(cfg.OfflineExpiration) + if err != nil { + return &Broker{}, fmt.Errorf("could not parse offline expiration: %v", err) + } + offlineExpiration = time.Duration(intValue*24) * time.Hour + } + + homeDirPath := "/home" + if cfg.HomeBaseDir != "" { + homeDirPath = cfg.HomeBaseDir + } + + // Generate a new private key for the broker. + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + panic(fmt.Sprintf("could not create an valid rsa key: %v", err)) + } + + // Create provider + provider, err := oidc.NewProvider(context.TODO(), issuerURL) + if err != nil { + return &Broker{}, err + } + oidcCfg := oidc.Config{ + ClientID: clientID, + InsecureSkipSignatureCheck: opts.skipJWTSignatureCheck, + } + oauthCfg := oauth2.Config{ + ClientID: clientID, + Endpoint: provider.Endpoint(), + Scopes: append([]string{oidc.ScopeOpenID, "profile", "email"}, opts.providerInfo.AdditionalScopes()...), + } + authCfg := authConfig{ + provider: provider, + providerURL: issuerURL, + cachePath: cfg.CachePath, + oidcCfg: oidcCfg, + oauthCfg: oauthCfg, + } + + return &Broker{ + providerInfo: opts.providerInfo, + auth: authCfg, + offlineExpiration: offlineExpiration, + homeDirPath: homeDirPath, + privateKey: privateKey, + + currentSessions: make(map[string]sessionInfo), + currentSessionsMu: sync.RWMutex{}, + }, nil +} + +// NewSession creates a new session for the user. +func (b *Broker) NewSession(username, lang, mode string) (sessionID, encryptionKey string, err error) { + defer decorate.OnError(&err, "could not create new session for user %q", username) + + sessionID = uuid.New().String() + session := sessionInfo{ + username: username, + lang: lang, + mode: mode, + + authInfo: make(map[string]any), + attemptsPerMode: make(map[string]int), + } + + pubASN1, err := x509.MarshalPKIXPublicKey(&b.privateKey.PublicKey) + if err != nil { + return "", "", err + } + + _, url, _ := strings.Cut(b.auth.providerURL, "://") + url = strings.ReplaceAll(url, "/", "_") + url = strings.ReplaceAll(url, ":", "_") + session.cachePath = filepath.Join(b.auth.cachePath, url, username+".cache") + + b.currentSessionsMu.Lock() + b.currentSessions[sessionID] = session + b.currentSessionsMu.Unlock() + + return sessionID, base64.StdEncoding.EncodeToString(pubASN1), nil +} + +// GetAuthenticationModes returns the authentication modes available for the user. +func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []map[string]string) (authModes []map[string]string, err error) { + session, err := b.getSession(sessionID) + if err != nil { + return nil, err + } + + supportedAuthModes := b.supportedAuthModesFromLayout(supportedUILayouts) + + // Checks if the token exists in the cache. + _, err = os.Stat(session.cachePath) + tokenExists := err == nil + + availableModes, err := b.providerInfo.CurrentAuthenticationModesOffered( + session.mode, + supportedAuthModes, + tokenExists, + session.currentAuthStep) + if err != nil { + return nil, err + } + + for _, id := range availableModes { + label, ok := supportedAuthModes[id] + if !ok { + return nil, fmt.Errorf("required mode %q is not supported", id) + } + authModes = append(authModes, map[string]string{ + "id": id, + "label": label, + }) + } + + session.supportedModes = supportedAuthModes + if err := b.updateSession(sessionID, session); err != nil { + return nil, err + } + + return authModes, nil +} + +func (b *Broker) supportedAuthModesFromLayout(supportedUILayouts []map[string]string) (supportedModes map[string]string) { + supportedModes = make(map[string]string) + for _, layout := range supportedUILayouts { + supportedEntries := strings.Split(strings.TrimPrefix(layout["entry"], "optional:"), ",") + switch layout["type"] { + case "qrcode": + if !strings.Contains(layout["wait"], "true") { + continue + } + supportedModes["qrcode"] = "Device Authentication" + + case "form": + if slices.Contains(supportedEntries, "chars_password") { + supportedModes["password"] = "Password Authentication" + } + + case "newpassword": + if slices.Contains(supportedEntries, "chars_password") { + supportedModes["newpassword"] = "Define your password" + } + } + } + + return supportedModes +} + +// SelectAuthenticationMode selects the authentication mode for the user. +func (b *Broker) SelectAuthenticationMode(sessionID, authModeID string) (uiLayoutInfo map[string]string, err error) { + session, err := b.getSession(sessionID) + if err != nil { + return nil, err + } + + // populate UI options based on selected authentication mode + uiLayoutInfo, err = b.generateUILayout(&session, authModeID) + if err != nil { + return nil, err + } + + // Store selected mode + session.selectedMode = authModeID + // Store the first one to use to update the lastSelectedMode in MFA cases. + if session.currentAuthStep == 0 { + session.firstSelectedMode = authModeID + } + + if err = b.updateSession(sessionID, session); err != nil { + return nil, err + } + + return uiLayoutInfo, nil +} + +func (b *Broker) generateUILayout(session *sessionInfo, authModeID string) (map[string]string, error) { + if _, exists := session.supportedModes[authModeID]; !exists { + return nil, fmt.Errorf("selected authentication mode %q does not exist", authModeID) + } + + var uiLayout map[string]string + switch authModeID { + case "qrcode": + response, err := b.auth.oauthCfg.DeviceAuth(context.TODO()) + if err != nil { + slog.Error(fmt.Sprintf("Error getting device auth: %v", err)) + return nil, errors.New("could not generate QR code layout") + } + session.authInfo["response"] = response + + uiLayout = map[string]string{ + "type": "qrcode", + "label": fmt.Sprintf( + "Scan the QR code or access %q and use the code %q", + response.VerificationURI, + response.UserCode, + ), + "wait": "true", + "button": "regenerate QR code", + "content": response.VerificationURI, + } + + case "password": + uiLayout = map[string]string{ + "type": "form", + "label": "Gimme your password", + "entry": "chars_password", + } + + case "newpassword": + uiLayout = map[string]string{ + "type": "newpassword", + "label": "Enter your password", + "entry": "chars_password", + } + } + + return uiLayout, nil +} // IsAuthenticated evaluates the provided authenticationData and returns the authentication status for the user. -func (b *Broker) IsAuthenticated(sessionID, authenticationData string) (access, data string, err error) { - fmt.Println("IsAuthenticated generic OIDC code") - getGroups() - return "", "", nil +func (b *Broker) IsAuthenticated(sessionID, authenticationData string) (string, string, error) { + session, err := b.getSession(sessionID) + if err != nil { + return AuthDenied, "", err + } + + var authData map[string]string + if authenticationData != "" { + if err := json.Unmarshal([]byte(authenticationData), &authData); err != nil { + return AuthDenied, "", errors.New("authentication data is not a valid json value") + } + } + + ctx, err := b.startAuthenticate(sessionID) + if err != nil { + return AuthDenied, "", err + } + + // Cleans up the IsAuthenticated context when the call is done. + defer b.CancelIsAuthenticated(sessionID) + + authDone := make(chan struct{}) + var access, data string + go func() { + access, data = b.handleIsAuthenticated(ctx, &session, authData) + close(authDone) + }() + + select { + case <-authDone: + case <-ctx.Done(): + return AuthCancelled, `{"message": "authentication request cancelled"}`, ctx.Err() + } + + switch access { + case AuthRetry: + session.attemptsPerMode[session.selectedMode]++ + if session.attemptsPerMode[session.selectedMode] == maxAuthAttempts { + access = AuthDenied + data = `{"message": "maximum number of attempts reached"}` + } + + case AuthNext: + session.currentAuthStep++ + } + + if err = b.updateSession(sessionID, session); err != nil { + return AuthDenied, "", err + } + return access, data, nil +} + +func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo, authData map[string]string) (access, data string) { + // Decrypt challenge if present. + challenge, err := decodeRawChallenge(b.privateKey, authData["challenge"]) + if err != nil { + return AuthRetry, fmt.Sprintf(`{"message": "could not decode challenge: %v"}`, err) + } + + var authInfo authCachedInfo + var userClaims claims + var groups []group.Info + + offline := false + switch session.selectedMode { + case "qrcode": + response, ok := session.authInfo["response"].(*oauth2.DeviceAuthResponse) + if !ok { + return AuthDenied, `{"message": "could not get required response"}` + } + + t, err := b.auth.oauthCfg.DeviceAccessToken(ctx, response, b.providerInfo.AuthOptions()...) + if err != nil { + return AuthRetry, fmt.Sprintf(`{"message": "could not authenticate user: %v"}`, err) + } + + rawIDToken, ok := t.Extra("id_token").(string) + if !ok { + return AuthDenied, `{"message": "could not get id_token"}` + } + + session.authInfo["auth_info"] = authCachedInfo{Token: t, RawIDToken: rawIDToken} + return AuthNext, "" + + case "password": + authInfo, offline, err = b.loadAuthInfo(session, challenge) + if err != nil { + return AuthRetry, fmt.Sprintf(`{"message": "could not authenticate user: %v"}`, err) + } + + if session.mode == "passwd" { + session.authInfo["auth_info"] = authInfo + return AuthNext, "" + } + + case "newpassword": + var ok bool + // This mode must always come after a authentication mode, so it has to have an auth_info. + authInfo, ok = session.authInfo["auth_info"].(authCachedInfo) + if !ok { + return AuthDenied, `{"message": "could not get required information"}` + } + } + + if authInfo.UserInfo == "" { + userClaims, groups, err = b.fetchUserInfo(ctx, session, &authInfo) + if err != nil { + return AuthDenied, fmt.Sprintf(`{"message": "could not get user info: %v"}`, err) + } + + authInfo.UserInfo, err = b.userInfoFromClaims(userClaims, groups) + if err != nil { + return AuthDenied, fmt.Sprintf(`{"message": "could not get user info: %v"}`, err) + } + } + + if offline { + return AuthGranted, fmt.Sprintf(`{"userinfo": %s}`, authInfo.UserInfo) + } + + if err := b.cacheAuthInfo(session, authInfo, challenge); err != nil { + return AuthRetry, fmt.Sprintf(`{"message": "could not update token: %v"}`, err) + } + + return AuthGranted, fmt.Sprintf(`{"userinfo": %s}`, authInfo.UserInfo) +} + +func (b *Broker) startAuthenticate(sessionID string) (context.Context, error) { + session, err := b.getSession(sessionID) + if err != nil { + return nil, err + } + + if session.isAuthenticating != nil { + return nil, fmt.Errorf("IsAuthenticated already running for session %q", sessionID) + } + + ctx, cancel := context.WithCancel(context.Background()) + session.isAuthenticating = &isAuthenticatedCtx{ctx: ctx, cancelFunc: cancel} + + if err := b.updateSession(sessionID, session); err != nil { + cancel() + return nil, err + } + + return ctx, nil +} + +// EndSession ends the session for the user. +func (b *Broker) EndSession(sessionID string) error { + session, err := b.getSession(sessionID) + if err != nil { + return err + } + + // Checks if there is a isAuthenticated call running for this session and cancels it before ending the session. + if session.isAuthenticating != nil { + b.CancelIsAuthenticated(sessionID) + } + + b.currentSessionsMu.Lock() + defer b.currentSessionsMu.Unlock() + delete(b.currentSessions, sessionID) + return nil +} + +// CancelIsAuthenticated cancels the IsAuthenticated call for the user. +func (b *Broker) CancelIsAuthenticated(sessionID string) { + session, err := b.getSession(sessionID) + if err != nil { + return + } + + if session.isAuthenticating == nil { + return + } + + session.isAuthenticating.cancelFunc() + session.isAuthenticating = nil + + if err := b.updateSession(sessionID, session); err != nil { + slog.Error(fmt.Sprintf("Error when cancelling IsAuthenticated: %v", err)) + } +} + +// getSession returns the session information for the specified session ID or an error if the session is not active. +func (b *Broker) getSession(sessionID string) (sessionInfo, error) { + b.currentSessionsMu.RLock() + defer b.currentSessionsMu.RUnlock() + session, active := b.currentSessions[sessionID] + if !active { + return sessionInfo{}, fmt.Errorf("%s is not a current transaction", sessionID) + } + return session, nil +} + +// updateSession checks if the session is still active and updates the session info. +func (b *Broker) updateSession(sessionID string, session sessionInfo) error { + // Checks if the session was ended in the meantime, otherwise we would just accidentally recreate it. + if _, err := b.getSession(sessionID); err != nil { + return err + } + b.currentSessionsMu.Lock() + defer b.currentSessionsMu.Unlock() + b.currentSessions[sessionID] = session + return nil +} + +// authCachedInfo represents the token that will be saved on disk for offline authentication. +type authCachedInfo struct { + Token *oauth2.Token + AcquiredAt time.Time + RawIDToken string + UserInfo string +} + +// cacheAuthInfo serializes the access token and cache it. +func (b *Broker) cacheAuthInfo(session *sessionInfo, authInfo authCachedInfo, password string) (err error) { + defer func() { + // Override the error so that we don't leak information. Also, abstract it for the user. + // We still log as error for the admin to get access. + if err != nil { + slog.Error(fmt.Sprintf("Error when caching token: %v", err)) + err = errors.New("could not cache token") + } + }() + + authInfo.AcquiredAt = time.Now() + content, err := json.Marshal(authInfo) + if err != nil { + return fmt.Errorf("could not marshal token: %v", err) + } + + serialized, err := encrypt(content, []byte(password)) + if err != nil { + return fmt.Errorf("could not encrypt token: %v", err) + } + + // Create issuer specific cache directory if it doesn't exist. + if err = os.MkdirAll(filepath.Dir(session.cachePath), 0700); err != nil { + return fmt.Errorf("could not create token directory: %v", err) + } + + if err = os.WriteFile(session.cachePath, serialized, 0600); err != nil { + return fmt.Errorf("could not save token: %v", err) + } + + return nil +} + +// loadAuthInfo deserializes the token from the cache and refreshes it if needed. +func (b *Broker) loadAuthInfo(session *sessionInfo, password string) (loadedInfo authCachedInfo, offline bool, err error) { + defer func() { + // Override the error so that we don't leak information. Also, abstract it for the user. + // We still log as error for the admin to get access. + if err != nil { + slog.Error(fmt.Sprintf("Error when loading token: %v", err)) + err = errors.New("could not load token") + } + }() + + s, err := os.ReadFile(session.cachePath) + if err != nil { + return authCachedInfo{}, false, fmt.Errorf("could not read token: %v", err) + } + + deserialized, err := decrypt(s, []byte(password)) + if err != nil { + return authCachedInfo{}, false, fmt.Errorf("could not deserializing token: %v", err) + } + + var cachedInfo authCachedInfo + if err := json.Unmarshal(deserialized, &cachedInfo); err != nil { + return authCachedInfo{}, false, fmt.Errorf("could not unmarshaling token: %v", err) + } + + // If the token is not expired, we should use the cached information. + if cachedInfo.Token.Valid() { + return cachedInfo, true, nil + } + + // Tries to refresh the access token. If the service is unavailable and token is still valid from the broker + // perspective (i.e. last modification was between now and now-expiration), we can use it. + tok, err := b.auth.oauthCfg.TokenSource(context.Background(), cachedInfo.Token).Token() + if err != nil { + castErr := &oauth2.RetrieveError{} + if errors.As(err, &castErr) && castErr.Response.StatusCode != http.StatusServiceUnavailable { + return authCachedInfo{}, false, fmt.Errorf("could not refresh token: %v", err) + } + + if time.Since(cachedInfo.AcquiredAt) >= b.offlineExpiration { + return authCachedInfo{}, false, errors.New("token exceeded offline expiration") + } + + // If we get here, it means that the token is expired and we could not refresh it + // but we can still use it locally due to offline expiration configuration + // However, we don't want to update the token file neither its saved files. + return cachedInfo, true, nil + } + + // If the ID token was refreshed, we overwrite the cached one. + refreshedIDToken, ok := tok.Extra("id_token").(string) + if !ok { + refreshedIDToken = cachedInfo.RawIDToken + } + + return authCachedInfo{Token: tok, RawIDToken: refreshedIDToken}, false, nil +} + +func (b *Broker) fetchUserInfo(ctx context.Context, session *sessionInfo, t *authCachedInfo) (userClaims claims, userGroups []group.Info, err error) { + defer func() { + // Override the error so that we don't leak information. Also, abstract it for the user. + // We still log as error for the admin to get access. + if err != nil { + slog.Error(fmt.Sprintf("Error when fetching user info: %v", err)) + err = errors.New("could not fetch user info") + } + }() + + // If we didn't restore user information from the cache, we need to query the provider for it, which means + // we need to validate the token. + idToken, err := b.auth.provider.Verifier(&b.auth.oidcCfg).Verify(ctx, t.RawIDToken) + if err != nil { + return claims{}, nil, fmt.Errorf("could not verify token: %v", err) + } + + userGroups, err = b.providerInfo.GetGroups(t.Token) + if err != nil { + return claims{}, nil, fmt.Errorf("could not get user groups: %v", err) + } + + if err := idToken.Claims(&userClaims); err != nil { + return claims{}, nil, fmt.Errorf("could not get user info: %v", err) + } + + if userClaims.Email == "" { + return claims{}, nil, errors.New("invalid claims") + } + + if userClaims.Email != session.username { + return claims{}, nil, fmt.Errorf("returned user %q does not match the selected one %q", userClaims.Email, session.username) + } + + return userClaims, userGroups, nil +} + +type claims struct { + Name string `json:"name"` + PreferredUserName string `json:"preferred_username"` + Email string `json:"email"` + EmailVerified bool `json:"email_verified"` + Sub string `json:"sub"` +} + +func (b *Broker) userInfoFromClaims(userClaims claims, groups []group.Info) (string, error) { + user := struct { + Name string + UUID string + Home string + Shell string + Gecos string + Groups []group.Info + }{ + Name: userClaims.Email, + UUID: userClaims.Sub, + Home: filepath.Join(b.homeDirPath, userClaims.Email), + Shell: "/usr/bin/bash", + Gecos: userClaims.Name, + Groups: groups, + } + + var buf bytes.Buffer + err := template.Must(template.New("").Parse(`{ + "name": "{{.Name}}", + "uuid": "{{.UUID}}", + "gecos": "{{.Gecos}}", + "dir": "{{.Home}}", + "shell": "{{.Shell}}", + "groups": [ {{range $index, $g := .Groups}} + {{- if $index}}, {{end -}} + {"name": "{{.Name}}", "ugid": "{{.UGID}}"} + {{- end}} ] +}`)).Execute(&buf, user) + if err != nil { + return "", err + } + + return buf.String(), nil } diff --git a/internal/broker/consts.go b/internal/broker/consts.go new file mode 100644 index 0000000000..dd1a08a4f7 --- /dev/null +++ b/internal/broker/consts.go @@ -0,0 +1,18 @@ +package broker + +// Broker responses. +const ( + // AuthGranted is the response when the authentication is granted. + AuthGranted = "granted" + // AuthDenied is the response when the authentication is denied. + AuthDenied = "denied" + // AuthCancelled is the response when the authentication is cancelled. + AuthCancelled = "cancelled" + // AuthRetry is the response when the authentication needs to be retried (another chance). + AuthRetry = "retry" + // AuthNext is the response when another MFA (including changing password) authentication is necessary. + AuthNext = "next" +) + +// AuthReplies is the list of all possible authentication replies. +var AuthReplies = []string{AuthGranted, AuthDenied, AuthCancelled, AuthRetry, AuthNext} diff --git a/internal/broker/encrypt.go b/internal/broker/encrypt.go new file mode 100644 index 0000000000..74cce26172 --- /dev/null +++ b/internal/broker/encrypt.go @@ -0,0 +1,101 @@ +package broker + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/rsa" + "crypto/sha512" + "encoding/base64" + "errors" + "fmt" + + "golang.org/x/crypto/scrypt" + "golang.org/x/exp/slog" +) + +const saltLen = 32 + +func encrypt(raw, key []byte) ([]byte, error) { + salt := make([]byte, saltLen) + if _, err := rand.Read(salt); err != nil { + return nil, err + } + + derivedKey, err := scrypt.Key(key, salt, 32768, 8, 1, 32) + if err != nil { + return nil, err + } + + block, err := aes.NewCipher(derivedKey) + if err != nil { + return nil, err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + nonce := make([]byte, gcm.NonceSize()) + if _, err := rand.Read(nonce); err != nil { + return nil, err + } + + ciphered := gcm.Seal(nonce, nonce, raw, nil) + return append(ciphered, salt...), nil +} + +func decrypt(ciphered, key []byte) ([]byte, error) { + salt, data := ciphered[len(ciphered)-saltLen:], ciphered[:len(ciphered)-saltLen] + + derivedKey, err := scrypt.Key(key, salt, 32768, 8, 1, 32) + if err != nil { + return nil, err + } + + block, err := aes.NewCipher(derivedKey) + if err != nil { + return nil, err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + decrypted, err := gcm.Open(nil, data[:gcm.NonceSize()], data[gcm.NonceSize():], nil) + if err != nil { + return nil, err + } + + return decrypted, nil +} + +// decodeRawChallenge extract the base64 challenge and try to decrypt it with the private key. +func decodeRawChallenge(priv *rsa.PrivateKey, rawChallenge string) (decoded string, err error) { + defer func() { + // Override the error so that we don't leak information. Also, abstract it for the user. + // We still log as error for the admin to get access. + if err != nil { + slog.Error(fmt.Sprintf("Error when decoding challenge: %v", err)) + err = errors.New("could not decode challenge") + } + }() + + if rawChallenge == "" { + return "", nil + } + + ciphertext, err := base64.StdEncoding.DecodeString(rawChallenge) + if err != nil { + return "", err + } + + plaintext, err := rsa.DecryptOAEP(sha512.New(), nil, priv, ciphertext, nil) + if err != nil { + return "", fmt.Errorf("could not decrypt challenge: %v", err) + } + + return string(plaintext), nil +} From a69d5884391b867edcfdb6e2760adfe5371808ef Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Wed, 10 Apr 2024 11:35:58 -0400 Subject: [PATCH 0037/1670] Add tests and helpers to internal/broker package --- internal/broker/broker_test.go | 620 ++++++++++++++++++ internal/broker/export_test.go | 76 +++ internal/broker/helper_test.go | 157 +++++ internal/broker/options_test.go | 17 + ...r_info_with_default_home_when_not_provided | 8 + .../successfully_fetch_user_info_with_groups | 8 + ...uccessfully_fetch_user_info_without_groups | 8 + ...icated_with_password_and_session_is_passwd | 2 + ...sword_if_already_authenticated_with_qrcode | 2 + ...word_if_token_exists_and_session_is_passwd | 2 + .../get_password_and_qrcode_if_token_exists | 4 + .../golden/get_qrcode_if_there_is_no_token | 2 + .../provider_url/test-user@email.com.cache | 1 + .../first_call | 11 + .../provider_url/test-user@email.com.cache | 1 + .../first_call | 11 + .../provider_url/test-user@email.com.cache | 1 + .../first_call | 3 + .../second_call | 11 + .../cache/.empty | 0 .../first_call | 3 + .../cache/.empty | 0 .../error_when_can_not_cache_token/first_call | 3 + .../second_call | 3 + .../cache/.empty | 0 .../first_call | 3 + .../cache/.empty | 0 .../first_call | 3 + .../second_call | 3 + .../cache/.empty | 0 .../first_call | 3 + .../cache/.empty | 0 .../first_call | 3 + .../cache/.empty | 0 .../first_call | 3 + .../provider_url/test-user@email.com.cache | 1 + .../first_call | 3 + .../provider_url/test-user@email.com.cache | 1 + .../first_call | 3 + .../provider_url/test-user@email.com.cache | 1 + .../first_call | 3 + .../cache/.empty | 0 .../first_call | 3 + .../provider_url/test-user@email.com.cache | 1 + .../first_call | 3 + .../provider_url/test-user@email.com.cache | 1 + .../first_call | 3 + .../cache/.empty | 0 .../first_call | 5 + .../cache/.empty | 0 .../first_call | 3 + .../provider_url/test-user@email.com.cache | 1 + .../first_call | 3 + .../provider_url/test-user@email.com.cache | 1 + .../first_call | 11 + .../provider_url/test-user@email.com.cache | 1 + .../first_call | 3 + .../second_call | 11 + .../golden/successfully_select_newpassword | 3 + .../golden/successfully_select_password | 3 + .../golden/successfully_select_qrcode | 5 + 61 files changed, 1045 insertions(+) create mode 100644 internal/broker/broker_test.go create mode 100644 internal/broker/export_test.go create mode 100644 internal/broker/helper_test.go create mode 100644 internal/broker/options_test.go create mode 100644 internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_default_home_when_not_provided create mode 100644 internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_groups create mode 100644 internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_without_groups create mode 100644 internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_password_and_session_is_passwd create mode 100644 internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_qrcode create mode 100644 internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_session_is_passwd create mode 100644 internal/broker/testdata/TestGetAuthenticationModes/golden/get_password_and_qrcode_if_token_exists create mode 100644 internal/broker/testdata/TestGetAuthenticationModes/golden/get_qrcode_if_there_is_no_token create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/cache/provider_url/test-user@email.com.cache create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable_and_token_is_not_offline-expired/cache/provider_url/test-user@email.com.cache create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable_and_token_is_not_offline-expired/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/cache/provider_url/test-user@email.com.cache create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/second_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_authentication_data_is_invalid/cache/.empty create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_authentication_data_is_invalid/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/cache/.empty create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/second_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_challenge_can_not_be_decrypted/cache/.empty create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_challenge_can_not_be_decrypted/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/cache/.empty create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/second_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/cache/.empty create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/cache/.empty create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_token_is_not_set/cache/.empty create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_token_is_not_set/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/cache/provider_url/test-user@email.com.cache create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/cache/provider_url/test-user@email.com.cache create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_server_offline_and_token_expired/cache/provider_url/test-user@email.com.cache create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_server_offline_and_token_expired/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/cache/.empty create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/cache/provider_url/test-user@email.com.cache create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_offline-valid_but_server_returns_error/cache/provider_url/test-user@email.com.cache create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_offline-valid_but_server_returns_error/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token/cache/.empty create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_response_is_invalid/cache/.empty create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_response_is_invalid/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/cache/provider_url/test-user@email.com.cache create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/cache/provider_url/test-user@email.com.cache create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/cache/provider_url/test-user@email.com.cache create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/second_call create mode 100644 internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_newpassword create mode 100644 internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_password create mode 100644 internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_qrcode diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go new file mode 100644 index 0000000000..2d160ab372 --- /dev/null +++ b/internal/broker/broker_test.go @@ -0,0 +1,620 @@ +package broker_test + +import ( + "context" + "flag" + "fmt" + "net/http/httptest" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/ubuntu/oidc-broker/internal/broker" + "github.com/ubuntu/oidc-broker/internal/providers/group" + "github.com/ubuntu/oidc-broker/internal/testutils" + "golang.org/x/oauth2" + "gopkg.in/yaml.v3" +) + +var defaultProvider *httptest.Server + +func TestNew(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + issuer string + clientID string + cachePath string + offlineExpiration string + + wantErr bool + }{ + "Successfully create new broker": {}, + "Successfully create new broker with offline expiration": {offlineExpiration: "10"}, + + "Error if issuer is not provided": {issuer: "-", wantErr: true}, + "Error if provided issuer is not reachable": {issuer: "https://notavailable", wantErr: true}, + "Error if clientID is not provided": {clientID: "-", wantErr: true}, + "Error if cacheDir is not provided": {cachePath: "-", wantErr: true}, + "Error if offline expiration is provided but not parseable": {offlineExpiration: "invalid", wantErr: true}, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + switch tc.issuer { + case "": + tc.issuer = defaultProvider.URL + case "-": + tc.issuer = "" + } + + if tc.clientID == "-" { + tc.clientID = "" + } else { + tc.clientID = "test-client-id" + } + + if tc.cachePath == "-" { + tc.cachePath = "" + } else { + tc.cachePath = t.TempDir() + } + + bCfg := broker.Config{ + IssuerURL: tc.issuer, + ClientID: tc.clientID, + CachePath: tc.cachePath, + OfflineExpiration: tc.offlineExpiration, + } + b, err := broker.New(bCfg) + if tc.wantErr { + require.Error(t, err, "New should have returned an error") + return + } + require.NoError(t, err, "New should not have returned an error") + require.NotNil(t, b, "New should have returned a non-nil broker") + }) + } +} + +var supportedUILayouts = map[string]map[string]string{ + "form": { + "type": "form", + "entry": "chars_password", + }, + "form-without-entry": { + "type": "form", + }, + + "qrcode": { + "type": "qrcode", + "wait": "true", + }, + "qrcode-without-wait": { + "type": "qrcode", + }, + + "newpassword": { + "type": "newpassword", + "entry": "chars_password", + }, + "newpassword-without-entry": { + "type": "newpassword", + }, +} + +func TestGetAuthenticationModes(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + sessionMode string + sessionID string + supportedLayouts []string + + tokenExists bool + secondAuthStep bool + + wantErr bool + }{ + // Auth Session + "Get qrcode if there is no token": {}, + "Get newpassword if already authenticated with QRCode": {secondAuthStep: true}, + "Get password and qrcode if token exists": {tokenExists: true}, + + // Passwd Session + "Get only password if token exists and session is passwd": {sessionMode: "passwd", tokenExists: true}, + "Get newpassword if already authenticated with password and session is passwd": {sessionMode: "passwd", tokenExists: true, secondAuthStep: true}, + + "Error if there is no session": {sessionID: "-", wantErr: true}, + + // General errors + "Error if expecting qrcode but not supported": {supportedLayouts: []string{"qrcode-without-wait"}, wantErr: true}, + "Error if expecting newpassword but not supported": {supportedLayouts: []string{"newpassword-without-entry"}, wantErr: true}, + "Error if expecting password but not supported": {supportedLayouts: []string{"form-without-entry"}, wantErr: true}, + + // Passwd session errors + "Error if session is passwd but token does not exist": {sessionMode: "passwd", wantErr: true}, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + if tc.sessionMode == "" { + tc.sessionMode = "auth" + } + + b, sessionID, _ := newBrokerForTests(t, t.TempDir(), defaultProvider.URL, "", tc.sessionMode) + + if tc.sessionID == "-" { + sessionID = "" + } + if tc.tokenExists { + err := os.MkdirAll(filepath.Dir(b.TokenPathForSession(sessionID)), 0700) + require.NoError(t, err, "Setup: MkdirAll should not have returned an error") + err = os.WriteFile(b.TokenPathForSession(sessionID), []byte("some token"), 0600) + require.NoError(t, err, "Setup: WriteFile should not have returned an error") + } + if tc.secondAuthStep { + b.UpdateSessionAuthStep(sessionID, 1) + } + + if tc.supportedLayouts == nil { + tc.supportedLayouts = []string{"form", "qrcode", "newpassword"} + } + var layouts []map[string]string + for _, layout := range tc.supportedLayouts { + layouts = append(layouts, supportedUILayouts[layout]) + } + + got, err := b.GetAuthenticationModes(sessionID, layouts) + if tc.wantErr { + require.Error(t, err, "GetAuthenticationModes should have returned an error") + return + } + require.NoError(t, err, "GetAuthenticationModes should not have returned an error") + + want := testutils.LoadWithUpdateFromGoldenYAML(t, got) + require.Equal(t, want, got, "GetAuthenticationModes should have returned the expected value") + }) + } +} + +var supportedLayouts = []map[string]string{ + supportedUILayouts["form"], + supportedUILayouts["qrcode"], + supportedUILayouts["newpassword"], +} + +func TestSelectAuthenticationMode(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + modeName string + + customHandlers map[string]testutils.ProviderHandler + + wantErr bool + }{ + "Successfully select password": {modeName: "password"}, + "Successfully select qrcode": {modeName: "qrcode"}, + "Successfully select newpassword": {modeName: "newpassword"}, + + "Error when selecting invalid mode": {modeName: "invalid", wantErr: true}, + "Error when selecting qrcode but provider is unavailable": {modeName: "qrcode", wantErr: true, + customHandlers: map[string]testutils.ProviderHandler{ + "/device_auth": testutils.UnavailableHandler(), + }, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + provider := defaultProvider + if tc.customHandlers != nil { + var opts []testutils.OptionProvider + for path, handler := range tc.customHandlers { + opts = append(opts, testutils.WithHandler(path, handler)) + } + p, cleanup := testutils.StartMockProvider(opts...) + defer cleanup() + provider = p + } + + b, sessionID, _ := newBrokerForTests(t, t.TempDir(), provider.URL, "", "auth") + + // We need to do a GAM call first to get all the modes. + _, err := b.GetAuthenticationModes(sessionID, supportedLayouts) + require.NoError(t, err, "Setup: GetAuthenticationModes should not have returned an error") + + got, err := b.SelectAuthenticationMode(sessionID, tc.modeName) + if tc.wantErr { + require.Error(t, err, "SelectAuthenticationMode should have returned an error") + return + } + require.NoError(t, err, "SelectAuthenticationMode should not have returned an error") + + want := testutils.LoadWithUpdateFromGoldenYAML(t, got) + require.Equal(t, want, got, "SelectAuthenticationMode should have returned the expected layout") + }) + } +} + +func TestIsAuthenticated(t *testing.T) { + t.Parallel() + + correctPassword := "password" + + tests := map[string]struct { + sessionMode string + firstMode string + firstChallenge string + firstAuthInfo map[string]any + badFirstKey bool + + customHandlers map[string]testutils.ProviderHandler + + wantSecondCall bool + + preexistentToken string + offlineExpiration string + invalidAuthData bool + dontWaitForFirstCall bool + readOnlyCacheDir bool + }{ + "Successfully authenticate user with QRCode+newpassword": {firstChallenge: "-", wantSecondCall: true}, + "Successfully authenticate user with password": {firstMode: "password", preexistentToken: "valid"}, + + "Authenticating with qrcode reacquires token": {firstChallenge: "-", wantSecondCall: true, preexistentToken: "valid"}, + "Authenticating with password refreshes expired token": {firstMode: "password", preexistentToken: "expired"}, + "Authenticating with password still allowed if server is unreachable and token is not offline-expired": { + firstMode: "password", + preexistentToken: "expired", + offlineExpiration: "10", + customHandlers: map[string]testutils.ProviderHandler{ + "/token": testutils.UnavailableHandler(), + }, + }, + + "Error when authentication data is invalid": {invalidAuthData: true}, + "Error when challenge can not be decrypted": {firstMode: "password", badFirstKey: true}, + "Error when provided wrong challenge": {firstMode: "password", preexistentToken: "valid", firstChallenge: "wrongpassword"}, + "Error when can not cache token": {firstChallenge: "-", wantSecondCall: true, readOnlyCacheDir: true}, + "Error when IsAuthenticated is ongoing for session": {dontWaitForFirstCall: true, wantSecondCall: true}, + + "Error when mode is password and token does not exist": {firstMode: "password"}, + "Error when mode is password and server offline and token expired": { + firstMode: "password", + preexistentToken: "expired", + customHandlers: map[string]testutils.ProviderHandler{ + "/token": testutils.UnavailableHandler(), + }, + }, + "Error when mode is password and token is offline-valid but server returns error": { + firstMode: "password", + preexistentToken: "expired", + offlineExpiration: "10", + customHandlers: map[string]testutils.ProviderHandler{ + "/token": testutils.BadRequestHandler(), + }, + }, + "Error when mode is password and token is invalid": {firstMode: "password", preexistentToken: "invalid"}, + "Error when mode is password and cached token can't be refreshed": {firstMode: "password", preexistentToken: "no-refresh"}, + "Error when mode is password and can not fetch user info": {firstMode: "password", preexistentToken: "invalid-id"}, + + "Error when mode is qrcode and response is invalid": {firstAuthInfo: map[string]any{"response": "not a valid response"}}, + "Error when mode is qrcode and can not get token": { + customHandlers: map[string]testutils.ProviderHandler{ + "/token": testutils.UnavailableHandler(), + }, + }, + + "Error when mode is newpassword and token is not set": { + firstMode: "newpassword", + firstAuthInfo: map[string]any{"token": nil}, + }, + "Error when mode is newpassword and id token is not set": { + firstMode: "newpassword", + firstAuthInfo: map[string]any{"token": &oauth2.Token{}}, + }, + "Error when mode is newpassword and can not fetch user info": { + firstMode: "newpassword", + firstAuthInfo: map[string]any{ + "token": (&oauth2.Token{}).WithExtra(map[string]interface{}{"id_token": "invalid"}), + }, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + if tc.sessionMode == "" { + tc.sessionMode = "auth" + } + + outDir := t.TempDir() + cacheDir := filepath.Join(outDir, "cache") + + err := os.Mkdir(cacheDir, 0700) + require.NoError(t, err, "Setup: Mkdir should not have returned an error") + + provider := defaultProvider + if tc.customHandlers != nil { + var opts []testutils.OptionProvider + for path, handler := range tc.customHandlers { + opts = append(opts, testutils.WithHandler(path, handler)) + } + p, cleanup := testutils.StartMockProvider(opts...) + defer cleanup() + provider = p + } + b, sessionID, key := newBrokerForTests(t, cacheDir, provider.URL, tc.offlineExpiration, tc.sessionMode) + + if tc.preexistentToken != "" { + tok := generateCachedInfo(t, tc.preexistentToken, provider.URL) + err := b.CacheAuthInfo(sessionID, tok, correctPassword) + require.NoError(t, err, "Setup: SaveToken should not have returned an error") + } + + var readOnlyCacheCleanup, readOnlyTokenCleanup func() + if tc.readOnlyCacheDir { + if tc.preexistentToken != "" { + readOnlyTokenCleanup = testutils.MakeReadOnly(t, b.TokenPathForSession(sessionID)) + t.Cleanup(readOnlyTokenCleanup) + } + readOnlyCacheCleanup = testutils.MakeReadOnly(t, b.CachePath()) + t.Cleanup(readOnlyCacheCleanup) + } + + switch tc.firstChallenge { + case "": + tc.firstChallenge = correctPassword + case "-": + tc.firstChallenge = "" + } + + authData := "{}" + if tc.firstChallenge != "" { + eKey := key + if tc.badFirstKey { + eKey = "" + } + authData = `{"challenge":"` + encryptChallenge(t, tc.firstChallenge, eKey) + `"}` + } + if tc.invalidAuthData { + authData = "invalid json" + } + + type isAuthenticatedResponse struct { + Access string + Data string + Err string + } + + firstCallDone := make(chan struct{}) + go func() { + defer close(firstCallDone) + + if tc.firstMode == "" { + tc.firstMode = "qrcode" + } + updateAuthModes(t, b, sessionID, tc.firstMode) + + if tc.firstAuthInfo != nil { + for k, v := range tc.firstAuthInfo { + require.NoError(t, b.SetAuthInfo(sessionID, k, v), "Setup: Failed to set AuthInfo for tests") + } + } + + access, data, err := b.IsAuthenticated(sessionID, authData) + + // Redact variant session id from the response + data = strings.ReplaceAll(data, sessionID, "SESSION_ID") + errStr := strings.ReplaceAll(fmt.Sprintf("%v", err), sessionID, "SESSION_ID") + + got := isAuthenticatedResponse{Access: access, Data: data, Err: errStr} + + out, err := yaml.Marshal(got) + require.NoError(t, err, "Failed to marshal first response") + + err = os.WriteFile(filepath.Join(outDir, "first_call"), out, 0600) + require.NoError(t, err, "Failed to write first response") + }() + + if !tc.dontWaitForFirstCall { + <-firstCallDone + } + + if tc.wantSecondCall { + // Give some time for the first call + time.Sleep(10 * time.Millisecond) + + secondAuthData := `{"challenge":"` + encryptChallenge(t, "passwordpassword", key) + `"}` + if tc.invalidAuthData { + secondAuthData = "invalid json" + } + + secondCallDone := make(chan struct{}) + go func() { + defer close(secondCallDone) + + updateAuthModes(t, b, sessionID, "newpassword") + + access, data, err := b.IsAuthenticated(sessionID, secondAuthData) + + // Redact variant session id from the response + data = strings.ReplaceAll(data, sessionID, "SESSION_ID") + errStr := strings.ReplaceAll(fmt.Sprintf("%v", err), sessionID, "SESSION_ID") + + got := isAuthenticatedResponse{Access: access, Data: data, Err: errStr} + out, err := yaml.Marshal(got) + require.NoError(t, err, "Failed to marshal second response") + + err = os.WriteFile(filepath.Join(outDir, "second_call"), out, 0600) + require.NoError(t, err, "Failed to write second response") + }() + <-secondCallDone + } + <-firstCallDone + + // We need to restore some permissions in order to save the golden files. + if tc.readOnlyCacheDir { + readOnlyCacheCleanup() + if tc.preexistentToken != "" { + readOnlyTokenCleanup() + } + } + + // Ensure that the token content is generic to avoid golden file conflicts + if _, err := os.Stat(b.TokenPathForSession(sessionID)); err == nil { + err := os.WriteFile(b.TokenPathForSession(sessionID), []byte("Definitely an encrypted token"), 0600) + require.NoError(t, err, "Teardown: Failed to write generic token file") + } + + // Ensure that the directory structure is generic to avoid golden file conflicts + if _, err := os.Stat(filepath.Dir(b.TokenPathForSession(sessionID))); err == nil { + toReplace := strings.ReplaceAll(strings.TrimPrefix(provider.URL, "http://"), ":", "_") + providerCache := filepath.Dir(b.TokenPathForSession(sessionID)) + newProviderCache := strings.ReplaceAll(providerCache, toReplace, "provider_url") + err := os.Rename(providerCache, newProviderCache) + if err != nil { + require.ErrorIs(t, err, os.ErrNotExist, "Teardown: Failed to rename cache directory") + } + } + + testutils.CompareTreesWithFiltering(t, outDir, testutils.GoldenPath(t), testutils.Update()) + }) + } +} + +func TestFetchUserInfo(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + username string + userToken string + + emptyHomeDir bool + emptyGroups bool + wantGroupErr bool + wantErr bool + }{ + "Successfully fetch user info with groups": {}, + "Successfully fetch user info without groups": {emptyGroups: true}, + "Successfully fetch user info with default home when not provided": {emptyHomeDir: true}, + + "Error when token can not be validated": {userToken: "invalid", wantErr: true}, + "Error when ID token claims are invalid": {userToken: "invalid-id", wantErr: true}, + "Error when user email is not configured": {userToken: "no-email", wantErr: true}, + "Error when user email is different than the requested one": {userToken: "other-email", wantErr: true}, + "Error when getting user groups": {wantGroupErr: true, wantErr: true}, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + homeDirPath := "/home/userInfoTests/" + if tc.emptyHomeDir { + homeDirPath = "" + } + + brokerCfg := broker.Config{ + IssuerURL: defaultProvider.URL, + ClientID: "test-client-id", + CachePath: t.TempDir(), + HomeBaseDir: homeDirPath, + } + + mockInfoer := &testutils.MockProviderInfoer{ + GroupsErr: tc.wantGroupErr, + Groups: []group.Info{ + {Name: "remote-group", UGID: "12345"}, + {Name: "linux-local-group", UGID: ""}, + }, + } + if tc.emptyGroups { + mockInfoer.Groups = []group.Info{} + } + + b, err := broker.New(brokerCfg, broker.WithSkipSignatureCheck(), broker.WithCustomProviderInfo(mockInfoer)) + require.NoError(t, err, "Setup: New should not have returned an error") + + if tc.username == "" { + tc.username = "test-user@email.com" + } + if tc.userToken == "" { + tc.userToken = "valid" + } + + sessionID, _, err := b.NewSession(tc.username, "lang", "auth") + require.NoError(t, err, "Setup: Failed to create session for the tests") + + cachedInfo := generateCachedInfo(t, tc.userToken, defaultProvider.URL) + + got, err := b.FetchUserInfo(sessionID, &cachedInfo) + if tc.wantErr { + require.Error(t, err, "FetchUserInfo should have returned an error") + return + } + require.NoError(t, err, "FetchUserInfo should not have returned an error") + + want := testutils.LoadWithUpdateFromGolden(t, got) + require.Equal(t, want, got, "FetchUserInfo should have returned the expected value") + }) + } +} + +func TestCancelIsAuthenticated(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + provider, cleanup := testutils.StartMockProvider(testutils.WithHandler("/token", testutils.HangingHandler(ctx))) + t.Cleanup(cleanup) + + b, sessionID, _ := newBrokerForTests(t, t.TempDir(), provider.URL, "", "auth") + updateAuthModes(t, b, sessionID, "qrcode") + + stopped := make(chan struct{}) + go func() { + defer close(stopped) + _, _, err := b.IsAuthenticated(sessionID, `{}`) + require.Error(t, err, "IsAuthenticated should have returned an error") + }() + + // Wait for the call to hang + time.Sleep(50 * time.Millisecond) + + b.CancelIsAuthenticated(sessionID) + cancel() + <-stopped +} + +func TestEndSession(t *testing.T) { + t.Parallel() + + b, sessionID, _ := newBrokerForTests(t, t.TempDir(), defaultProvider.URL, "", "auth") + + // Try to end a session that does not exist + err := b.EndSession("nonexistent") + require.Error(t, err, "EndSession should have returned an error when ending a nonexistent session") + + // End a session that exists + err = b.EndSession(sessionID) + require.NoError(t, err, "EndSession should not have returned an error when ending an existent session") +} + +func TestMain(m *testing.M) { + testutils.InstallUpdateFlag() + flag.Parse() + + server, cleanup := testutils.StartMockProvider() + defer cleanup() + + defaultProvider = server + + m.Run() +} diff --git a/internal/broker/export_test.go b/internal/broker/export_test.go new file mode 100644 index 0000000000..66a5f65d37 --- /dev/null +++ b/internal/broker/export_test.go @@ -0,0 +1,76 @@ +package broker + +import "context" + +// TokenPathForSession returns the path to the token file for the given session. +func (b *Broker) TokenPathForSession(sessionID string) string { + b.currentSessionsMu.Lock() + defer b.currentSessionsMu.Unlock() + + session, ok := b.currentSessions[sessionID] + if !ok { + return "" + } + + return session.cachePath +} + +// CachePath returns the path to the cache directory for tests. +func (b *Broker) CachePath() string { + return b.auth.cachePath +} + +// UpdateSessionAuthStep updates the current auth step for the given session. +func (b *Broker) UpdateSessionAuthStep(sessionID string, authStep int) { + b.currentSessionsMu.Lock() + defer b.currentSessionsMu.Unlock() + + session, ok := b.currentSessions[sessionID] + if !ok { + return + } + + session.currentAuthStep = authStep + b.currentSessions[sessionID] = session +} + +// SetAuthInfo sets the given key and value for the given session.AuthInfo. +func (b *Broker) SetAuthInfo(sessionID, key string, value any) error { + s, err := b.getSession(sessionID) + if err != nil { + return err + } + + s.authInfo[key] = value + if err = b.updateSession(sessionID, s); err != nil { + return err + } + + return nil +} + +type AuthCachedInfo = authCachedInfo + +// CacheAuthInfo exposes the broker's cacheAuthInfo method for tests. +func (b *Broker) CacheAuthInfo(sessionID string, token authCachedInfo, password string) error { + s, err := b.getSession(sessionID) + if err != nil { + return err + } + return b.cacheAuthInfo(&s, token, password) +} + +// FetchUserInfo exposes the broker's fetchUserInfo method for tests. +func (b *Broker) FetchUserInfo(sessionID string, cachedInfo *authCachedInfo) (string, error) { + s, err := b.getSession(sessionID) + if err != nil { + return "", err + } + + uInfo, groups, err := b.fetchUserInfo(context.TODO(), &s, cachedInfo) + if err != nil { + return "", err + } + + return b.userInfoFromClaims(uInfo, groups) +} diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go new file mode 100644 index 0000000000..84c8fa6754 --- /dev/null +++ b/internal/broker/helper_test.go @@ -0,0 +1,157 @@ +package broker_test + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/sha512" + "crypto/x509" + "encoding/base64" + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/ubuntu/oidc-broker/internal/broker" + "github.com/ubuntu/oidc-broker/internal/providers/group" + "github.com/ubuntu/oidc-broker/internal/testutils" + "golang.org/x/oauth2" +) + +// newBrokerForTests is a helper function to create a new broker for tests and already starts a session for user +// t.Name() normalized. +func newBrokerForTests(t *testing.T, cachePath, issuerURL, offlineExpiration, mode string) (b *broker.Broker, id, key string) { + t.Helper() + + cfg := broker.Config{ + IssuerURL: issuerURL, + ClientID: "test-client-id", + CachePath: cachePath, + OfflineExpiration: offlineExpiration, + } + + b, err := broker.New( + cfg, + broker.WithSkipSignatureCheck(), + broker.WithCustomProviderInfo(&testutils.MockProviderInfoer{ + Groups: []group.Info{ + {Name: "remote-group", UGID: "12345"}, + {Name: "linux-local-group", UGID: ""}, + }, + }), + ) + require.NoError(t, err, "Setup: New should not have returned an error") + + id, key, err = b.NewSession("test-user@email.com", "some lang", mode) + require.NoError(t, err, "Setup: NewSession should not have returned an error") + + return b, id, key +} + +func encryptChallenge(t *testing.T, challenge, strKey string) string { + t.Helper() + + if strKey == "" { + return challenge + } + + pubASN1, err := base64.StdEncoding.DecodeString(strKey) + require.NoError(t, err, "Setup: base64 decoding should not have failed") + + pubKey, err := x509.ParsePKIXPublicKey(pubASN1) + require.NoError(t, err, "Setup: parsing public key should not have failed") + + rsaPubKey, ok := pubKey.(*rsa.PublicKey) + require.True(t, ok, "Setup: public key should be an RSA key") + + ciphertext, err := rsa.EncryptOAEP(sha512.New(), rand.Reader, rsaPubKey, []byte(challenge), nil) + require.NoError(t, err, "Setup: encryption should not have failed") + + // encrypt it to base64 and replace the challenge with it + return base64.StdEncoding.EncodeToString(ciphertext) +} + +func updateAuthModes(t *testing.T, b *broker.Broker, sessionID, selectedMode string) { + t.Helper() + + _, err := b.GetAuthenticationModes(sessionID, supportedLayouts) + require.NoError(t, err, "Setup: GetAuthenticationModes should not have returned an error") + _, err = b.SelectAuthenticationMode(sessionID, selectedMode) + require.NoError(t, err, "Setup: SelectAuthenticationMode should not have returned an error") +} + +var testTokens = map[string]broker.AuthCachedInfo{ + "valid": { + Token: &oauth2.Token{ + AccessToken: "accesstoken", + RefreshToken: "refreshtoken", + Expiry: time.Now().Add(1000 * time.Hour), + }, + }, + "expired": { + Token: &oauth2.Token{ + AccessToken: "accesstoken", + RefreshToken: "refreshtoken", + Expiry: time.Now().Add(-1000 * time.Hour), + }, + }, + "no-refresh": { + Token: &oauth2.Token{ + AccessToken: "accesstoken", + Expiry: time.Now().Add(-1000 * time.Hour), + }, + }, +} + +func generateCachedInfo(t *testing.T, preexistentToken, issuer string) broker.AuthCachedInfo { + t.Helper() + + if preexistentToken == "invalid" { + return broker.AuthCachedInfo{} + } + + var email string + switch preexistentToken { + case "no-email": + email = "" + case "other-email": + email = "other-user@email.com" + default: + email = "test-user@email.com" + } + + idToken := fmt.Sprintf(`{ + "iss": "%s", + "sub": "saved-user-id", + "aud": "test-client-id", + "name": "saved-user", + "exp": 9999999999, + "preferred_username": "User Saved", + "email": "%s", + "email_verified": true + }`, issuer, email) + encodedToken := fmt.Sprintf(".%s.", base64.RawURLEncoding.EncodeToString([]byte(idToken))) + + tok, ok := testTokens[preexistentToken] + if !ok { + tok = testTokens["valid"] + } + + tok.UserInfo = fmt.Sprintf(`{ + "name": "%[1]s", + "uuid": "saved-user-id", + "gecos": "saved-user", + "dir": "/home/%[1]s", + "shell": "/usr/bin/bash", + "groups": [{"name": "saved-remote-group", "gid": "12345"}, {"name": "saved-local-group", "gid": ""}] +}`, email) + + if preexistentToken == "invalid-id" { + encodedToken = ".invalid." + tok.UserInfo = "" + } + + tok.Token = tok.Token.WithExtra(map[string]string{"id_token": encodedToken}) + tok.RawIDToken = encodedToken + + return tok +} diff --git a/internal/broker/options_test.go b/internal/broker/options_test.go new file mode 100644 index 0000000000..bf84372788 --- /dev/null +++ b/internal/broker/options_test.go @@ -0,0 +1,17 @@ +package broker + +import "github.com/ubuntu/oidc-broker/internal/providers" + +// WithSkipSignatureCheck returns an option that skips the JWT signature check. +func WithSkipSignatureCheck() Option { + return func(o *option) { + o.skipJWTSignatureCheck = true + } +} + +// WithCustomProviderInfo returns an option that sets a custom provider infoer for the broker. +func WithCustomProviderInfo(p providers.ProviderInfoer) Option { + return func(o *option) { + o.providerInfo = p + } +} diff --git a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_default_home_when_not_provided b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_default_home_when_not_provided new file mode 100644 index 0000000000..1e20b9fd65 --- /dev/null +++ b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_default_home_when_not_provided @@ -0,0 +1,8 @@ +{ + "name": "test-user@email.com", + "uuid": "saved-user-id", + "gecos": "saved-user", + "dir": "/home/test-user@email.com", + "shell": "/usr/bin/bash", + "groups": [ {"name": "remote-group", "ugid": "12345"}, {"name": "linux-local-group", "ugid": ""} ] +} \ No newline at end of file diff --git a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_groups b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_groups new file mode 100644 index 0000000000..845e65ddc9 --- /dev/null +++ b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_groups @@ -0,0 +1,8 @@ +{ + "name": "test-user@email.com", + "uuid": "saved-user-id", + "gecos": "saved-user", + "dir": "/home/userInfoTests/test-user@email.com", + "shell": "/usr/bin/bash", + "groups": [ {"name": "remote-group", "ugid": "12345"}, {"name": "linux-local-group", "ugid": ""} ] +} \ No newline at end of file diff --git a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_without_groups b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_without_groups new file mode 100644 index 0000000000..601722ae01 --- /dev/null +++ b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_without_groups @@ -0,0 +1,8 @@ +{ + "name": "test-user@email.com", + "uuid": "saved-user-id", + "gecos": "saved-user", + "dir": "/home/userInfoTests/test-user@email.com", + "shell": "/usr/bin/bash", + "groups": [ ] +} \ No newline at end of file diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_password_and_session_is_passwd b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_password_and_session_is_passwd new file mode 100644 index 0000000000..114712a3d6 --- /dev/null +++ b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_password_and_session_is_passwd @@ -0,0 +1,2 @@ +- id: newpassword + label: Define your password diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_qrcode b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_qrcode new file mode 100644 index 0000000000..114712a3d6 --- /dev/null +++ b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_qrcode @@ -0,0 +1,2 @@ +- id: newpassword + label: Define your password diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_session_is_passwd b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_session_is_passwd new file mode 100644 index 0000000000..bf063a882a --- /dev/null +++ b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_session_is_passwd @@ -0,0 +1,2 @@ +- id: password + label: Password Authentication diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_password_and_qrcode_if_token_exists b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_password_and_qrcode_if_token_exists new file mode 100644 index 0000000000..f7104c5526 --- /dev/null +++ b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_password_and_qrcode_if_token_exists @@ -0,0 +1,4 @@ +- id: password + label: Password Authentication +- id: qrcode + label: Device Authentication diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_qrcode_if_there_is_no_token b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_qrcode_if_there_is_no_token new file mode 100644 index 0000000000..6ea169fdf1 --- /dev/null +++ b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_qrcode_if_there_is_no_token @@ -0,0 +1,2 @@ +- id: qrcode + label: Device Authentication diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/cache/provider_url/test-user@email.com.cache new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/cache/provider_url/test-user@email.com.cache @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/first_call new file mode 100644 index 0000000000..521a9376df --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/first_call @@ -0,0 +1,11 @@ +access: granted +data: |- + {"userinfo": { + "name": "test-user@email.com", + "uuid": "test-user-id", + "gecos": "test-user", + "dir": "/home/test-user@email.com", + "shell": "/usr/bin/bash", + "groups": [ {"name": "remote-group", "ugid": "12345"}, {"name": "linux-local-group", "ugid": ""} ] + }} +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable_and_token_is_not_offline-expired/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable_and_token_is_not_offline-expired/cache/provider_url/test-user@email.com.cache new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable_and_token_is_not_offline-expired/cache/provider_url/test-user@email.com.cache @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable_and_token_is_not_offline-expired/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable_and_token_is_not_offline-expired/first_call new file mode 100644 index 0000000000..b72d8b1117 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable_and_token_is_not_offline-expired/first_call @@ -0,0 +1,11 @@ +access: granted +data: |- + {"userinfo": { + "name": "test-user@email.com", + "uuid": "saved-user-id", + "gecos": "saved-user", + "dir": "/home/test-user@email.com", + "shell": "/usr/bin/bash", + "groups": [{"name": "saved-remote-group", "gid": "12345"}, {"name": "saved-local-group", "gid": ""}] + }} +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/cache/provider_url/test-user@email.com.cache new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/cache/provider_url/test-user@email.com.cache @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/first_call new file mode 100644 index 0000000000..de73804437 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/first_call @@ -0,0 +1,3 @@ +access: next +data: "" +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/second_call new file mode 100644 index 0000000000..521a9376df --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/second_call @@ -0,0 +1,11 @@ +access: granted +data: |- + {"userinfo": { + "name": "test-user@email.com", + "uuid": "test-user-id", + "gecos": "test-user", + "dir": "/home/test-user@email.com", + "shell": "/usr/bin/bash", + "groups": [ {"name": "remote-group", "ugid": "12345"}, {"name": "linux-local-group", "ugid": ""} ] + }} +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_authentication_data_is_invalid/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_authentication_data_is_invalid/cache/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_authentication_data_is_invalid/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_authentication_data_is_invalid/first_call new file mode 100644 index 0000000000..aff48e05fa --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_authentication_data_is_invalid/first_call @@ -0,0 +1,3 @@ +access: denied +data: "" +err: authentication data is not a valid json value diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/cache/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/first_call new file mode 100644 index 0000000000..de73804437 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/first_call @@ -0,0 +1,3 @@ +access: next +data: "" +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/second_call new file mode 100644 index 0000000000..2f9ee1cdd8 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/second_call @@ -0,0 +1,3 @@ +access: retry +data: '{"message": "could not update token: could not cache token"}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_challenge_can_not_be_decrypted/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_challenge_can_not_be_decrypted/cache/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_challenge_can_not_be_decrypted/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_challenge_can_not_be_decrypted/first_call new file mode 100644 index 0000000000..18e5039829 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_challenge_can_not_be_decrypted/first_call @@ -0,0 +1,3 @@ +access: retry +data: '{"message": "could not decode challenge: could not decode challenge"}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/cache/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/first_call new file mode 100644 index 0000000000..de73804437 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/first_call @@ -0,0 +1,3 @@ +access: next +data: "" +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/second_call new file mode 100644 index 0000000000..3597173349 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/second_call @@ -0,0 +1,3 @@ +access: denied +data: "" +err: IsAuthenticated already running for session "SESSION_ID" diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/cache/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/first_call new file mode 100644 index 0000000000..c5186dbd62 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/first_call @@ -0,0 +1,3 @@ +access: denied +data: '{"message": "could not get required information"}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/cache/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/first_call new file mode 100644 index 0000000000..c5186dbd62 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/first_call @@ -0,0 +1,3 @@ +access: denied +data: '{"message": "could not get required information"}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_token_is_not_set/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_token_is_not_set/cache/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_token_is_not_set/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_token_is_not_set/first_call new file mode 100644 index 0000000000..c5186dbd62 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_token_is_not_set/first_call @@ -0,0 +1,3 @@ +access: denied +data: '{"message": "could not get required information"}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/cache/provider_url/test-user@email.com.cache new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/cache/provider_url/test-user@email.com.cache @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/first_call new file mode 100644 index 0000000000..46a2042d78 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/first_call @@ -0,0 +1,3 @@ +access: retry +data: '{"message": "could not authenticate user: could not load token"}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/cache/provider_url/test-user@email.com.cache new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/cache/provider_url/test-user@email.com.cache @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/first_call new file mode 100644 index 0000000000..86ec24c5e1 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/first_call @@ -0,0 +1,3 @@ +access: denied +data: '{"message": "could not get user info: could not fetch user info"}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_server_offline_and_token_expired/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_server_offline_and_token_expired/cache/provider_url/test-user@email.com.cache new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_server_offline_and_token_expired/cache/provider_url/test-user@email.com.cache @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_server_offline_and_token_expired/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_server_offline_and_token_expired/first_call new file mode 100644 index 0000000000..46a2042d78 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_server_offline_and_token_expired/first_call @@ -0,0 +1,3 @@ +access: retry +data: '{"message": "could not authenticate user: could not load token"}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/cache/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call new file mode 100644 index 0000000000..46a2042d78 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call @@ -0,0 +1,3 @@ +access: retry +data: '{"message": "could not authenticate user: could not load token"}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/cache/provider_url/test-user@email.com.cache new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/cache/provider_url/test-user@email.com.cache @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/first_call new file mode 100644 index 0000000000..46a2042d78 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/first_call @@ -0,0 +1,3 @@ +access: retry +data: '{"message": "could not authenticate user: could not load token"}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_offline-valid_but_server_returns_error/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_offline-valid_but_server_returns_error/cache/provider_url/test-user@email.com.cache new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_offline-valid_but_server_returns_error/cache/provider_url/test-user@email.com.cache @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_offline-valid_but_server_returns_error/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_offline-valid_but_server_returns_error/first_call new file mode 100644 index 0000000000..46a2042d78 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_offline-valid_but_server_returns_error/first_call @@ -0,0 +1,3 @@ +access: retry +data: '{"message": "could not authenticate user: could not load token"}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token/cache/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token/first_call new file mode 100644 index 0000000000..9196360dd2 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token/first_call @@ -0,0 +1,5 @@ +access: retry +data: |- + {"message": "could not authenticate user: oauth2: cannot fetch token: 503 Service Unavailable + Response: "} +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_response_is_invalid/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_response_is_invalid/cache/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_response_is_invalid/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_response_is_invalid/first_call new file mode 100644 index 0000000000..288a94c9a5 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_response_is_invalid/first_call @@ -0,0 +1,3 @@ +access: denied +data: '{"message": "could not get required response"}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/cache/provider_url/test-user@email.com.cache new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/cache/provider_url/test-user@email.com.cache @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/first_call new file mode 100644 index 0000000000..46a2042d78 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/first_call @@ -0,0 +1,3 @@ +access: retry +data: '{"message": "could not authenticate user: could not load token"}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/cache/provider_url/test-user@email.com.cache new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/cache/provider_url/test-user@email.com.cache @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call new file mode 100644 index 0000000000..b72d8b1117 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call @@ -0,0 +1,11 @@ +access: granted +data: |- + {"userinfo": { + "name": "test-user@email.com", + "uuid": "saved-user-id", + "gecos": "saved-user", + "dir": "/home/test-user@email.com", + "shell": "/usr/bin/bash", + "groups": [{"name": "saved-remote-group", "gid": "12345"}, {"name": "saved-local-group", "gid": ""}] + }} +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/cache/provider_url/test-user@email.com.cache new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/cache/provider_url/test-user@email.com.cache @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/first_call new file mode 100644 index 0000000000..de73804437 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/first_call @@ -0,0 +1,3 @@ +access: next +data: "" +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/second_call new file mode 100644 index 0000000000..521a9376df --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/second_call @@ -0,0 +1,11 @@ +access: granted +data: |- + {"userinfo": { + "name": "test-user@email.com", + "uuid": "test-user-id", + "gecos": "test-user", + "dir": "/home/test-user@email.com", + "shell": "/usr/bin/bash", + "groups": [ {"name": "remote-group", "ugid": "12345"}, {"name": "linux-local-group", "ugid": ""} ] + }} +err: diff --git a/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_newpassword b/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_newpassword new file mode 100644 index 0000000000..0c159c9c01 --- /dev/null +++ b/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_newpassword @@ -0,0 +1,3 @@ +entry: chars_password +label: Enter your password +type: newpassword diff --git a/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_password b/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_password new file mode 100644 index 0000000000..762980395b --- /dev/null +++ b/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_password @@ -0,0 +1,3 @@ +entry: chars_password +label: Gimme your password +type: form diff --git a/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_qrcode b/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_qrcode new file mode 100644 index 0000000000..dbe7ecacb0 --- /dev/null +++ b/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_qrcode @@ -0,0 +1,5 @@ +button: regenerate QR code +content: https://verification_uri.com +label: Scan the QR code or access "https://verification_uri.com" and use the code "user_code" +type: qrcode +wait: "true" From f4f17717647377ec259a79104a3bd05e165c483b Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Wed, 10 Apr 2024 11:36:22 -0400 Subject: [PATCH 0038/1670] Update manifest files with new dependencies --- go.mod | 33 +++++++++++++++++++++-- go.sum | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 114 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8e73dbf244..c8c8b62f82 100644 --- a/go.mod +++ b/go.mod @@ -5,23 +5,46 @@ go 1.22.0 toolchain go1.22.3 require ( + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0 + github.com/coreos/go-oidc/v3 v3.9.0 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/godbus/dbus/v5 v5.1.0 + github.com/google/uuid v1.6.0 + github.com/microsoftgraph/msgraph-sdk-go v1.36.0 + github.com/microsoftgraph/msgraph-sdk-go-core v1.1.0 + github.com/otiai10/copy v1.14.0 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 + golang.org/x/crypto v0.21.0 + golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 + golang.org/x/oauth2 v0.18.0 + gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 ) require ( + github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect + github.com/cjlapao/common-go v0.0.39 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/go-jose/go-jose/v3 v3.0.3 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/microsoft/kiota-abstractions-go v1.5.6 // indirect + github.com/microsoft/kiota-authentication-azure-go v1.0.2 // indirect + github.com/microsoft/kiota-http-go v1.3.2 // indirect + github.com/microsoft/kiota-serialization-form-go v1.0.0 // indirect + github.com/microsoft/kiota-serialization-json-go v1.0.6 // indirect + github.com/microsoft/kiota-serialization-multipart-go v1.0.0 // indirect + github.com/microsoft/kiota-serialization-text-go v1.0.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -32,10 +55,16 @@ require ( github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/std-uritemplate/std-uritemplate/go v0.0.54 // indirect github.com/subosito/gotenv v1.6.0 // indirect + go.opentelemetry.io/otel v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/protobuf v1.33.0 // indirect ) diff --git a/go.sum b/go.sum index b19977a358..acca30cbed 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,11 @@ +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0 h1:n1DH8TPV4qqPTje2RcUBYwtrTWlabVp4n46+74X2pn4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0/go.mod h1:HDcZnuGbiyppErN6lB+idp4CKhjbc8gwjto6OPpyggM= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aMclParm9/5Vgp+TY51uBQ= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc= +github.com/cjlapao/common-go v0.0.39 h1:bAAUrj2B9v0kMzbAOhzjSmiyDy+rd56r2sy7oEiQLlA= +github.com/cjlapao/common-go v0.0.39/go.mod h1:M3dzazLjTjEtZJbbxoA5ZDiGCiHmpwqW9l4UWaddwOA= +github.com/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo= +github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -9,10 +17,25 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 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/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= +github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +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.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -25,8 +48,30 @@ github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 h1:9ZM/t5 github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069/go.mod h1:AT4NpQrOmyj1L/+hLja6aR0lk81yYYL4ePnj2kp7d6M= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/microsoft/kiota-abstractions-go v1.5.6 h1:3hd1sACWB2B9grv8KG1T8g/gGQ4A8kTLv91OUxHSxkE= +github.com/microsoft/kiota-abstractions-go v1.5.6/go.mod h1:2WX7Oh8V9SAdZ80OGeE53rcbdys54Pd38rAeDUghrpM= +github.com/microsoft/kiota-authentication-azure-go v1.0.2 h1:tClGeyFZJ+4Bakf8u0euPM4wqy4ethycdOgx3jyH3pI= +github.com/microsoft/kiota-authentication-azure-go v1.0.2/go.mod h1:aTcti0bUJEcq7kBfQG4Sr4ElvRNuaalXcFEu4iEyQ6M= +github.com/microsoft/kiota-http-go v1.3.2 h1:HSrbw6MDMmgLaqU1qT3SgdmD/18KPW2usfyUtE017Lw= +github.com/microsoft/kiota-http-go v1.3.2/go.mod h1:pEs3eLpCJBhsesjtvM/3T4ODKCofPgDlDsGHHetau2g= +github.com/microsoft/kiota-serialization-form-go v1.0.0 h1:UNdrkMnLFqUCccQZerKjblsyVgifS11b3WCx+eFEsAI= +github.com/microsoft/kiota-serialization-form-go v1.0.0/go.mod h1:h4mQOO6KVTNciMF6azi1J9QB19ujSw3ULKcSNyXXOMA= +github.com/microsoft/kiota-serialization-json-go v1.0.6 h1:8v8IXMGurLCRYZs1l0Ck75lN0wzKDLko69mNdQGVWeQ= +github.com/microsoft/kiota-serialization-json-go v1.0.6/go.mod h1:I0CiXKgvKDFOO35lQ5VpYmd2nFLXHdJUsHnG8z/TX7A= +github.com/microsoft/kiota-serialization-multipart-go v1.0.0 h1:3O5sb5Zj+moLBiJympbXNaeV07K0d46IfuEd5v9+pBs= +github.com/microsoft/kiota-serialization-multipart-go v1.0.0/go.mod h1:yauLeBTpANk4L03XD985akNysG24SnRJGaveZf+p4so= +github.com/microsoft/kiota-serialization-text-go v1.0.0 h1:XOaRhAXy+g8ZVpcq7x7a0jlETWnWrEum0RhmbYrTFnA= +github.com/microsoft/kiota-serialization-text-go v1.0.0/go.mod h1:sM1/C6ecnQ7IquQOGUrUldaO5wj+9+v7G2W3sQ3fy6M= +github.com/microsoftgraph/msgraph-sdk-go v1.36.0 h1:DFofL21syrHLa1C71kri9z5GfPxMu8vl2NXsKybJwyw= +github.com/microsoftgraph/msgraph-sdk-go v1.36.0/go.mod h1:LRiv9UP/7NFLvhQGKHWSMDN2eLSg5u5IJ6FUpigDVX8= +github.com/microsoftgraph/msgraph-sdk-go-core v1.1.0 h1:NB7c/n4Knj+TLaLfjsahhSqoUqoN/CtyNB0XIe/nJnM= +github.com/microsoftgraph/msgraph-sdk-go-core v1.1.0/go.mod h1:M3w/5IFJ1u/DpwOyjsjNSVEA43y1rLOeX58suyfBhGk= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= +github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= +github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= +github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -53,6 +98,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/std-uritemplate/std-uritemplate/go v0.0.54 h1:8t7J7tNuMDj4Vkqq+IRENQDZTZzXJZZbN+iD60PEiAs= +github.com/std-uritemplate/std-uritemplate/go v0.0.54/go.mod h1:CLZ1543WRCuUQQjK0BvPM4QrG2toY8xNZUm8Vbt7vTc= github.com/stretchr/objx v0.1.0/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= @@ -70,38 +117,73 @@ github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5/go.mod h1:PUpwIgUu github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 h1:CA2dVorxvzdsGtszqhSjyvkrXxZi4bS52ZKvP0Ko634= github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47/go.mod h1:ZRhdDyx6YkKz/YiMWi0gS3uMCltgdaKz9IpkiNf/GRg= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 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.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +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.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 531a42ae4357e84e29c53e5f19249450e9a8dd87 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Fri, 7 Jun 2024 10:47:48 -0400 Subject: [PATCH 0039/1670] Bump Go to 1.22.4 Fixes Vulnerability #1: GO-2024-2887 The various Is methods (IsPrivate, IsLoopback, etc) did not work as expected for IPv4-mapped IPv6 addresses, returning false for addresses which would return true in their traditional IPv4 forms. More info: https://pkg.go.dev/vuln/GO-2024-2887 Standard library Found in: net@go1.21.0 Fixed in: net@go1.22.4 --- go.mod | 2 +- tools/go.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index c8c8b62f82..bf219ebacc 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/ubuntu/oidc-broker go 1.22.0 -toolchain go1.22.3 +toolchain go1.22.4 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0 diff --git a/tools/go.mod b/tools/go.mod index d167681402..26bc2eef38 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -2,7 +2,7 @@ module github.com/ubuntu/oidc-broker/tools go 1.22.0 -toolchain go1.22.3 +toolchain go1.22.4 require github.com/golangci/golangci-lint v1.59.0 From ed2a357fe9d9e23e6736d477c8f9a77a8119d8a0 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Thu, 16 May 2024 15:48:12 +0200 Subject: [PATCH 0040/1670] Update config example values and name --- config/{oidc-broker.broker => oidc-broker} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename config/{oidc-broker.broker => oidc-broker} (71%) diff --git a/config/oidc-broker.broker b/config/oidc-broker similarity index 71% rename from config/oidc-broker.broker rename to config/oidc-broker index d811e543d8..d62104f1ef 100644 --- a/config/oidc-broker.broker +++ b/config/oidc-broker @@ -1,12 +1,12 @@ [authd] name = OIDC Broker -brand_icon = broker_icon.png -dbus_name = com.ubuntu.authd.oidc_broker -dbus_object = /com/ubuntu/authd/oidc_broker +brand_icon = {abs_path_to_icon} +dbus_name = com.ubuntu.authd.OidcBroker +dbus_object = /com/ubuntu/authd/OidcBroker [oidc] issuer = https://{issuer_url} -client_id = client_id +client_id = {client_id} # The amount of days the user will be allowed to authenticate without a network connection. offline_expiration = 180 From 71e19b976f3d7fdf4da92a11f412e53d97198543 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Thu, 16 May 2024 15:48:40 +0200 Subject: [PATCH 0041/1670] Snap package for the broker Create a snap package to install the default (basic OIDC) version of the broker --- snap/hooks/install | 24 ++++++++++++++++++++++++ snap/snapcraft.yaml | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 snap/hooks/install create mode 100644 snap/snapcraft.yaml diff --git a/snap/hooks/install b/snap/hooks/install new file mode 100644 index 0000000000..974fcabe2e --- /dev/null +++ b/snap/hooks/install @@ -0,0 +1,24 @@ +#!/bin/sh +set -eu + +snap_base_dir=$(dirname ${SNAP}) + +cat < ${SNAP_COMMON}/oidc-broker +[authd] +name = OIDC Broker +brand_icon = ${snap_base_dir}/current/broker_icon.png +dbus_name = com.ubuntu.authd.OidcBroker +dbus_object = /com/ubuntu/authd/OidcBroker + +[oidc] +issuer = https://{issuer_url} +client_id = {client_id} + +# The amount of days the user will be allowed to authenticate without a network connection. +# offline_expiration = 180 + +# The directory where the user's home directory will be created. +# The user home directory will be created in the format of {home_base_dir}/{username} +# home_base_dir = /home + +EOF diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml new file mode 100644 index 0000000000..fb26965034 --- /dev/null +++ b/snap/snapcraft.yaml @@ -0,0 +1,40 @@ +name: oidc-broker +summary: OIDC Broker for authd +description: | + Broker that enables OIDC authentication for authd. +version: git +grade: stable +base: core24 +confinement: strict +license: GPL-3.0+ + +apps: + oidc-broker: + command: bin/oidc-broker + daemon: simple + slots: + - dbus-oidc + plugs: + - network + - config-files + restart-condition: always + +slots: + dbus-oidc: + interface: dbus + bus: system + name: com.ubuntu.authd.OidcBroker + +plugs: + config-files: + interface: system-files + read: + - /etc/authd/brokers.d/oidc-broker + +parts: + oidc-broker: + source: . + source-type: local + plugin: go + build-snaps: + - go From 5bd8277a85b111fdb702eca2d3afa395a5d91ba5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 08:19:04 +0000 Subject: [PATCH 0042/1670] deps(go-tools): bump github.com/golangci/golangci-lint in /tools Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.59.0 to 1.59.1. - [Release notes](https://github.com/golangci/golangci-lint/releases) - [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md) - [Commits](https://github.com/golangci/golangci-lint/compare/v1.59.0...v1.59.1) --- updated-dependencies: - dependency-name: github.com/golangci/golangci-lint dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- tools/go.mod | 22 +++++++++++----------- tools/go.sum | 48 ++++++++++++++++++++++++------------------------ 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index 26bc2eef38..0b24c80e17 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -4,7 +4,7 @@ go 1.22.0 toolchain go1.22.4 -require github.com/golangci/golangci-lint v1.59.0 +require github.com/golangci/golangci-lint v1.59.1 require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect @@ -13,7 +13,7 @@ require ( github.com/Abirdcfly/dupword v0.0.14 // indirect github.com/Antonboom/errname v0.1.13 // indirect github.com/Antonboom/nilnil v0.1.9 // indirect - github.com/Antonboom/testifylint v1.3.0 // indirect + github.com/Antonboom/testifylint v1.3.1 // indirect github.com/BurntSushi/toml v1.4.0 // indirect github.com/Crocmagnon/fatcontext v0.2.2 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect @@ -59,14 +59,14 @@ require ( github.com/go-toolsmith/astp v1.1.0 // indirect github.com/go-toolsmith/strparse v1.1.0 // indirect github.com/go-toolsmith/typep v1.1.0 // indirect - github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect + github.com/go-viper/mapstructure/v2 v2.0.0 // indirect github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e // indirect - github.com/golangci/misspell v0.5.1 // indirect + github.com/golangci/misspell v0.6.0 // indirect github.com/golangci/modinfo v0.3.4 // indirect github.com/golangci/plugin-module-register v0.1.1 // indirect github.com/golangci/revgrep v0.5.3 // indirect @@ -119,7 +119,7 @@ require ( github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/polyfloyd/go-errorlint v1.5.1 // indirect + github.com/polyfloyd/go-errorlint v1.5.2 // indirect github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect @@ -134,7 +134,7 @@ require ( github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect - github.com/sashamelentyev/usestdlibvars v1.25.0 // indirect + github.com/sashamelentyev/usestdlibvars v1.26.0 // indirect github.com/securego/gosec/v2 v2.20.1-0.20240525090044-5f0084eb01a9 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect github.com/sirupsen/logrus v1.9.3 // indirect @@ -169,18 +169,18 @@ require ( github.com/ykadowak/zerologlint v0.1.5 // indirect gitlab.com/bosi/decorder v0.4.2 // indirect go-simpler.org/musttag v0.12.2 // indirect - go-simpler.org/sloglint v0.7.0 // indirect + go-simpler.org/sloglint v0.7.1 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/automaxprocs v1.5.3 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect - golang.org/x/mod v0.17.0 // indirect + golang.org/x/mod v0.18.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.20.0 // indirect + golang.org/x/sys v0.21.0 // indirect golang.org/x/text v0.15.0 // indirect - golang.org/x/tools v0.21.0 // indirect + golang.org/x/tools v0.22.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/ini.v1 v1.67.0 // indirect @@ -188,5 +188,5 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect honnef.co/go/tools v0.4.7 // indirect mvdan.cc/gofumpt v0.6.0 // indirect - mvdan.cc/unparam v0.0.0-20240427195214-063aff900ca1 // indirect + mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect ) diff --git a/tools/go.sum b/tools/go.sum index be04ca737e..d5558afe58 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -43,8 +43,8 @@ github.com/Antonboom/errname v0.1.13 h1:JHICqsewj/fNckzrfVSe+T33svwQxmjC+1ntDsHO github.com/Antonboom/errname v0.1.13/go.mod h1:uWyefRYRN54lBg6HseYCFhs6Qjcy41Y3Jl/dVhA87Ns= github.com/Antonboom/nilnil v0.1.9 h1:eKFMejSxPSA9eLSensFmjW2XTgTwJMjZ8hUHtV4s/SQ= github.com/Antonboom/nilnil v0.1.9/go.mod h1:iGe2rYwCq5/Me1khrysB4nwI7swQvjclR8/YRPl5ihQ= -github.com/Antonboom/testifylint v1.3.0 h1:UiqrddKs1W3YK8R0TUuWwrVKlVAnS07DTUVWWs9c+y4= -github.com/Antonboom/testifylint v1.3.0/go.mod h1:NV0hTlteCkViPW9mSR4wEMfwp+Hs1T3dY60bkvSfhpM= +github.com/Antonboom/testifylint v1.3.1 h1:Uam4q1Q+2b6H7gvk9RQFw6jyVDdpzIirFOOrbs14eG4= +github.com/Antonboom/testifylint v1.3.1/go.mod h1:NV0hTlteCkViPW9mSR4wEMfwp+Hs1T3dY60bkvSfhpM= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= @@ -184,8 +184,8 @@ github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQi github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= -github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c= -github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc= +github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U= github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= @@ -226,10 +226,10 @@ github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9 github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e h1:ULcKCDV1LOZPFxGZaA6TlQbiM3J2GCPnkx/bGF6sX/g= github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e/go.mod h1:Pm5KhLPA8gSnQwrQ6ukebRcapGb/BG9iUkdaiCcGHJM= -github.com/golangci/golangci-lint v1.59.0 h1:st69YDnAH/v2QXDcgUaZ0seQajHScPALBVkyitYLXEk= -github.com/golangci/golangci-lint v1.59.0/go.mod h1:QNA32UWdUdHXnu+Ap5/ZU4WVwyp2tL94UxEXrSErjg0= -github.com/golangci/misspell v0.5.1 h1:/SjR1clj5uDjNLwYzCahHwIOPmQgoH04AyQIiWGbhCM= -github.com/golangci/misspell v0.5.1/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= +github.com/golangci/golangci-lint v1.59.1 h1:CRRLu1JbhK5avLABFJ/OHVSQ0Ie5c4ulsOId1h3TTks= +github.com/golangci/golangci-lint v1.59.1/go.mod h1:jX5Oif4C7P0j9++YB2MMJmoNrb01NJ8ITqKWNLewThg= +github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= +github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= github.com/golangci/modinfo v0.3.4 h1:oU5huX3fbxqQXdfspamej74DFX0kyGLkw1ppvXoJ8GA= github.com/golangci/modinfo v0.3.4/go.mod h1:wytF1M5xl9u0ij8YSvhkEVPP3M5Mc7XLl1pxH3B2aUM= github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= @@ -416,8 +416,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polyfloyd/go-errorlint v1.5.1 h1:5gHxDjLyyWij7fhfrjYNNlHsUNQeyx0LFQKUelO3RBo= -github.com/polyfloyd/go-errorlint v1.5.1/go.mod h1:sH1QC1pxxi0fFecsVIzBmxtrgd9IF/SkJpA6wqyKAJs= +github.com/polyfloyd/go-errorlint v1.5.2 h1:SJhVik3Umsjh7mte1vE0fVZ5T1gznasQG3PV7U5xFdA= +github.com/polyfloyd/go-errorlint v1.5.2/go.mod h1:sH1QC1pxxi0fFecsVIzBmxtrgd9IF/SkJpA6wqyKAJs= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -466,8 +466,8 @@ github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6Ng github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= -github.com/sashamelentyev/usestdlibvars v1.25.0 h1:IK8SI2QyFzy/2OD2PYnhy84dpfNo9qADrRt6LH8vSzU= -github.com/sashamelentyev/usestdlibvars v1.25.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= +github.com/sashamelentyev/usestdlibvars v1.26.0 h1:LONR2hNVKxRmzIrZR0PhSF3mhCAzvnr+DcUiHgREfXE= +github.com/sashamelentyev/usestdlibvars v1.26.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= github.com/securego/gosec/v2 v2.20.1-0.20240525090044-5f0084eb01a9 h1:rnO6Zp1YMQwv8AyxzuwsVohljJgp4L0ZqiCgtACsPsc= github.com/securego/gosec/v2 v2.20.1-0.20240525090044-5f0084eb01a9/go.mod h1:dg7lPlu/xK/Ut9SedURCoZbVCR4yC7fM65DtH9/CDHs= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= @@ -566,8 +566,8 @@ go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= go-simpler.org/musttag v0.12.2 h1:J7lRc2ysXOq7eM8rwaTYnNrHd5JwjppzB6mScysB2Cs= go-simpler.org/musttag v0.12.2/go.mod h1:uN1DVIasMTQKk6XSik7yrJoEysGtR2GRqvWnI9S7TYM= -go-simpler.org/sloglint v0.7.0 h1:rMZRxD9MbaGoRFobIOicMxZzum7AXNFDlez6xxJs5V4= -go-simpler.org/sloglint v0.7.0/go.mod h1:g9SXiSWY0JJh4LS39/Q0GxzP/QX2cVcbTOYhDpXrJEs= +go-simpler.org/sloglint v0.7.1 h1:qlGLiqHbN5islOxjeLXoPtUdZXb669RW+BDQ+xOSNoU= +go-simpler.org/sloglint v0.7.1/go.mod h1:OlaVDRh/FKKd4X4sIMbsz8st97vomydceL146Fthh/c= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -635,8 +635,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -675,8 +675,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= 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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -752,8 +752,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -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/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -836,8 +836,8 @@ golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= -golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -949,8 +949,8 @@ honnef.co/go/tools v0.4.7 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs= honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo= mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA= -mvdan.cc/unparam v0.0.0-20240427195214-063aff900ca1 h1:Nykk7fggxChwLK4rUPYESzeIwqsuxXXlFEAh5YhaMRo= -mvdan.cc/unparam v0.0.0-20240427195214-063aff900ca1/go.mod h1:ZzZjEpJDOmx8TdVU6umamY3Xy0UAQUI2DHbf05USVbI= +mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f h1:lMpcwN6GxNbWtbpI1+xzFLSW8XzX0u72NttUGVFjO3U= +mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f/go.mod h1:RSLa7mKKCNeTTMHBw5Hsy2rfJmd6O2ivt9Dw9ZqCQpQ= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= From 42c8689bb1d9560b862430b4a14e71b4e64af0e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 08:50:53 +0000 Subject: [PATCH 0043/1670] deps(go): bump github.com/coreos/go-oidc/v3 from 3.9.0 to 3.10.0 Bumps [github.com/coreos/go-oidc/v3](https://github.com/coreos/go-oidc) from 3.9.0 to 3.10.0. - [Release notes](https://github.com/coreos/go-oidc/releases) - [Commits](https://github.com/coreos/go-oidc/compare/v3.9.0...v3.10.0) --- updated-dependencies: - dependency-name: github.com/coreos/go-oidc/v3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 23 ++++------------------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index bf219ebacc..0b88698936 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.22.4 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0 - github.com/coreos/go-oidc/v3 v3.9.0 + github.com/coreos/go-oidc/v3 v3.10.0 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/godbus/dbus/v5 v5.1.0 github.com/google/uuid v1.6.0 @@ -30,7 +30,7 @@ require ( github.com/cjlapao/common-go v0.0.39 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-jose/go-jose/v3 v3.0.3 // indirect + github.com/go-jose/go-jose/v4 v4.0.1 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/protobuf v1.5.4 // indirect diff --git a/go.sum b/go.sum index acca30cbed..35ce8ec30e 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aM github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc= github.com/cjlapao/common-go v0.0.39 h1:bAAUrj2B9v0kMzbAOhzjSmiyDy+rd56r2sy7oEiQLlA= github.com/cjlapao/common-go v0.0.39/go.mod h1:M3dzazLjTjEtZJbbxoA5ZDiGCiHmpwqW9l4UWaddwOA= -github.com/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo= -github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4= +github.com/coreos/go-oidc/v3 v3.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoEaJU= +github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -17,8 +17,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 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/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= -github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U= +github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -31,7 +31,6 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -127,25 +126,20 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 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.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -154,28 +148,19 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc 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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= From fc61e5e7688e8f116816663c8f8b37fcdf8010f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 12:28:17 +0000 Subject: [PATCH 0044/1670] deps(go): bump github.com/Azure/azure-sdk-for-go/sdk/azcore Bumps [github.com/Azure/azure-sdk-for-go/sdk/azcore](https://github.com/Azure/azure-sdk-for-go) from 1.10.0 to 1.12.0. - [Release notes](https://github.com/Azure/azure-sdk-for-go/releases) - [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/release.md) - [Commits](https://github.com/Azure/azure-sdk-for-go/compare/sdk/azcore/v1.10.0...sdk/azcore/v1.12.0) --- updated-dependencies: - dependency-name: github.com/Azure/azure-sdk-for-go/sdk/azcore dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 14 +++++++------- go.sum | 36 ++++++++++++++++++------------------ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/go.mod b/go.mod index 0b88698936..85bead15fd 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.0 toolchain go1.22.4 require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 github.com/coreos/go-oidc/v3 v3.10.0 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/godbus/dbus/v5 v5.1.0 @@ -18,7 +18,7 @@ require ( github.com/stretchr/testify v1.9.0 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 - golang.org/x/crypto v0.21.0 + golang.org/x/crypto v0.24.0 golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 golang.org/x/oauth2 v0.18.0 gopkg.in/ini.v1 v1.67.0 @@ -26,7 +26,7 @@ require ( ) require ( - github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 // indirect github.com/cjlapao/common-go v0.0.39 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect @@ -61,10 +61,10 @@ require ( go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.33.0 // indirect ) diff --git a/go.sum b/go.sum index 35ce8ec30e..d7d2cef80c 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0 h1:n1DH8TPV4qqPTje2RcUBYwtrTWlabVp4n46+74X2pn4= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.10.0/go.mod h1:HDcZnuGbiyppErN6lB+idp4CKhjbc8gwjto6OPpyggM= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aMclParm9/5Vgp+TY51uBQ= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 h1:1nGuui+4POelzDwI7RG56yfQJHCnKvwfMoU7VsEp+Zg= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0/go.mod h1:99EvauvlcJ1U06amZiksfYz/3aFGyIhWGHVyiZXtBAI= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 h1:H+U3Gk9zY56G3u872L82bk4thcsy2Gghb9ExT4Zvm1o= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0/go.mod h1:mgrmMSgaLp9hmax62XQTd0N4aAqSE5E0DulSpVYK7vc= github.com/cjlapao/common-go v0.0.39 h1:bAAUrj2B9v0kMzbAOhzjSmiyDy+rd56r2sy7oEiQLlA= github.com/cjlapao/common-go v0.0.39/go.mod h1:M3dzazLjTjEtZJbbxoA5ZDiGCiHmpwqW9l4UWaddwOA= github.com/coreos/go-oidc/v3 v3.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoEaJU= @@ -76,8 +76,8 @@ github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= @@ -126,38 +126,38 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-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.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -170,8 +170,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From e90263c540c72a4b19cc31801e31c729ebe461c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 12:38:32 +0000 Subject: [PATCH 0045/1670] deps(go): bump golang.org/x/oauth2 from 0.18.0 to 0.21.0 Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.18.0 to 0.21.0. - [Commits](https://github.com/golang/oauth2/compare/v0.18.0...v0.21.0) --- updated-dependencies: - dependency-name: golang.org/x/oauth2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 5 +---- go.sum | 16 ++-------------- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 85bead15fd..c1bd6fb359 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 golang.org/x/crypto v0.24.0 golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 - golang.org/x/oauth2 v0.18.0 + golang.org/x/oauth2 v0.21.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -33,7 +33,6 @@ require ( github.com/go-jose/go-jose/v4 v4.0.1 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/golang/protobuf v1.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 // indirect @@ -65,6 +64,4 @@ require ( golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.21.0 // indirect golang.org/x/text v0.16.0 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/protobuf v1.33.0 // indirect ) diff --git a/go.sum b/go.sum index d7d2cef80c..f1375b5b3b 100644 --- a/go.sum +++ b/go.sum @@ -26,11 +26,6 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -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.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -136,8 +131,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= @@ -162,13 +157,6 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -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.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From a8cf9b9449c7b083ac8c42c01606282dc0bbe0f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 12:38:47 +0000 Subject: [PATCH 0046/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.36.0 to 1.45.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.36.0...v1.45.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 85bead15fd..8583f96086 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/godbus/dbus/v5 v5.1.0 github.com/google/uuid v1.6.0 - github.com/microsoftgraph/msgraph-sdk-go v1.36.0 + github.com/microsoftgraph/msgraph-sdk-go v1.45.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.1.0 github.com/otiai10/copy v1.14.0 github.com/spf13/cobra v1.8.0 @@ -38,11 +38,11 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/microsoft/kiota-abstractions-go v1.5.6 // indirect + github.com/microsoft/kiota-abstractions-go v1.6.0 // indirect github.com/microsoft/kiota-authentication-azure-go v1.0.2 // indirect github.com/microsoft/kiota-http-go v1.3.2 // indirect github.com/microsoft/kiota-serialization-form-go v1.0.0 // indirect - github.com/microsoft/kiota-serialization-json-go v1.0.6 // indirect + github.com/microsoft/kiota-serialization-json-go v1.0.7 // indirect github.com/microsoft/kiota-serialization-multipart-go v1.0.0 // indirect github.com/microsoft/kiota-serialization-text-go v1.0.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect @@ -55,7 +55,7 @@ require ( github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/std-uritemplate/std-uritemplate/go v0.0.54 // indirect + github.com/std-uritemplate/std-uritemplate/go v0.0.55 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect diff --git a/go.sum b/go.sum index d7d2cef80c..a757ab96a7 100644 --- a/go.sum +++ b/go.sum @@ -47,22 +47,22 @@ github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 h1:9ZM/t5 github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069/go.mod h1:AT4NpQrOmyj1L/+hLja6aR0lk81yYYL4ePnj2kp7d6M= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/microsoft/kiota-abstractions-go v1.5.6 h1:3hd1sACWB2B9grv8KG1T8g/gGQ4A8kTLv91OUxHSxkE= -github.com/microsoft/kiota-abstractions-go v1.5.6/go.mod h1:2WX7Oh8V9SAdZ80OGeE53rcbdys54Pd38rAeDUghrpM= +github.com/microsoft/kiota-abstractions-go v1.6.0 h1:qbGBNMU0/o5myKbikCBXJFohVCFrrpx2cO15Rta2WyA= +github.com/microsoft/kiota-abstractions-go v1.6.0/go.mod h1:7YH20ZbRWXGfHSSvdHkdztzgCB9mRdtFx13+hrYIEpo= github.com/microsoft/kiota-authentication-azure-go v1.0.2 h1:tClGeyFZJ+4Bakf8u0euPM4wqy4ethycdOgx3jyH3pI= github.com/microsoft/kiota-authentication-azure-go v1.0.2/go.mod h1:aTcti0bUJEcq7kBfQG4Sr4ElvRNuaalXcFEu4iEyQ6M= github.com/microsoft/kiota-http-go v1.3.2 h1:HSrbw6MDMmgLaqU1qT3SgdmD/18KPW2usfyUtE017Lw= github.com/microsoft/kiota-http-go v1.3.2/go.mod h1:pEs3eLpCJBhsesjtvM/3T4ODKCofPgDlDsGHHetau2g= github.com/microsoft/kiota-serialization-form-go v1.0.0 h1:UNdrkMnLFqUCccQZerKjblsyVgifS11b3WCx+eFEsAI= github.com/microsoft/kiota-serialization-form-go v1.0.0/go.mod h1:h4mQOO6KVTNciMF6azi1J9QB19ujSw3ULKcSNyXXOMA= -github.com/microsoft/kiota-serialization-json-go v1.0.6 h1:8v8IXMGurLCRYZs1l0Ck75lN0wzKDLko69mNdQGVWeQ= -github.com/microsoft/kiota-serialization-json-go v1.0.6/go.mod h1:I0CiXKgvKDFOO35lQ5VpYmd2nFLXHdJUsHnG8z/TX7A= +github.com/microsoft/kiota-serialization-json-go v1.0.7 h1:yMbckSTPrjZdM4EMXgzLZSA3CtDaUBI350u0VoYRz7Y= +github.com/microsoft/kiota-serialization-json-go v1.0.7/go.mod h1:1krrY7DYl3ivPIzl4xTaBpew6akYNa8/Tal8g+kb0cc= github.com/microsoft/kiota-serialization-multipart-go v1.0.0 h1:3O5sb5Zj+moLBiJympbXNaeV07K0d46IfuEd5v9+pBs= github.com/microsoft/kiota-serialization-multipart-go v1.0.0/go.mod h1:yauLeBTpANk4L03XD985akNysG24SnRJGaveZf+p4so= github.com/microsoft/kiota-serialization-text-go v1.0.0 h1:XOaRhAXy+g8ZVpcq7x7a0jlETWnWrEum0RhmbYrTFnA= github.com/microsoft/kiota-serialization-text-go v1.0.0/go.mod h1:sM1/C6ecnQ7IquQOGUrUldaO5wj+9+v7G2W3sQ3fy6M= -github.com/microsoftgraph/msgraph-sdk-go v1.36.0 h1:DFofL21syrHLa1C71kri9z5GfPxMu8vl2NXsKybJwyw= -github.com/microsoftgraph/msgraph-sdk-go v1.36.0/go.mod h1:LRiv9UP/7NFLvhQGKHWSMDN2eLSg5u5IJ6FUpigDVX8= +github.com/microsoftgraph/msgraph-sdk-go v1.45.0 h1:PRE3nsGDlASfoM1h6QNCBXmU34LTiReg5LMBjRKOL3k= +github.com/microsoftgraph/msgraph-sdk-go v1.45.0/go.mod h1:MSMgjuMPKAsIz8XfH5l+e781fkWjUxc1XXhb2eoSdc0= github.com/microsoftgraph/msgraph-sdk-go-core v1.1.0 h1:NB7c/n4Knj+TLaLfjsahhSqoUqoN/CtyNB0XIe/nJnM= github.com/microsoftgraph/msgraph-sdk-go-core v1.1.0/go.mod h1:M3w/5IFJ1u/DpwOyjsjNSVEA43y1rLOeX58suyfBhGk= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -97,8 +97,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= -github.com/std-uritemplate/std-uritemplate/go v0.0.54 h1:8t7J7tNuMDj4Vkqq+IRENQDZTZzXJZZbN+iD60PEiAs= -github.com/std-uritemplate/std-uritemplate/go v0.0.54/go.mod h1:CLZ1543WRCuUQQjK0BvPM4QrG2toY8xNZUm8Vbt7vTc= +github.com/std-uritemplate/std-uritemplate/go v0.0.55 h1:muSH037g97K7U2f94G9LUuE8tZlJsoSSrPsO9V281WY= +github.com/std-uritemplate/std-uritemplate/go v0.0.55/go.mod h1:rG/bqh/ThY4xE5de7Rap3vaDkYUT76B0GPJ0loYeTTc= github.com/stretchr/objx v0.1.0/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= From bb9239f06684cf400e4cd3130ef92fe2d3092659 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Mon, 10 Jun 2024 12:23:23 -0400 Subject: [PATCH 0047/1670] Improve messages on auth modes labels and contents --- internal/broker/broker.go | 8 ++++---- ...eady_authenticated_with_password_and_session_is_passwd | 2 +- .../get_newpassword_if_already_authenticated_with_qrcode | 2 +- ...et_only_password_if_token_exists_and_session_is_passwd | 2 +- .../golden/get_password_and_qrcode_if_token_exists | 2 +- .../golden/successfully_select_newpassword | 2 +- .../golden/successfully_select_password | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 2af589c48c..2f89c220f5 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -259,12 +259,12 @@ func (b *Broker) supportedAuthModesFromLayout(supportedUILayouts []map[string]st case "form": if slices.Contains(supportedEntries, "chars_password") { - supportedModes["password"] = "Password Authentication" + supportedModes["password"] = "Local Password Authentication" } case "newpassword": if slices.Contains(supportedEntries, "chars_password") { - supportedModes["newpassword"] = "Define your password" + supportedModes["newpassword"] = "Define your local password" } } } @@ -329,14 +329,14 @@ func (b *Broker) generateUILayout(session *sessionInfo, authModeID string) (map[ case "password": uiLayout = map[string]string{ "type": "form", - "label": "Gimme your password", + "label": "Enter your local password", "entry": "chars_password", } case "newpassword": uiLayout = map[string]string{ "type": "newpassword", - "label": "Enter your password", + "label": "Create a local password", "entry": "chars_password", } } diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_password_and_session_is_passwd b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_password_and_session_is_passwd index 114712a3d6..1bf8f6dae4 100644 --- a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_password_and_session_is_passwd +++ b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_password_and_session_is_passwd @@ -1,2 +1,2 @@ - id: newpassword - label: Define your password + label: Define your local password diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_qrcode b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_qrcode index 114712a3d6..1bf8f6dae4 100644 --- a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_qrcode +++ b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_qrcode @@ -1,2 +1,2 @@ - id: newpassword - label: Define your password + label: Define your local password diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_session_is_passwd b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_session_is_passwd index bf063a882a..e4e7cb1d35 100644 --- a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_session_is_passwd +++ b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_session_is_passwd @@ -1,2 +1,2 @@ - id: password - label: Password Authentication + label: Local Password Authentication diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_password_and_qrcode_if_token_exists b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_password_and_qrcode_if_token_exists index f7104c5526..6c2b078bee 100644 --- a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_password_and_qrcode_if_token_exists +++ b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_password_and_qrcode_if_token_exists @@ -1,4 +1,4 @@ - id: password - label: Password Authentication + label: Local Password Authentication - id: qrcode label: Device Authentication diff --git a/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_newpassword b/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_newpassword index 0c159c9c01..246a5f3b8d 100644 --- a/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_newpassword +++ b/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_newpassword @@ -1,3 +1,3 @@ entry: chars_password -label: Enter your password +label: Create a local password type: newpassword diff --git a/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_password b/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_password index 762980395b..f5c748db40 100644 --- a/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_password +++ b/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_password @@ -1,3 +1,3 @@ entry: chars_password -label: Gimme your password +label: Enter your local password type: form From 7eda89cb1579875fadcac74b37df0e7476aadbe5 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Mon, 10 Jun 2024 12:26:56 -0400 Subject: [PATCH 0048/1670] No longer redact error messages We used to redact the error messages to avoid leaking sensitive information. By looking more carefully into the error messages, it's not necessary to redact them as they are not too sensitive. --- internal/broker/broker.go | 42 +++++-------------- internal/broker/broker_test.go | 6 ++- .../first_call | 2 +- .../second_call | 2 +- .../first_call | 2 +- .../first_call | 2 +- .../first_call | 4 +- .../first_call | 2 +- 8 files changed, 22 insertions(+), 40 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 2f89c220f5..6c52ab4e32 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -309,8 +309,7 @@ func (b *Broker) generateUILayout(session *sessionInfo, authModeID string) (map[ case "qrcode": response, err := b.auth.oauthCfg.DeviceAuth(context.TODO()) if err != nil { - slog.Error(fmt.Sprintf("Error getting device auth: %v", err)) - return nil, errors.New("could not generate QR code layout") + return nil, fmt.Errorf("could not generate QR code layout: %v", err) } session.authInfo["response"] = response @@ -354,7 +353,7 @@ func (b *Broker) IsAuthenticated(sessionID, authenticationData string) (string, var authData map[string]string if authenticationData != "" { if err := json.Unmarshal([]byte(authenticationData), &authData); err != nil { - return AuthDenied, "", errors.New("authentication data is not a valid json value") + return AuthDenied, "", fmt.Errorf("authentication data is not a valid json value: %v", err) } } @@ -457,7 +456,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo authInfo.UserInfo, err = b.userInfoFromClaims(userClaims, groups) if err != nil { - return AuthDenied, fmt.Sprintf(`{"message": "could not get user info: %v"}`, err) + return AuthDenied, fmt.Sprintf(`{"message": "could not parse user info from claims: %v"}`, err) } } @@ -466,7 +465,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo } if err := b.cacheAuthInfo(session, authInfo, challenge); err != nil { - return AuthRetry, fmt.Sprintf(`{"message": "could not update token: %v"}`, err) + return AuthRetry, fmt.Sprintf(`{"message": "could not update cached info: %v"}`, err) } return AuthGranted, fmt.Sprintf(`{"userinfo": %s}`, authInfo.UserInfo) @@ -563,14 +562,7 @@ type authCachedInfo struct { // cacheAuthInfo serializes the access token and cache it. func (b *Broker) cacheAuthInfo(session *sessionInfo, authInfo authCachedInfo, password string) (err error) { - defer func() { - // Override the error so that we don't leak information. Also, abstract it for the user. - // We still log as error for the admin to get access. - if err != nil { - slog.Error(fmt.Sprintf("Error when caching token: %v", err)) - err = errors.New("could not cache token") - } - }() + defer decorate.OnError(&err, "could not cache info") authInfo.AcquiredAt = time.Now() content, err := json.Marshal(authInfo) @@ -597,14 +589,7 @@ func (b *Broker) cacheAuthInfo(session *sessionInfo, authInfo authCachedInfo, pa // loadAuthInfo deserializes the token from the cache and refreshes it if needed. func (b *Broker) loadAuthInfo(session *sessionInfo, password string) (loadedInfo authCachedInfo, offline bool, err error) { - defer func() { - // Override the error so that we don't leak information. Also, abstract it for the user. - // We still log as error for the admin to get access. - if err != nil { - slog.Error(fmt.Sprintf("Error when loading token: %v", err)) - err = errors.New("could not load token") - } - }() + defer decorate.OnError(&err, "could not load cached info") s, err := os.ReadFile(session.cachePath) if err != nil { @@ -613,12 +598,12 @@ func (b *Broker) loadAuthInfo(session *sessionInfo, password string) (loadedInfo deserialized, err := decrypt(s, []byte(password)) if err != nil { - return authCachedInfo{}, false, fmt.Errorf("could not deserializing token: %v", err) + return authCachedInfo{}, false, fmt.Errorf("could not deserialize token: %v", err) } var cachedInfo authCachedInfo if err := json.Unmarshal(deserialized, &cachedInfo); err != nil { - return authCachedInfo{}, false, fmt.Errorf("could not unmarshaling token: %v", err) + return authCachedInfo{}, false, fmt.Errorf("could not unmarshal token: %v", err) } // If the token is not expired, we should use the cached information. @@ -655,14 +640,7 @@ func (b *Broker) loadAuthInfo(session *sessionInfo, password string) (loadedInfo } func (b *Broker) fetchUserInfo(ctx context.Context, session *sessionInfo, t *authCachedInfo) (userClaims claims, userGroups []group.Info, err error) { - defer func() { - // Override the error so that we don't leak information. Also, abstract it for the user. - // We still log as error for the admin to get access. - if err != nil { - slog.Error(fmt.Sprintf("Error when fetching user info: %v", err)) - err = errors.New("could not fetch user info") - } - }() + defer decorate.OnError(&err, "could not fetch user info") // If we didn't restore user information from the cache, we need to query the provider for it, which means // we need to validate the token. @@ -681,7 +659,7 @@ func (b *Broker) fetchUserInfo(ctx context.Context, session *sessionInfo, t *aut } if userClaims.Email == "" { - return claims{}, nil, errors.New("invalid claims") + return claims{}, nil, errors.New("user email is required, but was not provided") } if userClaims.Email != session.username { diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 2d160ab372..4594aded76 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -412,8 +412,9 @@ func TestIsAuthenticated(t *testing.T) { access, data, err := b.IsAuthenticated(sessionID, authData) - // Redact variant session id from the response + // Redact variant values from the response data = strings.ReplaceAll(data, sessionID, "SESSION_ID") + data = strings.ReplaceAll(data, filepath.Dir(b.TokenPathForSession(sessionID)), "provider_cache_path") errStr := strings.ReplaceAll(fmt.Sprintf("%v", err), sessionID, "SESSION_ID") got := isAuthenticatedResponse{Access: access, Data: data, Err: errStr} @@ -446,8 +447,9 @@ func TestIsAuthenticated(t *testing.T) { access, data, err := b.IsAuthenticated(sessionID, secondAuthData) - // Redact variant session id from the response + // Redact variant values from the response data = strings.ReplaceAll(data, sessionID, "SESSION_ID") + data = strings.ReplaceAll(data, filepath.Dir(b.TokenPathForSession(sessionID)), "provider_cache_path") errStr := strings.ReplaceAll(fmt.Sprintf("%v", err), sessionID, "SESSION_ID") got := isAuthenticatedResponse{Access: access, Data: data, Err: errStr} diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_authentication_data_is_invalid/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_authentication_data_is_invalid/first_call index aff48e05fa..fe56876788 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_authentication_data_is_invalid/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_authentication_data_is_invalid/first_call @@ -1,3 +1,3 @@ access: denied data: "" -err: authentication data is not a valid json value +err: 'authentication data is not a valid json value: invalid character ''i'' looking for beginning of value' diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/second_call index 2f9ee1cdd8..def12e2d2f 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/second_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/second_call @@ -1,3 +1,3 @@ access: retry -data: '{"message": "could not update token: could not cache token"}' +data: '{"message": "could not update cached info: could not cache info: could not create token directory: mkdir provider_cache_path: permission denied"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_server_offline_and_token_expired/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_server_offline_and_token_expired/first_call index 46a2042d78..0baccf0161 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_server_offline_and_token_expired/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_server_offline_and_token_expired/first_call @@ -1,3 +1,3 @@ access: retry -data: '{"message": "could not authenticate user: could not load token"}' +data: '{"message": "could not authenticate user: could not load cached info: token exceeded offline expiration"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call index 46a2042d78..21f07da868 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call @@ -1,3 +1,3 @@ access: retry -data: '{"message": "could not authenticate user: could not load token"}' +data: '{"message": "could not authenticate user: could not load cached info: could not read token: open provider_cache_path/test-user@email.com.cache: no such file or directory"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_offline-valid_but_server_returns_error/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_offline-valid_but_server_returns_error/first_call index 46a2042d78..86c05dcda3 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_offline-valid_but_server_returns_error/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_offline-valid_but_server_returns_error/first_call @@ -1,3 +1,5 @@ access: retry -data: '{"message": "could not authenticate user: could not load token"}' +data: |- + {"message": "could not authenticate user: could not load cached info: could not refresh token: oauth2: cannot fetch token: 400 Bad Request + Response: "} err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/first_call index 46a2042d78..c010949675 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/first_call @@ -1,3 +1,3 @@ access: retry -data: '{"message": "could not authenticate user: could not load token"}' +data: '{"message": "could not authenticate user: could not load cached info: could not deserialize token: cipher: message authentication failed"}' err: From 70ed1f967a90922cf090a076d072eaa08ef9a5bb Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Mon, 10 Jun 2024 12:28:41 -0400 Subject: [PATCH 0049/1670] Fix some bad behaviors in the broker and the tests After exposing the error messages, it was possible to spot some bad tests that were not testing exactly what they should. This commits fixes them. --- internal/broker/broker.go | 2 +- internal/broker/broker_test.go | 5 +++- internal/broker/export_test.go | 29 +++++++++++++++++-- internal/broker/helper_test.go | 6 ++-- .../first_call | 2 +- .../first_call | 2 +- .../first_call | 2 +- 7 files changed, 37 insertions(+), 11 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 6c52ab4e32..90254e3328 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -616,7 +616,7 @@ func (b *Broker) loadAuthInfo(session *sessionInfo, password string) (loadedInfo tok, err := b.auth.oauthCfg.TokenSource(context.Background(), cachedInfo.Token).Token() if err != nil { castErr := &oauth2.RetrieveError{} - if errors.As(err, &castErr) && castErr.Response.StatusCode != http.StatusServiceUnavailable { + if !errors.As(err, &castErr) || castErr.Response.StatusCode != http.StatusServiceUnavailable { return authCachedInfo{}, false, fmt.Errorf("could not refresh token: %v", err) } diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 4594aded76..8fbc300306 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -556,8 +556,11 @@ func TestFetchUserInfo(t *testing.T) { require.NoError(t, err, "Setup: Failed to create session for the tests") cachedInfo := generateCachedInfo(t, tc.userToken, defaultProvider.URL) + if cachedInfo == nil { + cachedInfo = &broker.AuthCachedInfo{} + } - got, err := b.FetchUserInfo(sessionID, &cachedInfo) + got, err := b.FetchUserInfo(sessionID, cachedInfo) if tc.wantErr { require.Error(t, err, "FetchUserInfo should have returned an error") return diff --git a/internal/broker/export_test.go b/internal/broker/export_test.go index 66a5f65d37..be3b96f692 100644 --- a/internal/broker/export_test.go +++ b/internal/broker/export_test.go @@ -1,6 +1,11 @@ package broker -import "context" +import ( + "context" + "fmt" + "os" + "path/filepath" +) // TokenPathForSession returns the path to the token file for the given session. func (b *Broker) TokenPathForSession(sessionID string) string { @@ -52,12 +57,30 @@ func (b *Broker) SetAuthInfo(sessionID, key string, value any) error { type AuthCachedInfo = authCachedInfo // CacheAuthInfo exposes the broker's cacheAuthInfo method for tests. -func (b *Broker) CacheAuthInfo(sessionID string, token authCachedInfo, password string) error { +func (b *Broker) CacheAuthInfo(sessionID string, token *authCachedInfo, password string) error { s, err := b.getSession(sessionID) if err != nil { return err } - return b.cacheAuthInfo(&s, token, password) + + if token == nil { + return writeTrashToken(s.cachePath, password) + } + + return b.cacheAuthInfo(&s, *token, password) +} + +func writeTrashToken(path, challenge string) error { + content, err := encrypt([]byte("This is a trash token that is not valid for authentication"), []byte(challenge)) + if err != nil { + return err + } + // Create issuer specific cache directory if it doesn't exist. + if err = os.MkdirAll(filepath.Dir(path), 0700); err != nil { + return fmt.Errorf("could not create token directory: %v", err) + } + + return os.WriteFile(path, content, 0600) } // FetchUserInfo exposes the broker's fetchUserInfo method for tests. diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index 84c8fa6754..42f4bdaa29 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -102,11 +102,11 @@ var testTokens = map[string]broker.AuthCachedInfo{ }, } -func generateCachedInfo(t *testing.T, preexistentToken, issuer string) broker.AuthCachedInfo { +func generateCachedInfo(t *testing.T, preexistentToken, issuer string) *broker.AuthCachedInfo { t.Helper() if preexistentToken == "invalid" { - return broker.AuthCachedInfo{} + return nil } var email string @@ -153,5 +153,5 @@ func generateCachedInfo(t *testing.T, preexistentToken, issuer string) broker.Au tok.Token = tok.Token.WithExtra(map[string]string{"id_token": encodedToken}) tok.RawIDToken = encodedToken - return tok + return &tok } diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/first_call index 46a2042d78..873b615cbd 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/first_call @@ -1,3 +1,3 @@ access: retry -data: '{"message": "could not authenticate user: could not load token"}' +data: '{"message": "could not authenticate user: could not load cached info: could not refresh token: oauth2: token expired and refresh token is not set"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/first_call index 86ec24c5e1..292abd2291 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/first_call @@ -1,3 +1,3 @@ access: denied -data: '{"message": "could not get user info: could not fetch user info"}' +data: '{"message": "could not get user info: could not fetch user info: could not verify token: oidc: failed to unmarshal claims: invalid character ''\u008a'' looking for beginning of value"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/first_call index 46a2042d78..9a155b08ed 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/first_call @@ -1,3 +1,3 @@ access: retry -data: '{"message": "could not authenticate user: could not load token"}' +data: '{"message": "could not authenticate user: could not load cached info: could not unmarshal token: invalid character ''T'' looking for beginning of value"}' err: From a7918f8a3b326b872c35c97453fb87d3629dee15 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Wed, 12 Jun 2024 11:47:00 -0400 Subject: [PATCH 0050/1670] Add CI to autorebase branch Co-authored-by: Didier Roche-Tolomelli --- .github/workflows/auto-updates.yml | 76 ++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 .github/workflows/auto-updates.yml diff --git a/.github/workflows/auto-updates.yml b/.github/workflows/auto-updates.yml new file mode 100644 index 0000000000..5bc3182a4e --- /dev/null +++ b/.github/workflows/auto-updates.yml @@ -0,0 +1,76 @@ +name: Auto update broker variant branches +on: + push: + branches: + - main +concurrency: auto-update-broker-variants + +permissions: + pull-requests: write + contents: write + +env: + DEBIAN_FRONTEND: noninteractive + +jobs: + update-snap-branches: + name: Update snap branches + strategy: + matrix: + branch_name: ["msentraid-broker"] + runs-on: ubuntu-latest + steps: + - name: Install dependencies + run: | + set -eu + sudo apt update + sudo apt install -y git + - uses: actions/checkout@v4 + with: + ref: main + fetch-depth: 0 + - name: Merge main into branches + id: merge + run: | + set -eux + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git fetch + git checkout ${{ matrix.branch_name }} + + # First, assume that we will have conflicts due to the merge command + # failing the action if there's any. + echo "has_conflicts=true" >> $GITHUB_OUTPUT + has_conflicts=true + if git merge main --commit; then + has_conflicts=false + fi + + echo "has_conflicts=${has_conflicts}" >> $GITHUB_OUTPUT + - name: Push branch + if: ${{ steps.merge.outputs.has_conflicts == 'false' }} + run: | + set -eux + git push origin ${{ matrix.branch_name }} + + # Potentially existing PR with conflicts is not valid anymore: we just automerged. + git push origin --delete update-${{ matrix.branch_name }} || true + - name: Restore and prepare branch + if: ${{ steps.merge.outputs.has_conflicts == 'true' }} + run: | + set -eux + # Reset the state of the current destination + git merge --abort + # Apply the changes we want to merge (which is the content of main) + git reset --hard main + - name: Create Pull Request + if: ${{ steps.merge.outputs.has_conflicts == 'true' }} + uses: peter-evans/create-pull-request@v6 + with: + commit-message: Auto update ${{ matrix.branch_name }} branch + title: Auto update ${{ matrix.branch_name }} branch + body: | + Pull request created due to conflicts found when merging main into ${{ matrix.branch_name }}. + branch: update-${{ matrix.branch_name }} + delete-branch: true + token: ${{ secrets.GITHUB_TOKEN }} From fc78293318001b14c06ad39593cb6c6dfcfccd47 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 08:31:02 +0000 Subject: [PATCH 0051/1670] deps(go): bump github.com/spf13/cobra from 1.8.0 to 1.8.1 Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.8.0 to 1.8.1. - [Release notes](https://github.com/spf13/cobra/releases) - [Commits](https://github.com/spf13/cobra/compare/v1.8.0...v1.8.1) --- updated-dependencies: - dependency-name: github.com/spf13/cobra dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 491ccdf4f6..3f42cc2888 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/microsoftgraph/msgraph-sdk-go v1.45.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.1.0 github.com/otiai10/copy v1.14.0 - github.com/spf13/cobra v1.8.0 + github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 diff --git a/go.sum b/go.sum index 4c2c9f2e15..9b9ff7ce15 100644 --- a/go.sum +++ b/go.sum @@ -8,7 +8,7 @@ github.com/coreos/go-oidc/v3 v3.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoE github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -86,8 +86,8 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= From 70f110d1b3c05c0e05190ad6cc15792f9f67a0a0 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 18 Jun 2024 07:53:00 -0400 Subject: [PATCH 0052/1670] Remove offline expiration from broker config --- config/oidc-broker | 3 --- internal/broker/broker.go | 36 +++++++++-------------------- internal/broker/broker_test.go | 36 +++++++++++++---------------- internal/broker/helper_test.go | 9 ++++---- internal/dbusservice/consts.go | 2 -- internal/dbusservice/dbusservice.go | 9 ++++---- 6 files changed, 35 insertions(+), 60 deletions(-) diff --git a/config/oidc-broker b/config/oidc-broker index d62104f1ef..3555afa3d6 100644 --- a/config/oidc-broker +++ b/config/oidc-broker @@ -8,9 +8,6 @@ dbus_object = /com/ubuntu/authd/OidcBroker issuer = https://{issuer_url} client_id = {client_id} -# The amount of days the user will be allowed to authenticate without a network connection. -offline_expiration = 180 - # The directory where the user's home directory will be created. # The user home directory will be created in the format of $homedir_path/$username home_base_dir = /home diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 90254e3328..785b3e0a72 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -15,7 +15,6 @@ import ( "os" "path/filepath" "slices" - "strconv" "strings" "sync" "text/template" @@ -34,19 +33,17 @@ const maxAuthAttempts = 3 // Config is the configuration for the broker. type Config struct { - IssuerURL string - ClientID string - CachePath string - OfflineExpiration string - HomeBaseDir string + IssuerURL string + ClientID string + CachePath string + HomeBaseDir string } // Broker is the real implementation of the broker to track sessions and process oidc calls. type Broker struct { - providerInfo providers.ProviderInfoer - auth authConfig - offlineExpiration time.Duration - homeDirPath string + providerInfo providers.ProviderInfoer + auth authConfig + homeDirPath string currentSessions map[string]sessionInfo currentSessionsMu sync.RWMutex @@ -119,16 +116,6 @@ func New(cfg Config, args ...Option) (b *Broker, err error) { return &Broker{}, errors.New("issuer and client ID must be provided") } - // Set offline expiration - var offlineExpiration time.Duration - if cfg.OfflineExpiration != "" { - intValue, err := strconv.Atoi(cfg.OfflineExpiration) - if err != nil { - return &Broker{}, fmt.Errorf("could not parse offline expiration: %v", err) - } - offlineExpiration = time.Duration(intValue*24) * time.Hour - } - homeDirPath := "/home" if cfg.HomeBaseDir != "" { homeDirPath = cfg.HomeBaseDir @@ -163,11 +150,10 @@ func New(cfg Config, args ...Option) (b *Broker, err error) { } return &Broker{ - providerInfo: opts.providerInfo, - auth: authCfg, - offlineExpiration: offlineExpiration, - homeDirPath: homeDirPath, - privateKey: privateKey, + providerInfo: opts.providerInfo, + auth: authCfg, + homeDirPath: homeDirPath, + privateKey: privateKey, currentSessions: make(map[string]sessionInfo), currentSessionsMu: sync.RWMutex{}, diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 8fbc300306..bf8da7ab32 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -25,21 +25,18 @@ func TestNew(t *testing.T) { t.Parallel() tests := map[string]struct { - issuer string - clientID string - cachePath string - offlineExpiration string + issuer string + clientID string + cachePath string wantErr bool }{ - "Successfully create new broker": {}, - "Successfully create new broker with offline expiration": {offlineExpiration: "10"}, - - "Error if issuer is not provided": {issuer: "-", wantErr: true}, - "Error if provided issuer is not reachable": {issuer: "https://notavailable", wantErr: true}, - "Error if clientID is not provided": {clientID: "-", wantErr: true}, - "Error if cacheDir is not provided": {cachePath: "-", wantErr: true}, - "Error if offline expiration is provided but not parseable": {offlineExpiration: "invalid", wantErr: true}, + "Successfully create new broker": {}, + + "Error if issuer is not provided": {issuer: "-", wantErr: true}, + "Error if provided issuer is not reachable": {issuer: "https://notavailable", wantErr: true}, + "Error if clientID is not provided": {clientID: "-", wantErr: true}, + "Error if cacheDir is not provided": {cachePath: "-", wantErr: true}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { @@ -65,10 +62,9 @@ func TestNew(t *testing.T) { } bCfg := broker.Config{ - IssuerURL: tc.issuer, - ClientID: tc.clientID, - CachePath: tc.cachePath, - OfflineExpiration: tc.offlineExpiration, + IssuerURL: tc.issuer, + ClientID: tc.clientID, + CachePath: tc.cachePath, } b, err := broker.New(bCfg) if tc.wantErr { @@ -147,7 +143,7 @@ func TestGetAuthenticationModes(t *testing.T) { tc.sessionMode = "auth" } - b, sessionID, _ := newBrokerForTests(t, t.TempDir(), defaultProvider.URL, "", tc.sessionMode) + b, sessionID, _ := newBrokerForTests(t, t.TempDir(), defaultProvider.URL, tc.sessionMode) if tc.sessionID == "-" { sessionID = "" @@ -225,7 +221,7 @@ func TestSelectAuthenticationMode(t *testing.T) { provider = p } - b, sessionID, _ := newBrokerForTests(t, t.TempDir(), provider.URL, "", "auth") + b, sessionID, _ := newBrokerForTests(t, t.TempDir(), provider.URL, "auth") // We need to do a GAM call first to get all the modes. _, err := b.GetAuthenticationModes(sessionID, supportedLayouts) @@ -580,7 +576,7 @@ func TestCancelIsAuthenticated(t *testing.T) { provider, cleanup := testutils.StartMockProvider(testutils.WithHandler("/token", testutils.HangingHandler(ctx))) t.Cleanup(cleanup) - b, sessionID, _ := newBrokerForTests(t, t.TempDir(), provider.URL, "", "auth") + b, sessionID, _ := newBrokerForTests(t, t.TempDir(), provider.URL, "auth") updateAuthModes(t, b, sessionID, "qrcode") stopped := make(chan struct{}) @@ -601,7 +597,7 @@ func TestCancelIsAuthenticated(t *testing.T) { func TestEndSession(t *testing.T) { t.Parallel() - b, sessionID, _ := newBrokerForTests(t, t.TempDir(), defaultProvider.URL, "", "auth") + b, sessionID, _ := newBrokerForTests(t, t.TempDir(), defaultProvider.URL, "auth") // Try to end a session that does not exist err := b.EndSession("nonexistent") diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index 42f4bdaa29..c0b9ef9b12 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -19,14 +19,13 @@ import ( // newBrokerForTests is a helper function to create a new broker for tests and already starts a session for user // t.Name() normalized. -func newBrokerForTests(t *testing.T, cachePath, issuerURL, offlineExpiration, mode string) (b *broker.Broker, id, key string) { +func newBrokerForTests(t *testing.T, cachePath, issuerURL, mode string) (b *broker.Broker, id, key string) { t.Helper() cfg := broker.Config{ - IssuerURL: issuerURL, - ClientID: "test-client-id", - CachePath: cachePath, - OfflineExpiration: offlineExpiration, + IssuerURL: issuerURL, + ClientID: "test-client-id", + CachePath: cachePath, } b, err := broker.New( diff --git a/internal/dbusservice/consts.go b/internal/dbusservice/consts.go index d0629cf4fc..845b657ec0 100644 --- a/internal/dbusservice/consts.go +++ b/internal/dbusservice/consts.go @@ -15,8 +15,6 @@ const ( issuerKey = "issuer" // clientIDKey is the key in the config file for the client ID. clientIDKey = "client_id" - // offlineExpirationKey is the key in the config file for the offline expiration. - offlineExpirationKey = "offline_expiration" // homeDirKey is the key in the config file for the home directory prefix. homeDirKey = "home_base_dir" ) diff --git a/internal/dbusservice/dbusservice.go b/internal/dbusservice/dbusservice.go index a714a69ab6..db566981c0 100644 --- a/internal/dbusservice/dbusservice.go +++ b/internal/dbusservice/dbusservice.go @@ -73,11 +73,10 @@ func New(_ context.Context, cfgPath, cachePath string) (s *Service, err error) { } bCfg := broker.Config{ - IssuerURL: cfg[oidcSection][issuerKey], - ClientID: cfg[oidcSection][clientIDKey], - OfflineExpiration: cfg[oidcSection][offlineExpirationKey], - HomeBaseDir: cfg[oidcSection][homeDirKey], - CachePath: cachePath, + IssuerURL: cfg[oidcSection][issuerKey], + ClientID: cfg[oidcSection][clientIDKey], + HomeBaseDir: cfg[oidcSection][homeDirKey], + CachePath: cachePath, } b, err := broker.New(bCfg) if err != nil { From 2c5cc658c93010a46d67398c8c18a5fa8b97904d Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 18 Jun 2024 08:44:34 -0400 Subject: [PATCH 0053/1670] Prevent empty local password Empty strings (i.e. "") still worked as local passwords for the broker users. We now prevent the user from setting an empty password as its local one. --- internal/broker/broker.go | 4 ++++ internal/broker/broker_test.go | 12 +++++++++--- .../cache/.empty | 0 .../first_call | 3 +++ .../second_call | 3 +++ 5 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/cache/.empty create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/second_call diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 90254e3328..4d21daeb4a 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -440,6 +440,10 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo } case "newpassword": + if challenge == "" { + return AuthRetry, `{"message": "challenge must not be empty"}` + } + var ok bool // This mode must always come after a authentication mode, so it has to have an auth_info. authInfo, ok = session.authInfo["auth_info"].(authCachedInfo) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 8fbc300306..f90150adfb 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -258,7 +258,8 @@ func TestIsAuthenticated(t *testing.T) { customHandlers map[string]testutils.ProviderHandler - wantSecondCall bool + wantSecondCall bool + secondChallenge string preexistentToken string offlineExpiration string @@ -312,7 +313,7 @@ func TestIsAuthenticated(t *testing.T) { "/token": testutils.UnavailableHandler(), }, }, - + "Error when empty challenge is provided for local password": {firstChallenge: "-", wantSecondCall: true, secondChallenge: "-"}, "Error when mode is newpassword and token is not set": { firstMode: "newpassword", firstAuthInfo: map[string]any{"token": nil}, @@ -434,7 +435,12 @@ func TestIsAuthenticated(t *testing.T) { // Give some time for the first call time.Sleep(10 * time.Millisecond) - secondAuthData := `{"challenge":"` + encryptChallenge(t, "passwordpassword", key) + `"}` + challenge := "passwordpassword" + if tc.secondChallenge == "-" { + challenge = "" + } + + secondAuthData := `{"challenge":"` + encryptChallenge(t, challenge, key) + `"}` if tc.invalidAuthData { secondAuthData = "invalid json" } diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/cache/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/first_call new file mode 100644 index 0000000000..de73804437 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/first_call @@ -0,0 +1,3 @@ +access: next +data: "" +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/second_call new file mode 100644 index 0000000000..b035cf6626 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/second_call @@ -0,0 +1,3 @@ +access: retry +data: '{"message": "challenge must not be empty"}' +err: From 4d1fc4de30b84f3ecbb32678c1a9af5cecc6c31b Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 18 Jun 2024 07:53:48 -0400 Subject: [PATCH 0054/1670] Update broker to not rely on offline expiration We used to have a limited amount of time for the user to authenticate offline. Now we allow offline authentication as long as the user have a token. --- internal/broker/broker.go | 16 ++--------- internal/broker/broker_test.go | 27 +++++++++---------- .../provider_url/test-user@email.com.cache | 0 .../first_call | 11 ++++++++ .../provider_url/test-user@email.com.cache | 0 .../first_call | 0 .../first_call | 3 --- .../provider_url/test-user@email.com.cache | 0 .../first_call | 0 .../first_call | 2 +- 10 files changed, 26 insertions(+), 33 deletions(-) rename internal/broker/testdata/TestIsAuthenticated/golden/{authenticating_with_password_still_allowed_if_server_is_unreachable_and_token_is_not_offline-expired => authenticating_with_password_still_allowed_if_server_is_unreachable}/cache/provider_url/test-user@email.com.cache (100%) create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/first_call rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_password_and_server_offline_and_token_expired => authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable}/cache/provider_url/test-user@email.com.cache (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{authenticating_with_password_still_allowed_if_server_is_unreachable_and_token_is_not_offline-expired => authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable}/first_call (100%) delete mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_server_offline_and_token_expired/first_call rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_password_and_token_is_offline-valid_but_server_returns_error => error_when_mode_is_password_but_server_returns_error}/cache/provider_url/test-user@email.com.cache (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_password_and_token_is_offline-valid_but_server_returns_error => error_when_mode_is_password_but_server_returns_error}/first_call (100%) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 785b3e0a72..627621848d 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -592,13 +592,7 @@ func (b *Broker) loadAuthInfo(session *sessionInfo, password string) (loadedInfo return authCachedInfo{}, false, fmt.Errorf("could not unmarshal token: %v", err) } - // If the token is not expired, we should use the cached information. - if cachedInfo.Token.Valid() { - return cachedInfo, true, nil - } - - // Tries to refresh the access token. If the service is unavailable and token is still valid from the broker - // perspective (i.e. last modification was between now and now-expiration), we can use it. + // Tries to refresh the access token. If the service is unavailable, we allow authentication. tok, err := b.auth.oauthCfg.TokenSource(context.Background(), cachedInfo.Token).Token() if err != nil { castErr := &oauth2.RetrieveError{} @@ -606,13 +600,7 @@ func (b *Broker) loadAuthInfo(session *sessionInfo, password string) (loadedInfo return authCachedInfo{}, false, fmt.Errorf("could not refresh token: %v", err) } - if time.Since(cachedInfo.AcquiredAt) >= b.offlineExpiration { - return authCachedInfo{}, false, errors.New("token exceeded offline expiration") - } - - // If we get here, it means that the token is expired and we could not refresh it - // but we can still use it locally due to offline expiration configuration - // However, we don't want to update the token file neither its saved files. + // The provider is unavailable, so we allow offline authentication. return cachedInfo, true, nil } diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index bf8da7ab32..d3ec71076c 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -257,7 +257,6 @@ func TestIsAuthenticated(t *testing.T) { wantSecondCall bool preexistentToken string - offlineExpiration string invalidAuthData bool dontWaitForFirstCall bool readOnlyCacheDir bool @@ -267,10 +266,16 @@ func TestIsAuthenticated(t *testing.T) { "Authenticating with qrcode reacquires token": {firstChallenge: "-", wantSecondCall: true, preexistentToken: "valid"}, "Authenticating with password refreshes expired token": {firstMode: "password", preexistentToken: "expired"}, - "Authenticating with password still allowed if server is unreachable and token is not offline-expired": { - firstMode: "password", - preexistentToken: "expired", - offlineExpiration: "10", + "Authenticating with password still allowed if server is unreachable": { + firstMode: "password", + preexistentToken: "valid", + customHandlers: map[string]testutils.ProviderHandler{ + "/token": testutils.UnavailableHandler(), + }, + }, + "Authenticating with password still allowed if token is expired and server is unreachable": { + firstMode: "password", + preexistentToken: "expired", customHandlers: map[string]testutils.ProviderHandler{ "/token": testutils.UnavailableHandler(), }, @@ -283,17 +288,9 @@ func TestIsAuthenticated(t *testing.T) { "Error when IsAuthenticated is ongoing for session": {dontWaitForFirstCall: true, wantSecondCall: true}, "Error when mode is password and token does not exist": {firstMode: "password"}, - "Error when mode is password and server offline and token expired": { + "Error when mode is password but server returns error": { firstMode: "password", preexistentToken: "expired", - customHandlers: map[string]testutils.ProviderHandler{ - "/token": testutils.UnavailableHandler(), - }, - }, - "Error when mode is password and token is offline-valid but server returns error": { - firstMode: "password", - preexistentToken: "expired", - offlineExpiration: "10", customHandlers: map[string]testutils.ProviderHandler{ "/token": testutils.BadRequestHandler(), }, @@ -348,7 +345,7 @@ func TestIsAuthenticated(t *testing.T) { defer cleanup() provider = p } - b, sessionID, key := newBrokerForTests(t, cacheDir, provider.URL, tc.offlineExpiration, tc.sessionMode) + b, sessionID, key := newBrokerForTests(t, cacheDir, provider.URL, tc.sessionMode) if tc.preexistentToken != "" { tok := generateCachedInfo(t, tc.preexistentToken, provider.URL) diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable_and_token_is_not_offline-expired/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/cache/provider_url/test-user@email.com.cache similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable_and_token_is_not_offline-expired/cache/provider_url/test-user@email.com.cache rename to internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/cache/provider_url/test-user@email.com.cache diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/first_call new file mode 100644 index 0000000000..bd0029412a --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/first_call @@ -0,0 +1,11 @@ +access: granted +data: |- + {"userinfo": { + "name": "test-user@email.com", + "uuid": "saved-user-id", + "gecos": "saved-user", + "dir": "/home/test-user@email.com", + "shell": "/usr/bin/bash", + "groups": [ {"name": "remote-group", "ugid": "12345"}, {"name": "linux-local-group", "ugid": ""} ] + }} +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_server_offline_and_token_expired/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/cache/provider_url/test-user@email.com.cache similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_server_offline_and_token_expired/cache/provider_url/test-user@email.com.cache rename to internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/cache/provider_url/test-user@email.com.cache diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable_and_token_is_not_offline-expired/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable_and_token_is_not_offline-expired/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_server_offline_and_token_expired/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_server_offline_and_token_expired/first_call deleted file mode 100644 index 0baccf0161..0000000000 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_server_offline_and_token_expired/first_call +++ /dev/null @@ -1,3 +0,0 @@ -access: retry -data: '{"message": "could not authenticate user: could not load cached info: token exceeded offline expiration"}' -err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_offline-valid_but_server_returns_error/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/cache/provider_url/test-user@email.com.cache similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_offline-valid_but_server_returns_error/cache/provider_url/test-user@email.com.cache rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/cache/provider_url/test-user@email.com.cache diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_offline-valid_but_server_returns_error/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_offline-valid_but_server_returns_error/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call index b72d8b1117..bd0029412a 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call @@ -6,6 +6,6 @@ data: |- "gecos": "saved-user", "dir": "/home/test-user@email.com", "shell": "/usr/bin/bash", - "groups": [{"name": "saved-remote-group", "gid": "12345"}, {"name": "saved-local-group", "gid": ""}] + "groups": [ {"name": "remote-group", "ugid": "12345"}, {"name": "linux-local-group", "ugid": ""} ] }} err: From ca7b16c83fac935ba683089488f721ccc3ae0d78 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Wed, 26 Jun 2024 10:34:31 -0400 Subject: [PATCH 0055/1670] Simplify GAM offered modes confirmation After refactoring the providers, we were validating the returned modes twice (once in the provider.GetOfferedModes and once in broker.GetAuthenticationModes). This could be simplified to make the code cleaner --- internal/broker/broker.go | 12 ++++-------- internal/broker/export_test.go | 10 ++++++++++ internal/broker/helper_test.go | 4 ++-- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 04762b45bb..caf99fa530 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -68,7 +68,7 @@ type sessionInfo struct { selectedMode string firstSelectedMode string - supportedModes map[string]string + authModes []string attemptsPerMode map[string]int authInfo map[string]any @@ -214,17 +214,13 @@ func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []m } for _, id := range availableModes { - label, ok := supportedAuthModes[id] - if !ok { - return nil, fmt.Errorf("required mode %q is not supported", id) - } authModes = append(authModes, map[string]string{ "id": id, - "label": label, + "label": supportedAuthModes[id], }) } - session.supportedModes = supportedAuthModes + session.authModes = availableModes if err := b.updateSession(sessionID, session); err != nil { return nil, err } @@ -286,7 +282,7 @@ func (b *Broker) SelectAuthenticationMode(sessionID, authModeID string) (uiLayou } func (b *Broker) generateUILayout(session *sessionInfo, authModeID string) (map[string]string, error) { - if _, exists := session.supportedModes[authModeID]; !exists { + if !slices.Contains(session.authModes, authModeID) { return nil, fmt.Errorf("selected authentication mode %q does not exist", authModeID) } diff --git a/internal/broker/export_test.go b/internal/broker/export_test.go index be3b96f692..fb983b8653 100644 --- a/internal/broker/export_test.go +++ b/internal/broker/export_test.go @@ -54,6 +54,16 @@ func (b *Broker) SetAuthInfo(sessionID, key string, value any) error { return nil } +func (b *Broker) SetAvailableMode(sessionID, mode string) error { + s, err := b.getSession(sessionID) + if err != nil { + return err + } + s.authModes = []string{mode} + + return b.updateSession(sessionID, s) +} + type AuthCachedInfo = authCachedInfo // CacheAuthInfo exposes the broker's cacheAuthInfo method for tests. diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index c0b9ef9b12..1054b12561 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -72,8 +72,8 @@ func encryptChallenge(t *testing.T, challenge, strKey string) string { func updateAuthModes(t *testing.T, b *broker.Broker, sessionID, selectedMode string) { t.Helper() - _, err := b.GetAuthenticationModes(sessionID, supportedLayouts) - require.NoError(t, err, "Setup: GetAuthenticationModes should not have returned an error") + err := b.SetAvailableMode(sessionID, selectedMode) + require.NoError(t, err, "Setup: SetAvailableMode should not have returned an error") _, err = b.SelectAuthenticationMode(sessionID, selectedMode) require.NoError(t, err, "Setup: SelectAuthenticationMode should not have returned an error") } From 8ed909fb899ac8a91bff7ef22353053a00276ce2 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Wed, 26 Jun 2024 10:34:57 -0400 Subject: [PATCH 0056/1670] Add code field to QRCode layout After the change introduced in authd by https://github.com/ubuntu/authd/pull/387, we can now provide the device code as a separate field in the layout to improve the way it's rendered by PAM. --- internal/broker/broker.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index caf99fa530..4ff069c8bc 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -298,13 +298,13 @@ func (b *Broker) generateUILayout(session *sessionInfo, authModeID string) (map[ uiLayout = map[string]string{ "type": "qrcode", "label": fmt.Sprintf( - "Scan the QR code or access %q and use the code %q", + "Scan the QR code or access %q and use the provided code", response.VerificationURI, - response.UserCode, ), "wait": "true", "button": "regenerate QR code", "content": response.VerificationURI, + "code": response.UserCode, } case "password": From c4e8023985afff29d54b5e7b12e755bd51c69ff8 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Wed, 26 Jun 2024 10:59:42 -0400 Subject: [PATCH 0057/1670] Rename qrcode authentication mode to device_auth This was always being named qrcode, even though qrcode relates more to the UI type than to the actual authentication mode, so it's better to rename it for consistency --- internal/broker/broker.go | 6 +++--- internal/broker/broker_test.go | 4 ++-- ...ere_is_no_token => get_device_auth_if_there_is_no_token} | 2 +- ...t_newpassword_if_already_authenticated_with_device_auth} | 0 ..._exists => get_password_and_device_auth_if_token_exists} | 2 +- ...sfully_select_qrcode => successfully_select_device_auth} | 3 ++- internal/providers/microsoft_entra_id/microsoft-entra-id.go | 4 ++-- internal/providers/noprovider/noprovider.go | 4 ++-- internal/testutils/provider.go | 4 ++-- 9 files changed, 15 insertions(+), 14 deletions(-) rename internal/broker/testdata/TestGetAuthenticationModes/golden/{get_qrcode_if_there_is_no_token => get_device_auth_if_there_is_no_token} (63%) rename internal/broker/testdata/TestGetAuthenticationModes/golden/{get_newpassword_if_already_authenticated_with_qrcode => get_newpassword_if_already_authenticated_with_device_auth} (100%) rename internal/broker/testdata/TestGetAuthenticationModes/golden/{get_password_and_qrcode_if_token_exists => get_password_and_device_auth_if_token_exists} (82%) rename internal/broker/testdata/TestSelectAuthenticationMode/golden/{successfully_select_qrcode => successfully_select_device_auth} (78%) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 4ff069c8bc..55eb26bc46 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -237,7 +237,7 @@ func (b *Broker) supportedAuthModesFromLayout(supportedUILayouts []map[string]st if !strings.Contains(layout["wait"], "true") { continue } - supportedModes["qrcode"] = "Device Authentication" + supportedModes["device_auth"] = "Device Authentication" case "form": if slices.Contains(supportedEntries, "chars_password") { @@ -288,7 +288,7 @@ func (b *Broker) generateUILayout(session *sessionInfo, authModeID string) (map[ var uiLayout map[string]string switch authModeID { - case "qrcode": + case "device_auth": response, err := b.auth.oauthCfg.DeviceAuth(context.TODO()) if err != nil { return nil, fmt.Errorf("could not generate QR code layout: %v", err) @@ -391,7 +391,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo offline := false switch session.selectedMode { - case "qrcode": + case "device_auth": response, ok := session.authInfo["response"].(*oauth2.DeviceAuthResponse) if !ok { return AuthDenied, `{"message": "could not get required response"}` diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 93260fc8bd..827a322b70 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -394,7 +394,7 @@ func TestIsAuthenticated(t *testing.T) { defer close(firstCallDone) if tc.firstMode == "" { - tc.firstMode = "qrcode" + tc.firstMode = "device_auth" } updateAuthModes(t, b, sessionID, tc.firstMode) @@ -580,7 +580,7 @@ func TestCancelIsAuthenticated(t *testing.T) { t.Cleanup(cleanup) b, sessionID, _ := newBrokerForTests(t, t.TempDir(), provider.URL, "auth") - updateAuthModes(t, b, sessionID, "qrcode") + updateAuthModes(t, b, sessionID, "device_auth") stopped := make(chan struct{}) go func() { diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_qrcode_if_there_is_no_token b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_device_auth_if_there_is_no_token similarity index 63% rename from internal/broker/testdata/TestGetAuthenticationModes/golden/get_qrcode_if_there_is_no_token rename to internal/broker/testdata/TestGetAuthenticationModes/golden/get_device_auth_if_there_is_no_token index 6ea169fdf1..b4398710b8 100644 --- a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_qrcode_if_there_is_no_token +++ b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_device_auth_if_there_is_no_token @@ -1,2 +1,2 @@ -- id: qrcode +- id: device_auth label: Device Authentication diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_qrcode b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_device_auth similarity index 100% rename from internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_qrcode rename to internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_device_auth diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_password_and_qrcode_if_token_exists b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_password_and_device_auth_if_token_exists similarity index 82% rename from internal/broker/testdata/TestGetAuthenticationModes/golden/get_password_and_qrcode_if_token_exists rename to internal/broker/testdata/TestGetAuthenticationModes/golden/get_password_and_device_auth_if_token_exists index 6c2b078bee..39b3df5a1d 100644 --- a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_password_and_qrcode_if_token_exists +++ b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_password_and_device_auth_if_token_exists @@ -1,4 +1,4 @@ - id: password label: Local Password Authentication -- id: qrcode +- id: device_auth label: Device Authentication diff --git a/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_qrcode b/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_device_auth similarity index 78% rename from internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_qrcode rename to internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_device_auth index dbe7ecacb0..5cadae3325 100644 --- a/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_qrcode +++ b/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_device_auth @@ -1,5 +1,6 @@ button: regenerate QR code +code: user_code content: https://verification_uri.com -label: Scan the QR code or access "https://verification_uri.com" and use the code "user_code" +label: Scan the QR code or access "https://verification_uri.com" and use the provided code type: qrcode wait: "true" diff --git a/internal/providers/microsoft_entra_id/microsoft-entra-id.go b/internal/providers/microsoft_entra_id/microsoft-entra-id.go index e550c29c69..7189630aa6 100644 --- a/internal/providers/microsoft_entra_id/microsoft-entra-id.go +++ b/internal/providers/microsoft_entra_id/microsoft-entra-id.go @@ -106,9 +106,9 @@ func (p MSEntraIDProvider) CurrentAuthenticationModesOffered(sessionMode string, } default: // auth mode - offeredModes = []string{"qrcode"} + offeredModes = []string{"device_auth"} if tokenExists { - offeredModes = []string{"password", "qrcode"} + offeredModes = append([]string{"password"}, offeredModes...) } if currentAuthStep > 0 { offeredModes = []string{"newpassword"} diff --git a/internal/providers/noprovider/noprovider.go b/internal/providers/noprovider/noprovider.go index aefe7873d8..ec6812754c 100644 --- a/internal/providers/noprovider/noprovider.go +++ b/internal/providers/noprovider/noprovider.go @@ -42,9 +42,9 @@ func (p NoProvider) CurrentAuthenticationModesOffered(sessionMode string, suppor } default: // auth mode - offeredModes = []string{"qrcode"} + offeredModes = []string{"device_auth"} if tokenExists { - offeredModes = []string{"password", "qrcode"} + offeredModes = append([]string{"password"}, offeredModes...) } if currentAuthStep > 0 { offeredModes = []string{"newpassword"} diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index 771fcfe537..4cae535458 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -217,9 +217,9 @@ func (p *MockProviderInfoer) CurrentAuthenticationModesOffered(sessionMode strin } default: // auth mode - offeredModes = []string{"qrcode"} + offeredModes = []string{"device_auth"} if tokenExists { - offeredModes = []string{"password", "qrcode"} + offeredModes = append([]string{"password"}, offeredModes...) } if currentAuthStep > 0 { offeredModes = []string{"newpassword"} From 14afa561fdd47bea32abcad796caf76b99556c77 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Wed, 26 Jun 2024 11:05:26 -0400 Subject: [PATCH 0058/1670] No longer show device_auth mode if invalid The device authentication flow is invalid when the provider is not available (due to the user being offline or the provider not being reachable) or if the device_auth endpoint is not configured and, therefore, not supported. --- cmd/oidc-broker/daemon/daemon_test.go | 2 +- internal/broker/broker.go | 18 +++++ internal/broker/broker_test.go | 66 ++++++++++++++----- ..._and_provider_does_not_support_device_auth | 2 + ...token_exists_and_provider_is_not_available | 2 + .../microsoft_entra_id/microsoft-entra-id.go | 13 +++- internal/providers/noprovider/noprovider.go | 13 +++- internal/providers/providers.go | 9 ++- internal/testutils/provider.go | 46 +++++++++++-- 9 files changed, 146 insertions(+), 25 deletions(-) create mode 100644 internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_provider_does_not_support_device_auth create mode 100644 internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_provider_is_not_available diff --git a/cmd/oidc-broker/daemon/daemon_test.go b/cmd/oidc-broker/daemon/daemon_test.go index a5b8b06424..8c51025450 100644 --- a/cmd/oidc-broker/daemon/daemon_test.go +++ b/cmd/oidc-broker/daemon/daemon_test.go @@ -386,7 +386,7 @@ func TestMain(m *testing.M) { defer cleanup() // Start provider mock - providerServer, cleanup := testutils.StartMockProvider() + providerServer, cleanup := testutils.StartMockProvider("") defer cleanup() mockProvider = providerServer diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 55eb26bc46..911cccad39 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -204,10 +204,24 @@ func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []m _, err = os.Stat(session.cachePath) tokenExists := err == nil + // Checks if the provider is accessible and if device authentication is supported + providerReachable := true + r, err := http.Get(strings.TrimSuffix(b.auth.providerURL, "/") + "/.well-known/openid-configuration") + // This means the provider is not available or something bad happened, so we assume no connection. + if err != nil || r.StatusCode != http.StatusOK { + providerReachable = false + } + availableModes, err := b.providerInfo.CurrentAuthenticationModesOffered( session.mode, supportedAuthModes, tokenExists, + providerReachable, + map[string]string{ + "auth": b.auth.provider.Endpoint().AuthURL, + "device_auth": b.auth.provider.Endpoint().DeviceAuthURL, + "token": b.auth.provider.Endpoint().TokenURL, + }, session.currentAuthStep) if err != nil { return nil, err @@ -220,6 +234,10 @@ func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []m }) } + if len(authModes) == 0 { + return nil, fmt.Errorf("no authentication modes available for user %q", session.username) + } + session.authModes = availableModes if err := b.updateSession(sessionID, session); err != nil { return nil, err diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 827a322b70..c7e4ecb9c8 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -111,15 +111,21 @@ func TestGetAuthenticationModes(t *testing.T) { sessionID string supportedLayouts []string - tokenExists bool - secondAuthStep bool + providerAddress string + tokenExists bool + secondAuthStep bool + unavailableProvider bool + deviceAuthUnsupported bool wantErr bool }{ // Auth Session - "Get qrcode if there is no token": {}, - "Get newpassword if already authenticated with QRCode": {secondAuthStep: true}, - "Get password and qrcode if token exists": {tokenExists: true}, + "Get device_auth if there is no token": {}, + "Get newpassword if already authenticated with device_auth": {secondAuthStep: true}, + "Get password and device_auth if token exists": {tokenExists: true}, + + "Get only password if token exists and provider is not available": {tokenExists: true, providerAddress: "127.0.0.1:31310", unavailableProvider: true}, + "Get only password if token exists and provider does not support device_auth": {tokenExists: true, providerAddress: "127.0.0.1:31311", deviceAuthUnsupported: true}, // Passwd Session "Get only password if token exists and session is passwd": {sessionMode: "passwd", tokenExists: true}, @@ -128,7 +134,8 @@ func TestGetAuthenticationModes(t *testing.T) { "Error if there is no session": {sessionID: "-", wantErr: true}, // General errors - "Error if expecting qrcode but not supported": {supportedLayouts: []string{"qrcode-without-wait"}, wantErr: true}, + "Error if no authentication mode is supported": {providerAddress: "127.0.0.1:31312", deviceAuthUnsupported: true, wantErr: true}, + "Error if expecting device_auth but not supported": {supportedLayouts: []string{"qrcode-without-wait"}, wantErr: true}, "Error if expecting newpassword but not supported": {supportedLayouts: []string{"newpassword-without-entry"}, wantErr: true}, "Error if expecting password but not supported": {supportedLayouts: []string{"form-without-entry"}, wantErr: true}, @@ -143,7 +150,21 @@ func TestGetAuthenticationModes(t *testing.T) { tc.sessionMode = "auth" } - b, sessionID, _ := newBrokerForTests(t, t.TempDir(), defaultProvider.URL, tc.sessionMode) + provider := defaultProvider + var stopServer func() + if tc.providerAddress != "" { + address := tc.providerAddress + opts := []testutils.OptionProvider{} + if tc.deviceAuthUnsupported { + opts = append(opts, testutils.WithHandler( + "/.well-known/openid-configuration", + testutils.OpenIDHandlerWithNoDeviceEndpoint("http://"+address), + )) + } + provider, stopServer = testutils.StartMockProvider(address, opts...) + t.Cleanup(stopServer) + } + b, sessionID, _ := newBrokerForTests(t, t.TempDir(), provider.URL, tc.sessionMode) if tc.sessionID == "-" { sessionID = "" @@ -166,6 +187,10 @@ func TestGetAuthenticationModes(t *testing.T) { layouts = append(layouts, supportedUILayouts[layout]) } + if tc.unavailableProvider { + stopServer() + } + got, err := b.GetAuthenticationModes(sessionID, layouts) if tc.wantErr { require.Error(t, err, "GetAuthenticationModes should have returned an error") @@ -191,16 +216,18 @@ func TestSelectAuthenticationMode(t *testing.T) { tests := map[string]struct { modeName string + tokenExists bool + secondAuthStep bool customHandlers map[string]testutils.ProviderHandler wantErr bool }{ - "Successfully select password": {modeName: "password"}, - "Successfully select qrcode": {modeName: "qrcode"}, - "Successfully select newpassword": {modeName: "newpassword"}, + "Successfully select password": {modeName: "password", tokenExists: true}, + "Successfully select device_auth": {modeName: "device_auth"}, + "Successfully select newpassword": {modeName: "newpassword", secondAuthStep: true}, "Error when selecting invalid mode": {modeName: "invalid", wantErr: true}, - "Error when selecting qrcode but provider is unavailable": {modeName: "qrcode", wantErr: true, + "Error when selecting device_auth but provider is unavailable": {modeName: "device_auth", wantErr: true, customHandlers: map[string]testutils.ProviderHandler{ "/device_auth": testutils.UnavailableHandler(), }, @@ -216,12 +243,21 @@ func TestSelectAuthenticationMode(t *testing.T) { for path, handler := range tc.customHandlers { opts = append(opts, testutils.WithHandler(path, handler)) } - p, cleanup := testutils.StartMockProvider(opts...) + p, cleanup := testutils.StartMockProvider("", opts...) defer cleanup() provider = p } b, sessionID, _ := newBrokerForTests(t, t.TempDir(), provider.URL, "auth") + if tc.tokenExists { + err := os.MkdirAll(filepath.Dir(b.TokenPathForSession(sessionID)), 0700) + require.NoError(t, err, "Setup: MkdirAll should not have returned an error") + err = os.WriteFile(b.TokenPathForSession(sessionID), []byte("some token"), 0600) + require.NoError(t, err, "Setup: WriteFile should not have returned an error") + } + if tc.secondAuthStep { + b.UpdateSessionAuthStep(sessionID, 1) + } // We need to do a GAM call first to get all the modes. _, err := b.GetAuthenticationModes(sessionID, supportedLayouts) @@ -342,7 +378,7 @@ func TestIsAuthenticated(t *testing.T) { for path, handler := range tc.customHandlers { opts = append(opts, testutils.WithHandler(path, handler)) } - p, cleanup := testutils.StartMockProvider(opts...) + p, cleanup := testutils.StartMockProvider("", opts...) defer cleanup() provider = p } @@ -576,7 +612,7 @@ func TestCancelIsAuthenticated(t *testing.T) { t.Parallel() ctx, cancel := context.WithCancel(context.Background()) - provider, cleanup := testutils.StartMockProvider(testutils.WithHandler("/token", testutils.HangingHandler(ctx))) + provider, cleanup := testutils.StartMockProvider("", testutils.WithHandler("/token", testutils.HangingHandler(ctx))) t.Cleanup(cleanup) b, sessionID, _ := newBrokerForTests(t, t.TempDir(), provider.URL, "auth") @@ -615,7 +651,7 @@ func TestMain(m *testing.M) { testutils.InstallUpdateFlag() flag.Parse() - server, cleanup := testutils.StartMockProvider() + server, cleanup := testutils.StartMockProvider("") defer cleanup() defaultProvider = server diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_provider_does_not_support_device_auth b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_provider_does_not_support_device_auth new file mode 100644 index 0000000000..e4e7cb1d35 --- /dev/null +++ b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_provider_does_not_support_device_auth @@ -0,0 +1,2 @@ +- id: password + label: Local Password Authentication diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_provider_is_not_available b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_provider_is_not_available new file mode 100644 index 0000000000..e4e7cb1d35 --- /dev/null +++ b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_provider_is_not_available @@ -0,0 +1,2 @@ +- id: password + label: Local Password Authentication diff --git a/internal/providers/microsoft_entra_id/microsoft-entra-id.go b/internal/providers/microsoft_entra_id/microsoft-entra-id.go index 7189630aa6..2370697173 100644 --- a/internal/providers/microsoft_entra_id/microsoft-entra-id.go +++ b/internal/providers/microsoft_entra_id/microsoft-entra-id.go @@ -93,7 +93,14 @@ func (p MSEntraIDProvider) GetGroups(token *oauth2.Token) ([]group.Info, error) // CurrentAuthenticationModesOffered returns the generic authentication modes supported by the provider. // // Token validity is not considered, only the presence of a token. -func (p MSEntraIDProvider) CurrentAuthenticationModesOffered(sessionMode string, supportedAuthModes map[string]string, tokenExists bool, currentAuthStep int) ([]string, error) { +func (p MSEntraIDProvider) CurrentAuthenticationModesOffered( + sessionMode string, + supportedAuthModes map[string]string, + tokenExists bool, + providerReachable bool, + endpoints map[string]string, + currentAuthStep int, +) ([]string, error) { var offeredModes []string switch sessionMode { case "passwd": @@ -106,7 +113,9 @@ func (p MSEntraIDProvider) CurrentAuthenticationModesOffered(sessionMode string, } default: // auth mode - offeredModes = []string{"device_auth"} + if providerReachable && endpoints["device_auth"] != "" { + offeredModes = []string{"device_auth"} + } if tokenExists { offeredModes = append([]string{"password"}, offeredModes...) } diff --git a/internal/providers/noprovider/noprovider.go b/internal/providers/noprovider/noprovider.go index ec6812754c..3b4c38b933 100644 --- a/internal/providers/noprovider/noprovider.go +++ b/internal/providers/noprovider/noprovider.go @@ -29,7 +29,14 @@ func (p NoProvider) GetGroups(_ *oauth2.Token) ([]group.Info, error) { } // CurrentAuthenticationModesOffered returns the generic authentication modes supported by the provider. -func (p NoProvider) CurrentAuthenticationModesOffered(sessionMode string, supportedAuthModes map[string]string, tokenExists bool, currentAuthStep int) ([]string, error) { +func (p NoProvider) CurrentAuthenticationModesOffered( + sessionMode string, + supportedAuthModes map[string]string, + tokenExists bool, + providerReachable bool, + endpoints map[string]string, + currentAuthStep int, +) ([]string, error) { var offeredModes []string switch sessionMode { case "passwd": @@ -42,7 +49,9 @@ func (p NoProvider) CurrentAuthenticationModesOffered(sessionMode string, suppor } default: // auth mode - offeredModes = []string{"device_auth"} + if providerReachable && endpoints["device_auth"] != "" { + offeredModes = []string{"device_auth"} + } if tokenExists { offeredModes = append([]string{"password"}, offeredModes...) } diff --git a/internal/providers/providers.go b/internal/providers/providers.go index 25f6d356a9..2d183a7a1f 100644 --- a/internal/providers/providers.go +++ b/internal/providers/providers.go @@ -10,6 +10,13 @@ import ( type ProviderInfoer interface { AdditionalScopes() []string AuthOptions() []oauth2.AuthCodeOption - CurrentAuthenticationModesOffered(sessionMode string, supportedAuthModes map[string]string, tokenExists bool, currentAuthStep int) ([]string, error) + CurrentAuthenticationModesOffered( + sessionMode string, + supportedAuthModes map[string]string, + tokenExists bool, + providerReachable bool, + endpoints map[string]string, + currentAuthStep int, + ) ([]string, error) GetGroups(*oauth2.Token) ([]group.Info, error) } diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index 4cae535458..7cc6b6698a 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -5,6 +5,7 @@ import ( "encoding/base64" "errors" "fmt" + "net" "net/http" "net/http/httptest" "time" @@ -32,9 +33,18 @@ func WithHandler(path string, handler func(http.ResponseWriter, *http.Request)) } // StartMockProvider starts a new HTTP server to be used as an OpenID Connect provider for tests. -func StartMockProvider(args ...OptionProvider) (*httptest.Server, func()) { +func StartMockProvider(address string, args ...OptionProvider) (*httptest.Server, func()) { servMux := http.NewServeMux() - server := httptest.NewServer(servMux) + server := httptest.NewUnstartedServer(servMux) + + if address != "" { + l, err := net.Listen("tcp", address) + if err != nil { + panic(fmt.Sprintf("error starting listener: %v", err)) + } + server.Listener = l + } + server.Start() opts := optionProvider{ handlers: map[string]ProviderHandler{ @@ -79,6 +89,25 @@ func DefaultOpenIDHandler(serverURL string) ProviderHandler { } } +// OpenIDHandlerWithNoDeviceEndpoint returns a handler that returns an OpenID Connect configuration without device endpoint. +func OpenIDHandlerWithNoDeviceEndpoint(serverURL string) ProviderHandler { + return func(w http.ResponseWriter, _ *http.Request) { + wellKnown := fmt.Sprintf(`{ + "issuer": "%[1]s", + "authorization_endpoint": "%[1]s/auth", + "token_endpoint": "%[1]s/token", + "jwks_uri": "%[1]s/keys", + "id_token_signing_alg_values_supported": ["RS256"] + }`, serverURL) + + w.Header().Add("Content-Type", "application/json") + _, err := w.Write([]byte(wellKnown)) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + } + } +} + // DefaultDeviceAuthHandler returns a handler that returns a default device auth response. func DefaultDeviceAuthHandler() ProviderHandler { return func(w http.ResponseWriter, _ *http.Request) { @@ -204,7 +233,14 @@ func (p *MockProviderInfoer) GetGroups(*oauth2.Token) ([]group.Info, error) { } // CurrentAuthenticationModesOffered returns the authentication modes supported by the provider. -func (p *MockProviderInfoer) CurrentAuthenticationModesOffered(sessionMode string, supportedAuthModes map[string]string, tokenExists bool, currentAuthStep int) ([]string, error) { +func (p MockProviderInfoer) CurrentAuthenticationModesOffered( + sessionMode string, + supportedAuthModes map[string]string, + tokenExists bool, + providerReachable bool, + endpoints map[string]string, + currentAuthStep int, +) ([]string, error) { var offeredModes []string switch sessionMode { case "passwd": @@ -217,7 +253,9 @@ func (p *MockProviderInfoer) CurrentAuthenticationModesOffered(sessionMode strin } default: // auth mode - offeredModes = []string{"device_auth"} + if providerReachable && endpoints["device_auth"] != "" { + offeredModes = []string{"device_auth"} + } if tokenExists { offeredModes = append([]string{"password"}, offeredModes...) } From b3c7f3d4640adfaf8a66e20d1dbfb0687583560f Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Thu, 27 Jun 2024 06:53:24 -0400 Subject: [PATCH 0059/1670] Update install hook for the snap Remove the previous offline expiration setting and add/improve comments on some of the sections. --- snap/hooks/install | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/snap/hooks/install b/snap/hooks/install index 974fcabe2e..0545e9eef0 100644 --- a/snap/hooks/install +++ b/snap/hooks/install @@ -5,6 +5,8 @@ snap_base_dir=$(dirname ${SNAP}) cat < ${SNAP_COMMON}/oidc-broker [authd] +# This section is used by authd to identify and communicate with the broker. +# It should not be edited. name = OIDC Broker brand_icon = ${snap_base_dir}/current/broker_icon.png dbus_name = com.ubuntu.authd.OidcBroker @@ -14,10 +16,8 @@ dbus_object = /com/ubuntu/authd/OidcBroker issuer = https://{issuer_url} client_id = {client_id} -# The amount of days the user will be allowed to authenticate without a network connection. -# offline_expiration = 180 - -# The directory where the user's home directory will be created. +# The directory where the home directory will be created for new users. +# Existing users will keep their current directory. # The user home directory will be created in the format of {home_base_dir}/{username} # home_base_dir = /home From ba8b5e02f62808386d37c4b6e849965bfaea943c Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Mon, 1 Jul 2024 07:21:48 -0400 Subject: [PATCH 0060/1670] Improve newpassword label for passwd sessions We used to show the same label for both sessions types (auth and passwd), but this could create confusion for users when, in passwd sessions, they were asked to "Create local password" instead of changing it. This commit should make things clearer and easier to navigate. --- internal/broker/broker.go | 7 ++++++- internal/broker/broker_test.go | 10 +++++++++- ...d_newpassword_shows_correct_label_in_passwd_session | 3 +++ 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 internal/broker/testdata/TestSelectAuthenticationMode/golden/selected_newpassword_shows_correct_label_in_passwd_session diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 911cccad39..ebbac5e096 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -333,9 +333,14 @@ func (b *Broker) generateUILayout(session *sessionInfo, authModeID string) (map[ } case "newpassword": + label := "Create a local password" + if session.mode == "passwd" { + label = "Update your local password" + } + uiLayout = map[string]string{ "type": "newpassword", - "label": "Create a local password", + "label": label, "entry": "chars_password", } } diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index c7e4ecb9c8..cf28866e61 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -218,6 +218,7 @@ func TestSelectAuthenticationMode(t *testing.T) { tokenExists bool secondAuthStep bool + passwdSession bool customHandlers map[string]testutils.ProviderHandler wantErr bool @@ -226,6 +227,8 @@ func TestSelectAuthenticationMode(t *testing.T) { "Successfully select device_auth": {modeName: "device_auth"}, "Successfully select newpassword": {modeName: "newpassword", secondAuthStep: true}, + "Selected newpassword shows correct label in passwd session": {modeName: "newpassword", passwdSession: true, tokenExists: true, secondAuthStep: true}, + "Error when selecting invalid mode": {modeName: "invalid", wantErr: true}, "Error when selecting device_auth but provider is unavailable": {modeName: "device_auth", wantErr: true, customHandlers: map[string]testutils.ProviderHandler{ @@ -248,7 +251,12 @@ func TestSelectAuthenticationMode(t *testing.T) { provider = p } - b, sessionID, _ := newBrokerForTests(t, t.TempDir(), provider.URL, "auth") + sessionType := "auth" + if tc.passwdSession { + sessionType = "passwd" + } + + b, sessionID, _ := newBrokerForTests(t, t.TempDir(), provider.URL, sessionType) if tc.tokenExists { err := os.MkdirAll(filepath.Dir(b.TokenPathForSession(sessionID)), 0700) require.NoError(t, err, "Setup: MkdirAll should not have returned an error") diff --git a/internal/broker/testdata/TestSelectAuthenticationMode/golden/selected_newpassword_shows_correct_label_in_passwd_session b/internal/broker/testdata/TestSelectAuthenticationMode/golden/selected_newpassword_shows_correct_label_in_passwd_session new file mode 100644 index 0000000000..31798b45da --- /dev/null +++ b/internal/broker/testdata/TestSelectAuthenticationMode/golden/selected_newpassword_shows_correct_label_in_passwd_session @@ -0,0 +1,3 @@ +entry: chars_password +label: Update your local password +type: newpassword From 70231a1632961087ef23176e34fdb1dbc13c1848 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Tue, 2 Jul 2024 08:08:43 +0200 Subject: [PATCH 0061/1670] Rename oidc-broker and project content To align all namings, we rename: - the project itself, contaings code for multiple authd brokers to authd-oidc-brokers. - oidc-broker implementation to authd-oidc. This way, all projects relates to authd. We rename the snap names, URLs, imports, configuration files to prevent stuttering. --- .github/ISSUE_TEMPLATE/bug_report.yml | 18 ++++++++--------- .github/ISSUE_TEMPLATE/feature_request.yml | 18 ++++++++--------- .gitignore | 8 ++++---- README.md | 20 +++++++++---------- .../daemon/config.go | 4 ++-- .../daemon/daemon.go | 6 +++--- .../daemon/daemon_test.go | 6 +++--- .../daemon/export_test.go | 0 cmd/{oidc-broker => authd-oidc}/daemon/fs.go | 0 .../daemon/version.go | 2 +- cmd/{oidc-broker => authd-oidc}/main.go | 6 +++--- cmd/{oidc-broker => authd-oidc}/main_test.go | 0 go.mod | 2 +- internal/broker/broker.go | 4 ++-- internal/broker/broker_test.go | 6 +++--- internal/broker/helper_test.go | 6 +++--- internal/broker/options_test.go | 2 +- internal/consts/consts.go | 2 +- internal/dbusservice/dbusservice.go | 2 +- internal/dbusservice/localbus.go | 2 +- internal/providers/default.go | 2 +- .../microsoft_entra_id/microsoft-entra-id.go | 2 +- internal/providers/noprovider/noprovider.go | 2 +- internal/providers/providers.go | 2 +- internal/providers/withmsentraid.go | 2 +- internal/testutils/provider.go | 2 +- snap/hooks/install | 8 ++++---- snap/snapcraft.yaml | 20 +++++++++---------- tools/go.mod | 2 +- 29 files changed, 78 insertions(+), 78 deletions(-) rename cmd/{oidc-broker => authd-oidc}/daemon/config.go (96%) rename cmd/{oidc-broker => authd-oidc}/daemon/daemon.go (96%) rename cmd/{oidc-broker => authd-oidc}/daemon/daemon_test.go (98%) rename cmd/{oidc-broker => authd-oidc}/daemon/export_test.go (100%) rename cmd/{oidc-broker => authd-oidc}/daemon/fs.go (100%) rename cmd/{oidc-broker => authd-oidc}/daemon/version.go (92%) rename cmd/{oidc-broker => authd-oidc}/main.go (89%) rename cmd/{oidc-broker => authd-oidc}/main_test.go (100%) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index b7a75cac5b..9922a93518 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,7 +1,7 @@ name: Report an issue description: Create a bug report to fix an issue -title: 'Issue: ' -labels: ['bug'] +title: "Issue: " +labels: ["bug"] body: - type: markdown attributes: @@ -9,11 +9,11 @@ body: :warning: **Please do not report security vulnerabilities here** Be careful with sensitive information and security vulnerabilities. In order to report bugs that could contain - sensitive information, use [Launchpad](https://bugs.launchpad.net/ubuntu/+source/oidc-broker/+filebug) instead. - On Ubuntu machines, you can use `ubuntu-bug oidc-broker` to collect relevant information. + sensitive information, use [Launchpad](https://bugs.launchpad.net/ubuntu/+source/authd-oidc/+filebug) instead. + On Ubuntu machines, you can use `ubuntu-bug authd-oidc` to collect relevant information. - Thanks for taking the time to report an issue and help improve oidc-broker! Please fill out the form below as + Thanks for taking the time to report an issue and help improve authd-oidc! Please fill out the form below as best as you can so that we can help you. Your additional work here is greatly appreciated and will help us respond as quickly as possible. For general @@ -53,7 +53,7 @@ body: attributes: label: "Ubuntu users: System information and logs" description: > - Ubuntu users can run `ubuntu-bug oidc-broker --save=/tmp/report.txt` + Ubuntu users can run `ubuntu-bug authd-oidc --save=/tmp/report.txt` and drag the file below. It will contain useful information pertaining to the system and the packages installed. @@ -64,14 +64,14 @@ body: For users of distributions other than Ubuntu, provide details about the environment you experienced the issue in: value: | ### Environment - * oidc-broker version: please run //TODO + * authd-oidc version: please run //TODO * Distribution: (**NAME** in `/etc/os-release`) * Distribution version: (**VERSION_ID** on `/etc/os-release`): ### Log files Please redact/remove sensitive information: ```raw - oidc-broker logs can be found in //TODO + authd-oidc logs can be found in //TODO ``` ### Application settings @@ -83,7 +83,7 @@ body: attributes: label: Relevant information description: > - Please look at our [Troubleshooting guide](../#troubleshooting) and provide logs for the oidc-broker package. + Please look at our [Troubleshooting guide](../#troubleshooting) and provide logs for the authd-oidc package. placeholder: Remember to redact any sensitive information from them. - type: checkboxes attributes: diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index a7ee9fb194..97739d69f7 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,7 +1,7 @@ name: Request a feature description: Suggest new functionality or improvements for this project -title: 'Feature: ' -labels: ['feature'] +title: "Feature: " +labels: ["feature"] body: - type: markdown attributes: @@ -9,11 +9,11 @@ body: :warning: **Please do not report security vulnerabilities here** Be careful with sensitive information and security vulnerabilities. In order to report bugs that could contain - sensitive information, use [Launchpad](https://bugs.launchpad.net/ubuntu/+source/oidc-broker/+filebug) instead. - On Ubuntu machines, you can use `ubuntu-bug oidc-broker` to collect relevant information. + sensitive information, use [Launchpad](https://bugs.launchpad.net/ubuntu/+source/authd-oidc/+filebug) instead. + On Ubuntu machines, you can use `ubuntu-bug authd-oidc` to collect relevant information. - Thanks for taking the time to report an issue and help improve oidc-broker! Please fill out the form below as + Thanks for taking the time to report an issue and help improve authd-oidc! Please fill out the form below as best as you can so that we can help you. Your additional work here is greatly appreciated and will help us respond as quickly as possible. For general @@ -49,7 +49,7 @@ body: attributes: label: "Ubuntu users: System information and logs" description: > - Ubuntu users can run `ubuntu-bug oidc-broker --save=/tmp/report.txt` + Ubuntu users can run `ubuntu-bug authd-oidc --save=/tmp/report.txt` and drag the file below. It will contain useful information pertaining to the system and the packages installed. @@ -60,14 +60,14 @@ body: For users of distributions other than Ubuntu, provide details about the environment you experienced the issue in: value: | ### Environment - * oidc-broker version: please run //TODO + * authd-oidc version: please run //TODO * Distribution: (**NAME** in `/etc/os-release`) * Distribution version: (**VERSION_ID** on `/etc/os-release`): ### Log files Please redact/remove sensitive information: ```raw - oidc-broker logs can be found in //TODO + authd-oidc logs can be found in //TODO ``` ### Application settings @@ -79,7 +79,7 @@ body: attributes: label: Relevant information description: > - Please look at our [Troubleshooting guide](../#troubleshooting) and provide logs for the oidc-broker package. + Please look at our [Troubleshooting guide](../#troubleshooting) and provide logs for the authd-oidc package. placeholder: Remember to redact any sensitive information from them. - type: checkboxes attributes: diff --git a/.gitignore b/.gitignore index f69237766c..ab682a31fd 100644 --- a/.gitignore +++ b/.gitignore @@ -2,10 +2,10 @@ # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore # # Binaries for programs and plugins -/oid-broker -cmd/oidc-broker/oidc-broker -/microsoft-entra-id-broker -cmd/oidc-broker/microsoft-entra-id-broker +/authd-oidc +cmd/authd-oidc/authd-oidc +/authd-msentraid +cmd/authd-oidc/authd-msentraid # Test binary, built with `go test -c` *.test diff --git a/README.md b/README.md index a8858e975f..ea0895c02f 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,18 @@ -# Welcome to OpenID Connect Broker +# Welcome to Authd OpenID Connect Broker -[actions-image]: https://github.com/ubuntu/oidc-broker/actions/workflows/ci.yaml/badge.svg -[actions-url]: https://github.com/ubuntu/oidc-broker/actions?query=workflow%3ACI +[actions-image]: https://github.com/ubuntu/authd-oidc-brokers/actions/workflows/ci.yaml/badge.svg +[actions-url]: https://github.com/ubuntu/authd-oidc-brokers/actions?query=workflow%3ACI [license-image]: https://img.shields.io/badge/License-GPL3.0-blue.svg -[codecov-image]: https://codecov.io/gh/ubuntu/oidc-broker/graph/badge.svg -[codecov-url]: https://codecov.io/gh/ubuntu/oidc-broker +[codecov-image]: https://codecov.io/gh/ubuntu/authd-oidc-brokers/graph/badge.svg +[codecov-url]: https://codecov.io/gh/ubuntu/authd-oidc-brokers -[user-documentation-image]: https://pkg.go.dev/github.com/ubuntu/oidc-broker -[user-documentation-url]: https://pkg.go.dev/github.com/ubuntu/oidc-broker +[user-documentation-image]: https://pkg.go.dev/github.com/ubuntu/authd-oidc-brokers +[user-documentation-url]: https://pkg.go.dev/github.com/ubuntu/authd-oidc-brokers -[goreport-image]: https://goreportcard.com/badge/github.com/ubuntu/oidc-broker -[goreport-url]: https://goreportcard.com/report/github.com/ubuntu/oidc-broker +[goreport-image]: https://goreportcard.com/badge/github.com/ubuntu/authd-oidc-brokers +[goreport-url]: https://goreportcard.com/report/github.com/ubuntu/authd-oidc-brokers [![Code quality][actions-image]][actions-url] [![License][license-image]](LICENSE) @@ -20,7 +20,7 @@ [![User Documentation][user-documentation-image]][user-documentation-url] [![Go Report Card][goreport-image]][goreport-url] -This is the code repository for OpenID Connect (OIDC) broker. It is used in conjunction with Ubuntu authentication daemon authd. +This is the code repository for Authd OpenID Connect (OIDC) brokers. It is used in conjunction with Ubuntu authentication daemon authd. This project contains specific code for different OpenID Connect providers. We build different binaries based on build tags. diff --git a/cmd/oidc-broker/daemon/config.go b/cmd/authd-oidc/daemon/config.go similarity index 96% rename from cmd/oidc-broker/daemon/config.go rename to cmd/authd-oidc/daemon/config.go index 09d6c9320d..d2ff172e01 100644 --- a/cmd/oidc-broker/daemon/config.go +++ b/cmd/authd-oidc/daemon/config.go @@ -10,9 +10,9 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/ubuntu/authd-oidc-brokers/internal/consts" + "github.com/ubuntu/authd-oidc-brokers/internal/log" "github.com/ubuntu/decorate" - "github.com/ubuntu/oidc-broker/internal/consts" - "github.com/ubuntu/oidc-broker/internal/log" ) // initViperConfig sets verbosity level and add config env variables and file support based on name prefix. diff --git a/cmd/oidc-broker/daemon/daemon.go b/cmd/authd-oidc/daemon/daemon.go similarity index 96% rename from cmd/oidc-broker/daemon/daemon.go rename to cmd/authd-oidc/daemon/daemon.go index 46a6741d75..339f842bd7 100644 --- a/cmd/oidc-broker/daemon/daemon.go +++ b/cmd/authd-oidc/daemon/daemon.go @@ -11,9 +11,9 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/ubuntu/oidc-broker/internal/consts" - "github.com/ubuntu/oidc-broker/internal/daemon" - "github.com/ubuntu/oidc-broker/internal/dbusservice" + "github.com/ubuntu/authd-oidc-brokers/internal/consts" + "github.com/ubuntu/authd-oidc-brokers/internal/daemon" + "github.com/ubuntu/authd-oidc-brokers/internal/dbusservice" ) // App encapsulate commands and options of the daemon, which can be controlled by env variables and config files. diff --git a/cmd/oidc-broker/daemon/daemon_test.go b/cmd/authd-oidc/daemon/daemon_test.go similarity index 98% rename from cmd/oidc-broker/daemon/daemon_test.go rename to cmd/authd-oidc/daemon/daemon_test.go index 8c51025450..c30184441a 100644 --- a/cmd/oidc-broker/daemon/daemon_test.go +++ b/cmd/authd-oidc/daemon/daemon_test.go @@ -13,9 +13,9 @@ import ( "time" "github.com/stretchr/testify/require" - "github.com/ubuntu/oidc-broker/cmd/oidc-broker/daemon" - "github.com/ubuntu/oidc-broker/internal/consts" - "github.com/ubuntu/oidc-broker/internal/testutils" + "github.com/ubuntu/authd-oidc-brokers/cmd/authd-oidc/daemon" + "github.com/ubuntu/authd-oidc-brokers/internal/consts" + "github.com/ubuntu/authd-oidc-brokers/internal/testutils" ) var mockProvider *httptest.Server diff --git a/cmd/oidc-broker/daemon/export_test.go b/cmd/authd-oidc/daemon/export_test.go similarity index 100% rename from cmd/oidc-broker/daemon/export_test.go rename to cmd/authd-oidc/daemon/export_test.go diff --git a/cmd/oidc-broker/daemon/fs.go b/cmd/authd-oidc/daemon/fs.go similarity index 100% rename from cmd/oidc-broker/daemon/fs.go rename to cmd/authd-oidc/daemon/fs.go diff --git a/cmd/oidc-broker/daemon/version.go b/cmd/authd-oidc/daemon/version.go similarity index 92% rename from cmd/oidc-broker/daemon/version.go rename to cmd/authd-oidc/daemon/version.go index b814501967..5ba9121add 100644 --- a/cmd/oidc-broker/daemon/version.go +++ b/cmd/authd-oidc/daemon/version.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/spf13/cobra" - "github.com/ubuntu/oidc-broker/internal/consts" + "github.com/ubuntu/authd-oidc-brokers/internal/consts" ) func (a *App) installVersion() { diff --git a/cmd/oidc-broker/main.go b/cmd/authd-oidc/main.go similarity index 89% rename from cmd/oidc-broker/main.go rename to cmd/authd-oidc/main.go index f630b1ab29..44c8a179f6 100644 --- a/cmd/oidc-broker/main.go +++ b/cmd/authd-oidc/main.go @@ -9,10 +9,10 @@ import ( "sync" "syscall" + "github.com/ubuntu/authd-oidc-brokers/cmd/authd-oidc/daemon" + "github.com/ubuntu/authd-oidc-brokers/internal/consts" + "github.com/ubuntu/authd-oidc-brokers/po" "github.com/ubuntu/go-i18n" - "github.com/ubuntu/oidc-broker/cmd/oidc-broker/daemon" - "github.com/ubuntu/oidc-broker/internal/consts" - "github.com/ubuntu/oidc-broker/po" ) //FIXME go:generate go run ../generate_completion_documentation.go completion ../../generated diff --git a/cmd/oidc-broker/main_test.go b/cmd/authd-oidc/main_test.go similarity index 100% rename from cmd/oidc-broker/main_test.go rename to cmd/authd-oidc/main_test.go diff --git a/go.mod b/go.mod index 3f42cc2888..2c87024075 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/ubuntu/oidc-broker +module github.com/ubuntu/authd-oidc-brokers go 1.22.0 diff --git a/internal/broker/broker.go b/internal/broker/broker.go index ebbac5e096..86406d1c11 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -22,9 +22,9 @@ import ( "github.com/coreos/go-oidc/v3/oidc" "github.com/google/uuid" + "github.com/ubuntu/authd-oidc-brokers/internal/providers" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/group" "github.com/ubuntu/decorate" - "github.com/ubuntu/oidc-broker/internal/providers" - "github.com/ubuntu/oidc-broker/internal/providers/group" "golang.org/x/exp/slog" "golang.org/x/oauth2" ) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index cf28866e61..094de8e7ad 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -12,9 +12,9 @@ import ( "time" "github.com/stretchr/testify/require" - "github.com/ubuntu/oidc-broker/internal/broker" - "github.com/ubuntu/oidc-broker/internal/providers/group" - "github.com/ubuntu/oidc-broker/internal/testutils" + "github.com/ubuntu/authd-oidc-brokers/internal/broker" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/group" + "github.com/ubuntu/authd-oidc-brokers/internal/testutils" "golang.org/x/oauth2" "gopkg.in/yaml.v3" ) diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index 1054b12561..97e2fe7451 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -11,9 +11,9 @@ import ( "time" "github.com/stretchr/testify/require" - "github.com/ubuntu/oidc-broker/internal/broker" - "github.com/ubuntu/oidc-broker/internal/providers/group" - "github.com/ubuntu/oidc-broker/internal/testutils" + "github.com/ubuntu/authd-oidc-brokers/internal/broker" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/group" + "github.com/ubuntu/authd-oidc-brokers/internal/testutils" "golang.org/x/oauth2" ) diff --git a/internal/broker/options_test.go b/internal/broker/options_test.go index bf84372788..94880b6012 100644 --- a/internal/broker/options_test.go +++ b/internal/broker/options_test.go @@ -1,6 +1,6 @@ package broker -import "github.com/ubuntu/oidc-broker/internal/providers" +import "github.com/ubuntu/authd-oidc-brokers/internal/providers" // WithSkipSignatureCheck returns an option that skips the JWT signature check. func WithSkipSignatureCheck() Option { diff --git a/internal/consts/consts.go b/internal/consts/consts.go index 5ae79ddd12..9c63dcdfcd 100644 --- a/internal/consts/consts.go +++ b/internal/consts/consts.go @@ -10,7 +10,7 @@ var ( const ( // TEXTDOMAIN is the gettext domain for l10n. - TEXTDOMAIN = "oidc-broker" + TEXTDOMAIN = "authd-oidc" // DefaultLevelLog is the default logging level selected without any option. DefaultLevelLog = slog.LevelWarn diff --git a/internal/dbusservice/dbusservice.go b/internal/dbusservice/dbusservice.go index db566981c0..cae9f1810c 100644 --- a/internal/dbusservice/dbusservice.go +++ b/internal/dbusservice/dbusservice.go @@ -8,7 +8,7 @@ import ( "github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5/introspect" - "github.com/ubuntu/oidc-broker/internal/broker" + "github.com/ubuntu/authd-oidc-brokers/internal/broker" "gopkg.in/ini.v1" ) diff --git a/internal/dbusservice/localbus.go b/internal/dbusservice/localbus.go index e8ff09adad..423e36fb34 100644 --- a/internal/dbusservice/localbus.go +++ b/internal/dbusservice/localbus.go @@ -8,7 +8,7 @@ import ( "os" "github.com/godbus/dbus/v5" - "github.com/ubuntu/oidc-broker/internal/testutils" + "github.com/ubuntu/authd-oidc-brokers/internal/testutils" ) // getBus creates the local bus and returns a connection to the bus. diff --git a/internal/providers/default.go b/internal/providers/default.go index f16f8f1438..21514ecfc9 100644 --- a/internal/providers/default.go +++ b/internal/providers/default.go @@ -3,7 +3,7 @@ package providers import ( - "github.com/ubuntu/oidc-broker/internal/providers/noprovider" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/noprovider" ) // CurrentProviderInfo returns a generic oidc provider implementation. diff --git a/internal/providers/microsoft_entra_id/microsoft-entra-id.go b/internal/providers/microsoft_entra_id/microsoft-entra-id.go index 2370697173..a2a211bf6a 100644 --- a/internal/providers/microsoft_entra_id/microsoft-entra-id.go +++ b/internal/providers/microsoft_entra_id/microsoft-entra-id.go @@ -14,7 +14,7 @@ import ( "github.com/coreos/go-oidc/v3/oidc" msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go" msauth "github.com/microsoftgraph/msgraph-sdk-go-core/authentication" - "github.com/ubuntu/oidc-broker/internal/providers/group" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/group" "golang.org/x/oauth2" ) diff --git a/internal/providers/noprovider/noprovider.go b/internal/providers/noprovider/noprovider.go index 3b4c38b933..6f6f898727 100644 --- a/internal/providers/noprovider/noprovider.go +++ b/internal/providers/noprovider/noprovider.go @@ -6,7 +6,7 @@ import ( "fmt" "github.com/coreos/go-oidc/v3/oidc" - "github.com/ubuntu/oidc-broker/internal/providers/group" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/group" "golang.org/x/oauth2" ) diff --git a/internal/providers/providers.go b/internal/providers/providers.go index 2d183a7a1f..c37716a10f 100644 --- a/internal/providers/providers.go +++ b/internal/providers/providers.go @@ -2,7 +2,7 @@ package providers import ( - "github.com/ubuntu/oidc-broker/internal/providers/group" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/group" "golang.org/x/oauth2" ) diff --git a/internal/providers/withmsentraid.go b/internal/providers/withmsentraid.go index cbc1619dd4..e8e4cafd68 100644 --- a/internal/providers/withmsentraid.go +++ b/internal/providers/withmsentraid.go @@ -3,7 +3,7 @@ package providers import ( - "github.com/ubuntu/oidc-broker/internal/providers/microsoft_entra_id" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/microsoft_entra_id" ) // CurrentProviderInfo returns a Microsoft Entra ID provider implementation. diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index 7cc6b6698a..77ad842fab 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -11,7 +11,7 @@ import ( "time" "github.com/coreos/go-oidc/v3/oidc" - "github.com/ubuntu/oidc-broker/internal/providers/group" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/group" "golang.org/x/oauth2" ) diff --git a/snap/hooks/install b/snap/hooks/install index 0545e9eef0..c02d66a9c3 100644 --- a/snap/hooks/install +++ b/snap/hooks/install @@ -3,14 +3,14 @@ set -eu snap_base_dir=$(dirname ${SNAP}) -cat < ${SNAP_COMMON}/oidc-broker +cat < ${SNAP_COMMON}/oidc [authd] # This section is used by authd to identify and communicate with the broker. # It should not be edited. -name = OIDC Broker +name = OIDC brand_icon = ${snap_base_dir}/current/broker_icon.png -dbus_name = com.ubuntu.authd.OidcBroker -dbus_object = /com/ubuntu/authd/OidcBroker +dbus_name = com.ubuntu.authd.Oidc +dbus_object = /com/ubuntu/authd/Oidc [oidc] issuer = https://{issuer_url} diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index fb26965034..dd7ee0d5c8 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,4 +1,4 @@ -name: oidc-broker +name: authd-oidc summary: OIDC Broker for authd description: | Broker that enables OIDC authentication for authd. @@ -9,30 +9,30 @@ confinement: strict license: GPL-3.0+ apps: - oidc-broker: - command: bin/oidc-broker + authd-oidc: + command: bin/authd-oidc daemon: simple slots: - - dbus-oidc + - dbus-authd plugs: - network - - config-files + - config-file restart-condition: always slots: - dbus-oidc: + dbus-authd: interface: dbus bus: system - name: com.ubuntu.authd.OidcBroker + name: com.ubuntu.authd.Oidc plugs: - config-files: + config-file: interface: system-files read: - - /etc/authd/brokers.d/oidc-broker + - /etc/authd/brokers.d/oidc parts: - oidc-broker: + broker: source: . source-type: local plugin: go diff --git a/tools/go.mod b/tools/go.mod index 0b24c80e17..43f5149949 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -1,4 +1,4 @@ -module github.com/ubuntu/oidc-broker/tools +module github.com/ubuntu/authd-oidc-brokers/tools go 1.22.0 From 8cef4158fc3756c38b96c939ba592e94612db8f8 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Tue, 2 Jul 2024 08:10:48 +0200 Subject: [PATCH 0062/1670] Remove the config static file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In long term, we probably want to only ship a static configuration file and remove the hook, but that’s for another PR. --- config/oidc-broker | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 config/oidc-broker diff --git a/config/oidc-broker b/config/oidc-broker deleted file mode 100644 index 3555afa3d6..0000000000 --- a/config/oidc-broker +++ /dev/null @@ -1,13 +0,0 @@ -[authd] -name = OIDC Broker -brand_icon = {abs_path_to_icon} -dbus_name = com.ubuntu.authd.OidcBroker -dbus_object = /com/ubuntu/authd/OidcBroker - -[oidc] -issuer = https://{issuer_url} -client_id = {client_id} - -# The directory where the user's home directory will be created. -# The user home directory will be created in the format of $homedir_path/$username -home_base_dir = /home From f84676eccf98a3b8aa4c5b2aeca6d5d0f7e953db Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Tue, 2 Jul 2024 08:11:51 +0200 Subject: [PATCH 0063/1670] Update gitignore for snap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We don’t want to commit it by mistake --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ab682a31fd..42af04833c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore # # Binaries for programs and plugins +*.snap /authd-oidc cmd/authd-oidc/authd-oidc /authd-msentraid From d37e4ab7277199894f2c9344e69b7788f6c22e04 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Tue, 2 Jul 2024 09:00:08 +0200 Subject: [PATCH 0064/1670] Fix license We never use +. --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index dd7ee0d5c8..2b5d1b9d59 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -6,7 +6,7 @@ version: git grade: stable base: core24 confinement: strict -license: GPL-3.0+ +license: GPL-3.0 apps: authd-oidc: From a7097d35cf10f4c0c46e61ecb50009a3077dcb96 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Tue, 2 Jul 2024 09:59:25 +0200 Subject: [PATCH 0065/1670] Rename msentraid target branch --- .github/workflows/auto-updates.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto-updates.yml b/.github/workflows/auto-updates.yml index 5bc3182a4e..3ed53f5cfe 100644 --- a/.github/workflows/auto-updates.yml +++ b/.github/workflows/auto-updates.yml @@ -17,7 +17,7 @@ jobs: name: Update snap branches strategy: matrix: - branch_name: ["msentraid-broker"] + branch_name: ["msentraid"] runs-on: ubuntu-latest steps: - name: Install dependencies From 5eaa8afd93d248be84687d7e6ccb01c5094ffe8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 2 Jul 2024 19:10:11 +0200 Subject: [PATCH 0066/1670] broker: Use better wording for the qrcode button The button is selecting the authentication again, requesting a new login code, not the qrcode itself. So improve wording here a bit --- internal/broker/broker.go | 4 ++-- .../golden/successfully_select_device_auth | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 86406d1c11..0bb5f1d82d 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -316,11 +316,11 @@ func (b *Broker) generateUILayout(session *sessionInfo, authModeID string) (map[ uiLayout = map[string]string{ "type": "qrcode", "label": fmt.Sprintf( - "Scan the QR code or access %q and use the provided code", + "Scan the QR code or access %q and use the provided login code", response.VerificationURI, ), "wait": "true", - "button": "regenerate QR code", + "button": "Request new login code", "content": response.VerificationURI, "code": response.UserCode, } diff --git a/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_device_auth b/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_device_auth index 5cadae3325..59d9711147 100644 --- a/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_device_auth +++ b/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_device_auth @@ -1,6 +1,6 @@ -button: regenerate QR code +button: Request new login code code: user_code content: https://verification_uri.com -label: Scan the QR code or access "https://verification_uri.com" and use the provided code +label: Scan the QR code or access "https://verification_uri.com" and use the provided login code type: qrcode wait: "true" From 0e00f8e5cea9c29824cb3c0141f98dac6f5d40b1 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Wed, 3 Jul 2024 06:43:50 -0400 Subject: [PATCH 0067/1670] Bump Go to 1.22.5 Fixes Vulnerability #1: GO-2024-2963 Denial of service due to improper 100-continue handling in net/http More info: https://pkg.go.dev/vuln/GO-2024-2963 Standard library Found in: net/http@go1.22.4 Fixed in: net/http@go1.22.5 --- go.mod | 2 +- tools/go.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 2c87024075..0159ecb986 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/ubuntu/authd-oidc-brokers go 1.22.0 -toolchain go1.22.4 +toolchain go1.22.5 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 diff --git a/tools/go.mod b/tools/go.mod index 43f5149949..3e03a64d3f 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -2,7 +2,7 @@ module github.com/ubuntu/authd-oidc-brokers/tools go 1.22.0 -toolchain go1.22.4 +toolchain go1.22.5 require github.com/golangci/golangci-lint v1.59.1 From e5b7f666ffa8eab7c7f01c838b7f77c9c12e665d Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Wed, 3 Jul 2024 08:41:27 -0400 Subject: [PATCH 0068/1670] Turn userInfo into own type and refactor FetchUserInfo To prepare for the json marshaling changes, we need to turn the userInfo into its own type and add json annotations to the struct so that it can be marshaled correctly through json.Marshal. --- internal/broker/broker.go | 90 ++++++++----------- internal/broker/broker_test.go | 2 +- internal/broker/export_test.go | 15 ++-- internal/broker/helper_test.go | 21 +++-- ...r_info_with_default_home_when_not_provided | 18 ++-- .../successfully_fetch_user_info_with_groups | 18 ++-- ...uccessfully_fetch_user_info_without_groups | 14 ++- 7 files changed, 88 insertions(+), 90 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 0bb5f1d82d..1df5e8bf28 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -2,7 +2,6 @@ package broker import ( - "bytes" "context" "crypto/rand" "crypto/rsa" @@ -17,8 +16,6 @@ import ( "slices" "strings" "sync" - "text/template" - "time" "github.com/coreos/go-oidc/v3/oidc" "github.com/google/uuid" @@ -564,16 +561,14 @@ func (b *Broker) updateSession(sessionID string, session sessionInfo) error { // authCachedInfo represents the token that will be saved on disk for offline authentication. type authCachedInfo struct { Token *oauth2.Token - AcquiredAt time.Time RawIDToken string - UserInfo string + UserInfo userInfo } // cacheAuthInfo serializes the access token and cache it. func (b *Broker) cacheAuthInfo(session *sessionInfo, authInfo authCachedInfo, password string) (err error) { defer decorate.OnError(&err, "could not cache info") - authInfo.AcquiredAt = time.Now() content, err := json.Marshal(authInfo) if err != nil { return fmt.Errorf("could not marshal token: %v", err) @@ -636,53 +631,64 @@ func (b *Broker) loadAuthInfo(session *sessionInfo, password string) (loadedInfo return authCachedInfo{Token: tok, RawIDToken: refreshedIDToken}, false, nil } -func (b *Broker) fetchUserInfo(ctx context.Context, session *sessionInfo, t *authCachedInfo) (userClaims claims, userGroups []group.Info, err error) { +func (b *Broker) fetchUserInfo(ctx context.Context, session *sessionInfo, t *authCachedInfo) (userInfo userInfo, err error) { defer decorate.OnError(&err, "could not fetch user info") - // If we didn't restore user information from the cache, we need to query the provider for it, which means - // we need to validate the token. - idToken, err := b.auth.provider.Verifier(&b.auth.oidcCfg).Verify(ctx, t.RawIDToken) + userClaims, err := b.userClaims(ctx, t.RawIDToken, session.username) if err != nil { - return claims{}, nil, fmt.Errorf("could not verify token: %v", err) + return userInfo, err } - userGroups, err = b.providerInfo.GetGroups(t.Token) + userGroups, err := b.providerInfo.GetGroups(t.Token) if err != nil { - return claims{}, nil, fmt.Errorf("could not get user groups: %v", err) + return userInfo, fmt.Errorf("could not get user groups: %v", err) + } + + return b.userInfoFromClaims(userClaims, userGroups), nil +} + +type claims struct { + Name string `json:"name"` + PreferredUserName string `json:"preferred_username"` + Email string `json:"email"` + EmailVerified bool `json:"email_verified"` + Sub string `json:"sub"` +} + +func (b *Broker) userClaims(ctx context.Context, rawIDToken, username string) (userClaims claims, err error) { + // We need to verify the token to get the user claims. + idToken, err := b.auth.provider.Verifier(&b.auth.oidcCfg).Verify(ctx, rawIDToken) + if err != nil { + return claims{}, fmt.Errorf("could not verify token: %v", err) } if err := idToken.Claims(&userClaims); err != nil { - return claims{}, nil, fmt.Errorf("could not get user info: %v", err) + return claims{}, fmt.Errorf("could not get user info: %v", err) } if userClaims.Email == "" { - return claims{}, nil, errors.New("user email is required, but was not provided") + return claims{}, errors.New("user email is required, but was not provided") } - if userClaims.Email != session.username { - return claims{}, nil, fmt.Errorf("returned user %q does not match the selected one %q", userClaims.Email, session.username) + if userClaims.Email != username { + return claims{}, fmt.Errorf("returned user %q does not match the selected one %q", userClaims.Email, username) } - return userClaims, userGroups, nil + return userClaims, nil } -type claims struct { - Name string `json:"name"` - PreferredUserName string `json:"preferred_username"` - Email string `json:"email"` - EmailVerified bool `json:"email_verified"` - Sub string `json:"sub"` +// userInfo represents the user information obtained from the provider. +type userInfo struct { + Name string `json:"name"` + UUID string `json:"uuid"` + Home string `json:"dir"` + Shell string `json:"shell"` + Gecos string `json:"gecos"` + Groups []group.Info `json:"groups"` } -func (b *Broker) userInfoFromClaims(userClaims claims, groups []group.Info) (string, error) { - user := struct { - Name string - UUID string - Home string - Shell string - Gecos string - Groups []group.Info - }{ +func (b *Broker) userInfoFromClaims(userClaims claims, groups []group.Info) userInfo { + return userInfo{ Name: userClaims.Email, UUID: userClaims.Sub, Home: filepath.Join(b.homeDirPath, userClaims.Email), @@ -690,22 +696,4 @@ func (b *Broker) userInfoFromClaims(userClaims claims, groups []group.Info) (str Gecos: userClaims.Name, Groups: groups, } - - var buf bytes.Buffer - err := template.Must(template.New("").Parse(`{ - "name": "{{.Name}}", - "uuid": "{{.UUID}}", - "gecos": "{{.Gecos}}", - "dir": "{{.Home}}", - "shell": "{{.Shell}}", - "groups": [ {{range $index, $g := .Groups}} - {{- if $index}}, {{end -}} - {"name": "{{.Name}}", "ugid": "{{.UGID}}"} - {{- end}} ] -}`)).Execute(&buf, user) - if err != nil { - return "", err - } - - return buf.String(), nil } diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 094de8e7ad..5d1128c891 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -610,7 +610,7 @@ func TestFetchUserInfo(t *testing.T) { } require.NoError(t, err, "FetchUserInfo should not have returned an error") - want := testutils.LoadWithUpdateFromGolden(t, got) + want := testutils.LoadWithUpdateFromGoldenYAML(t, got) require.Equal(t, want, got, "FetchUserInfo should have returned the expected value") }) } diff --git a/internal/broker/export_test.go b/internal/broker/export_test.go index fb983b8653..75c18db314 100644 --- a/internal/broker/export_test.go +++ b/internal/broker/export_test.go @@ -94,16 +94,21 @@ func writeTrashToken(path, challenge string) error { } // FetchUserInfo exposes the broker's fetchUserInfo method for tests. -func (b *Broker) FetchUserInfo(sessionID string, cachedInfo *authCachedInfo) (string, error) { +// +//nolint:revive // This is a test helper and the returned userInfo will be marshaled for golden files. +func (b *Broker) FetchUserInfo(sessionID string, cachedInfo *authCachedInfo) (userInfo, error) { s, err := b.getSession(sessionID) if err != nil { - return "", err + return userInfo{}, err } - uInfo, groups, err := b.fetchUserInfo(context.TODO(), &s, cachedInfo) + uInfo, err := b.fetchUserInfo(context.TODO(), &s, cachedInfo) if err != nil { - return "", err + return userInfo{}, err } - return b.userInfoFromClaims(uInfo, groups) + return uInfo, err } + +// UserInfo exposes the private userInfo for tests. +type UserInfo = userInfo diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index 97e2fe7451..c57363c50f 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -135,18 +135,21 @@ func generateCachedInfo(t *testing.T, preexistentToken, issuer string) *broker.A tok = testTokens["valid"] } - tok.UserInfo = fmt.Sprintf(`{ - "name": "%[1]s", - "uuid": "saved-user-id", - "gecos": "saved-user", - "dir": "/home/%[1]s", - "shell": "/usr/bin/bash", - "groups": [{"name": "saved-remote-group", "gid": "12345"}, {"name": "saved-local-group", "gid": ""}] -}`, email) + tok.UserInfo = broker.UserInfo{ + Name: email, + UUID: "saved-user-id", + Home: "/home/" + email, + Gecos: "saved-user", + Shell: "/usr/bin/bash", + Groups: []group.Info{ + {Name: "saved-remote-group", UGID: "12345"}, + {Name: "saved-local-group", UGID: ""}, + }, + } if preexistentToken == "invalid-id" { encodedToken = ".invalid." - tok.UserInfo = "" + tok.UserInfo = broker.UserInfo{} } tok.Token = tok.Token.WithExtra(map[string]string{"id_token": encodedToken}) diff --git a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_default_home_when_not_provided b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_default_home_when_not_provided index 1e20b9fd65..5a6359313b 100644 --- a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_default_home_when_not_provided +++ b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_default_home_when_not_provided @@ -1,8 +1,10 @@ -{ - "name": "test-user@email.com", - "uuid": "saved-user-id", - "gecos": "saved-user", - "dir": "/home/test-user@email.com", - "shell": "/usr/bin/bash", - "groups": [ {"name": "remote-group", "ugid": "12345"}, {"name": "linux-local-group", "ugid": ""} ] -} \ No newline at end of file +name: test-user@email.com +uuid: saved-user-id +home: /home/test-user@email.com +shell: /usr/bin/bash +gecos: saved-user +groups: + - name: remote-group + ugid: "12345" + - name: linux-local-group + ugid: "" diff --git a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_groups b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_groups index 845e65ddc9..96d01f3fa6 100644 --- a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_groups +++ b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_groups @@ -1,8 +1,10 @@ -{ - "name": "test-user@email.com", - "uuid": "saved-user-id", - "gecos": "saved-user", - "dir": "/home/userInfoTests/test-user@email.com", - "shell": "/usr/bin/bash", - "groups": [ {"name": "remote-group", "ugid": "12345"}, {"name": "linux-local-group", "ugid": ""} ] -} \ No newline at end of file +name: test-user@email.com +uuid: saved-user-id +home: /home/userInfoTests/test-user@email.com +shell: /usr/bin/bash +gecos: saved-user +groups: + - name: remote-group + ugid: "12345" + - name: linux-local-group + ugid: "" diff --git a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_without_groups b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_without_groups index 601722ae01..0a0ae71794 100644 --- a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_without_groups +++ b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_without_groups @@ -1,8 +1,6 @@ -{ - "name": "test-user@email.com", - "uuid": "saved-user-id", - "gecos": "saved-user", - "dir": "/home/userInfoTests/test-user@email.com", - "shell": "/usr/bin/bash", - "groups": [ ] -} \ No newline at end of file +name: test-user@email.com +uuid: saved-user-id +home: /home/userInfoTests/test-user@email.com +shell: /usr/bin/bash +gecos: saved-user +groups: [] From 0a6045e094e9c334f6214600a455300227e81675 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Wed, 3 Jul 2024 08:47:02 -0400 Subject: [PATCH 0069/1670] Refactor IsAuthenticated logic to ensure valid JSON return We had some problems when error messages had double quotes in them, as it would break the JSON unmarshaling. To avoid this kind of errors again, we now use separate types for the returned data and marshal them through json.Marshal, which prevents mistakes that could happen when "manually" generating the message. --- internal/broker/authresponses.go | 19 +++++ internal/broker/broker.go | 69 ++++++++++--------- internal/broker/broker_test.go | 18 +++-- internal/broker/helper_test.go | 11 +-- .../first_call | 10 +-- .../first_call | 10 +-- .../first_call | 10 +-- .../first_call | 2 +- .../second_call | 10 +-- .../first_call | 2 +- .../error_when_can_not_cache_token/first_call | 2 +- .../second_call | 2 +- .../first_call | 2 +- .../first_call | 2 +- .../second_call | 2 +- .../first_call | 2 +- .../second_call | 2 +- .../first_call | 2 +- .../first_call | 2 +- .../first_call | 2 +- .../first_call | 2 +- .../first_call | 2 +- .../first_call | 2 +- .../first_call | 2 +- .../first_call | 4 +- .../first_call | 4 +- .../first_call | 2 +- .../first_call | 2 +- .../cache/.empty | 0 .../first_call | 3 + .../second_call | 3 + .../first_call | 10 +-- .../first_call | 2 +- .../second_call | 10 +-- internal/providers/group/info.go | 4 +- 35 files changed, 110 insertions(+), 123 deletions(-) create mode 100644 internal/broker/authresponses.go create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/cache/.empty create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/second_call diff --git a/internal/broker/authresponses.go b/internal/broker/authresponses.go new file mode 100644 index 0000000000..ddb8663e7b --- /dev/null +++ b/internal/broker/authresponses.go @@ -0,0 +1,19 @@ +package broker + +type isAuthenticatedDataResponse interface { + isAuthenticatedDataResponse() +} + +// userInfoMessage represents the user information message that is returned to authd. +type userInfoMessage struct { + UserInfo userInfo `json:"userinfo"` +} + +func (userInfoMessage) isAuthenticatedDataResponse() {} + +// errorMessage represents the error message that is returned to authd. +type errorMessage struct { + Message string `json:"message"` +} + +func (errorMessage) isAuthenticatedDataResponse() {} diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 1df5e8bf28..8fd9034cd6 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -349,35 +349,38 @@ func (b *Broker) generateUILayout(session *sessionInfo, authModeID string) (map[ func (b *Broker) IsAuthenticated(sessionID, authenticationData string) (string, string, error) { session, err := b.getSession(sessionID) if err != nil { - return AuthDenied, "", err + return AuthDenied, "{}", err } var authData map[string]string if authenticationData != "" { if err := json.Unmarshal([]byte(authenticationData), &authData); err != nil { - return AuthDenied, "", fmt.Errorf("authentication data is not a valid json value: %v", err) + return AuthDenied, "{}", fmt.Errorf("authentication data is not a valid json value: %v", err) } } ctx, err := b.startAuthenticate(sessionID) if err != nil { - return AuthDenied, "", err + return AuthDenied, "{}", err } // Cleans up the IsAuthenticated context when the call is done. defer b.CancelIsAuthenticated(sessionID) authDone := make(chan struct{}) - var access, data string + var access string + var iadResponse isAuthenticatedDataResponse go func() { - access, data = b.handleIsAuthenticated(ctx, &session, authData) + access, iadResponse = b.handleIsAuthenticated(ctx, &session, authData) close(authDone) }() select { case <-authDone: case <-ctx.Done(): - return AuthCancelled, `{"message": "authentication request cancelled"}`, ctx.Err() + // We can ignore the error here since the message is constant. + msg, _ := json.Marshal(errorMessage{Message: "authentication request cancelled"}) + return AuthCancelled, string(msg), ctx.Err() } switch access { @@ -385,7 +388,7 @@ func (b *Broker) IsAuthenticated(sessionID, authenticationData string) (string, session.attemptsPerMode[session.selectedMode]++ if session.attemptsPerMode[session.selectedMode] == maxAuthAttempts { access = AuthDenied - data = `{"message": "maximum number of attempts reached"}` + iadResponse = errorMessage{Message: "maximum number of attempts reached"} } case AuthNext: @@ -393,88 +396,90 @@ func (b *Broker) IsAuthenticated(sessionID, authenticationData string) (string, } if err = b.updateSession(sessionID, session); err != nil { - return AuthDenied, "", err + return AuthDenied, "{}", err + } + + encoded, err := json.Marshal(iadResponse) + if err != nil { + return AuthDenied, "{}", fmt.Errorf("could not parse data to JSON: %v", err) + } + + data := string(encoded) + if data == "null" { + data = "{}" } return access, data, nil } -func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo, authData map[string]string) (access, data string) { +func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo, authData map[string]string) (access string, data isAuthenticatedDataResponse) { // Decrypt challenge if present. challenge, err := decodeRawChallenge(b.privateKey, authData["challenge"]) if err != nil { - return AuthRetry, fmt.Sprintf(`{"message": "could not decode challenge: %v"}`, err) + return AuthRetry, errorMessage{Message: fmt.Sprintf("could not decode challenge: %v", err)} } var authInfo authCachedInfo - var userClaims claims - var groups []group.Info - offline := false switch session.selectedMode { case "device_auth": response, ok := session.authInfo["response"].(*oauth2.DeviceAuthResponse) if !ok { - return AuthDenied, `{"message": "could not get required response"}` + return AuthDenied, errorMessage{Message: "could not get required response"} } t, err := b.auth.oauthCfg.DeviceAccessToken(ctx, response, b.providerInfo.AuthOptions()...) if err != nil { - return AuthRetry, fmt.Sprintf(`{"message": "could not authenticate user: %v"}`, err) + return AuthRetry, errorMessage{Message: fmt.Sprintf("could not authenticate user: %v", err)} } rawIDToken, ok := t.Extra("id_token").(string) if !ok { - return AuthDenied, `{"message": "could not get id_token"}` + return AuthDenied, errorMessage{Message: "could not get id_token"} } session.authInfo["auth_info"] = authCachedInfo{Token: t, RawIDToken: rawIDToken} - return AuthNext, "" + return AuthNext, nil case "password": authInfo, offline, err = b.loadAuthInfo(session, challenge) if err != nil { - return AuthRetry, fmt.Sprintf(`{"message": "could not authenticate user: %v"}`, err) + return AuthRetry, errorMessage{Message: fmt.Sprintf("could not authenticate user: %v", err)} } if session.mode == "passwd" { session.authInfo["auth_info"] = authInfo - return AuthNext, "" + return AuthNext, nil } case "newpassword": if challenge == "" { - return AuthRetry, `{"message": "challenge must not be empty"}` + return AuthRetry, errorMessage{Message: "challenge must not be empty"} } var ok bool // This mode must always come after a authentication mode, so it has to have an auth_info. authInfo, ok = session.authInfo["auth_info"].(authCachedInfo) if !ok { - return AuthDenied, `{"message": "could not get required information"}` + return AuthDenied, errorMessage{Message: "could not get required information"} } } - if authInfo.UserInfo == "" { - userClaims, groups, err = b.fetchUserInfo(ctx, session, &authInfo) - if err != nil { - return AuthDenied, fmt.Sprintf(`{"message": "could not get user info: %v"}`, err) - } - - authInfo.UserInfo, err = b.userInfoFromClaims(userClaims, groups) + if authInfo.UserInfo.Name == "" { + authInfo.UserInfo, err = b.fetchUserInfo(ctx, session, &authInfo) if err != nil { - return AuthDenied, fmt.Sprintf(`{"message": "could not parse user info from claims: %v"}`, err) + return AuthDenied, errorMessage{Message: fmt.Sprintf("could not get user info: %v", err)} } } if offline { - return AuthGranted, fmt.Sprintf(`{"userinfo": %s}`, authInfo.UserInfo) + return AuthGranted, userInfoMessage{UserInfo: authInfo.UserInfo} } if err := b.cacheAuthInfo(session, authInfo, challenge); err != nil { - return AuthRetry, fmt.Sprintf(`{"message": "could not update cached info: %v"}`, err) + return AuthRetry, errorMessage{Message: fmt.Sprintf("could not update cached info: %v", err)} } - return AuthGranted, fmt.Sprintf(`{"userinfo": %s}`, authInfo.UserInfo) + return AuthGranted, userInfoMessage{UserInfo: authInfo.UserInfo} } func (b *Broker) startAuthenticate(sessionID string) (context.Context, error) { diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 5d1128c891..1e7183922b 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -2,6 +2,7 @@ package broker_test import ( "context" + "encoding/json" "flag" "fmt" "net/http/httptest" @@ -164,7 +165,7 @@ func TestGetAuthenticationModes(t *testing.T) { provider, stopServer = testutils.StartMockProvider(address, opts...) t.Cleanup(stopServer) } - b, sessionID, _ := newBrokerForTests(t, t.TempDir(), provider.URL, tc.sessionMode) + b, sessionID, _ := newBrokerForTests(t, t.TempDir(), provider.URL, tc.sessionMode, "") if tc.sessionID == "-" { sessionID = "" @@ -256,7 +257,7 @@ func TestSelectAuthenticationMode(t *testing.T) { sessionType = "passwd" } - b, sessionID, _ := newBrokerForTests(t, t.TempDir(), provider.URL, sessionType) + b, sessionID, _ := newBrokerForTests(t, t.TempDir(), provider.URL, sessionType, "") if tc.tokenExists { err := os.MkdirAll(filepath.Dir(b.TokenPathForSession(sessionID)), 0700) require.NoError(t, err, "Setup: MkdirAll should not have returned an error") @@ -290,7 +291,9 @@ func TestIsAuthenticated(t *testing.T) { correctPassword := "password" tests := map[string]struct { - sessionMode string + sessionMode string + username string + firstMode string firstChallenge string firstAuthInfo map[string]any @@ -365,6 +368,7 @@ func TestIsAuthenticated(t *testing.T) { "token": (&oauth2.Token{}).WithExtra(map[string]interface{}{"id_token": "invalid"}), }, }, + "Error when selected username does not match the provider one": {username: "not-matching", firstChallenge: "-", wantSecondCall: true}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { @@ -390,7 +394,7 @@ func TestIsAuthenticated(t *testing.T) { defer cleanup() provider = p } - b, sessionID, key := newBrokerForTests(t, cacheDir, provider.URL, tc.sessionMode) + b, sessionID, key := newBrokerForTests(t, cacheDir, provider.URL, tc.sessionMode, tc.username) if tc.preexistentToken != "" { tok := generateCachedInfo(t, tc.preexistentToken, provider.URL) @@ -449,6 +453,7 @@ func TestIsAuthenticated(t *testing.T) { } access, data, err := b.IsAuthenticated(sessionID, authData) + require.True(t, json.Valid([]byte(data)), "IsAuthenticated returned data must be a valid JSON") // Redact variant values from the response data = strings.ReplaceAll(data, sessionID, "SESSION_ID") @@ -489,6 +494,7 @@ func TestIsAuthenticated(t *testing.T) { updateAuthModes(t, b, sessionID, "newpassword") access, data, err := b.IsAuthenticated(sessionID, secondAuthData) + require.True(t, json.Valid([]byte(data)), "IsAuthenticated returned data must be a valid JSON") // Redact variant values from the response data = strings.ReplaceAll(data, sessionID, "SESSION_ID") @@ -623,7 +629,7 @@ func TestCancelIsAuthenticated(t *testing.T) { provider, cleanup := testutils.StartMockProvider("", testutils.WithHandler("/token", testutils.HangingHandler(ctx))) t.Cleanup(cleanup) - b, sessionID, _ := newBrokerForTests(t, t.TempDir(), provider.URL, "auth") + b, sessionID, _ := newBrokerForTests(t, t.TempDir(), provider.URL, "auth", "") updateAuthModes(t, b, sessionID, "device_auth") stopped := make(chan struct{}) @@ -644,7 +650,7 @@ func TestCancelIsAuthenticated(t *testing.T) { func TestEndSession(t *testing.T) { t.Parallel() - b, sessionID, _ := newBrokerForTests(t, t.TempDir(), defaultProvider.URL, "auth") + b, sessionID, _ := newBrokerForTests(t, t.TempDir(), defaultProvider.URL, "auth", "") // Try to end a session that does not exist err := b.EndSession("nonexistent") diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index c57363c50f..bace85e4b2 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -17,9 +17,9 @@ import ( "golang.org/x/oauth2" ) -// newBrokerForTests is a helper function to create a new broker for tests and already starts a session for user -// t.Name() normalized. -func newBrokerForTests(t *testing.T, cachePath, issuerURL, mode string) (b *broker.Broker, id, key string) { +// newBrokerForTests is a helper function to create a new broker for tests and already starts a session for the +// specified user. +func newBrokerForTests(t *testing.T, cachePath, issuerURL, mode, username string) (b *broker.Broker, id, key string) { t.Helper() cfg := broker.Config{ @@ -40,7 +40,10 @@ func newBrokerForTests(t *testing.T, cachePath, issuerURL, mode string) (b *brok ) require.NoError(t, err, "Setup: New should not have returned an error") - id, key, err = b.NewSession("test-user@email.com", "some lang", mode) + if username == "" { + username = "test-user@email.com" + } + id, key, err = b.NewSession(username, "some lang", mode) require.NoError(t, err, "Setup: NewSession should not have returned an error") return b, id, key diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/first_call index 521a9376df..ba20e9e1f2 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/first_call @@ -1,11 +1,3 @@ access: granted -data: |- - {"userinfo": { - "name": "test-user@email.com", - "uuid": "test-user-id", - "gecos": "test-user", - "dir": "/home/test-user@email.com", - "shell": "/usr/bin/bash", - "groups": [ {"name": "remote-group", "ugid": "12345"}, {"name": "linux-local-group", "ugid": ""} ] - }} +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/first_call index bd0029412a..72fec9bc67 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/first_call @@ -1,11 +1,3 @@ access: granted -data: |- - {"userinfo": { - "name": "test-user@email.com", - "uuid": "saved-user-id", - "gecos": "saved-user", - "dir": "/home/test-user@email.com", - "shell": "/usr/bin/bash", - "groups": [ {"name": "remote-group", "ugid": "12345"}, {"name": "linux-local-group", "ugid": ""} ] - }} +data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"saved-user","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/first_call index b72d8b1117..1d0929cfbd 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/first_call @@ -1,11 +1,3 @@ access: granted -data: |- - {"userinfo": { - "name": "test-user@email.com", - "uuid": "saved-user-id", - "gecos": "saved-user", - "dir": "/home/test-user@email.com", - "shell": "/usr/bin/bash", - "groups": [{"name": "saved-remote-group", "gid": "12345"}, {"name": "saved-local-group", "gid": ""}] - }} +data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"saved-user","groups":[{"name":"saved-remote-group","ugid":"12345"},{"name":"saved-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/first_call index de73804437..d0887a134f 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/first_call @@ -1,3 +1,3 @@ access: next -data: "" +data: '{}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/second_call index 521a9376df..ba20e9e1f2 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/second_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/second_call @@ -1,11 +1,3 @@ access: granted -data: |- - {"userinfo": { - "name": "test-user@email.com", - "uuid": "test-user-id", - "gecos": "test-user", - "dir": "/home/test-user@email.com", - "shell": "/usr/bin/bash", - "groups": [ {"name": "remote-group", "ugid": "12345"}, {"name": "linux-local-group", "ugid": ""} ] - }} +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_authentication_data_is_invalid/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_authentication_data_is_invalid/first_call index fe56876788..1ed5d6d1d0 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_authentication_data_is_invalid/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_authentication_data_is_invalid/first_call @@ -1,3 +1,3 @@ access: denied -data: "" +data: '{}' err: 'authentication data is not a valid json value: invalid character ''i'' looking for beginning of value' diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/first_call index de73804437..d0887a134f 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/first_call @@ -1,3 +1,3 @@ access: next -data: "" +data: '{}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/second_call index def12e2d2f..caa6fbf909 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/second_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/second_call @@ -1,3 +1,3 @@ access: retry -data: '{"message": "could not update cached info: could not cache info: could not create token directory: mkdir provider_cache_path: permission denied"}' +data: '{"message":"could not update cached info: could not cache info: could not create token directory: mkdir provider_cache_path: permission denied"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_challenge_can_not_be_decrypted/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_challenge_can_not_be_decrypted/first_call index 18e5039829..3e188452d6 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_challenge_can_not_be_decrypted/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_challenge_can_not_be_decrypted/first_call @@ -1,3 +1,3 @@ access: retry -data: '{"message": "could not decode challenge: could not decode challenge"}' +data: '{"message":"could not decode challenge: could not decode challenge"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/first_call index de73804437..d0887a134f 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/first_call @@ -1,3 +1,3 @@ access: next -data: "" +data: '{}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/second_call index b035cf6626..db4bb9d028 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/second_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/second_call @@ -1,3 +1,3 @@ access: retry -data: '{"message": "challenge must not be empty"}' +data: '{"message":"challenge must not be empty"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/first_call index de73804437..d0887a134f 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/first_call @@ -1,3 +1,3 @@ access: next -data: "" +data: '{}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/second_call index 3597173349..d3ee131870 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/second_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/second_call @@ -1,3 +1,3 @@ access: denied -data: "" +data: '{}' err: IsAuthenticated already running for session "SESSION_ID" diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/first_call index c5186dbd62..7260101931 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/first_call @@ -1,3 +1,3 @@ access: denied -data: '{"message": "could not get required information"}' +data: '{"message":"could not get required information"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/first_call index c5186dbd62..7260101931 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/first_call @@ -1,3 +1,3 @@ access: denied -data: '{"message": "could not get required information"}' +data: '{"message":"could not get required information"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_token_is_not_set/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_token_is_not_set/first_call index c5186dbd62..7260101931 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_token_is_not_set/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_token_is_not_set/first_call @@ -1,3 +1,3 @@ access: denied -data: '{"message": "could not get required information"}' +data: '{"message":"could not get required information"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/first_call index 873b615cbd..828bae2b62 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/first_call @@ -1,3 +1,3 @@ access: retry -data: '{"message": "could not authenticate user: could not load cached info: could not refresh token: oauth2: token expired and refresh token is not set"}' +data: '{"message":"could not authenticate user: could not load cached info: could not refresh token: oauth2: token expired and refresh token is not set"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/first_call index 292abd2291..c46e0130e7 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/first_call @@ -1,3 +1,3 @@ access: denied -data: '{"message": "could not get user info: could not fetch user info: could not verify token: oidc: failed to unmarshal claims: invalid character ''\u008a'' looking for beginning of value"}' +data: '{"message":"could not get user info: could not fetch user info: could not verify token: oidc: failed to unmarshal claims: invalid character ''\\u008a'' looking for beginning of value"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call index 21f07da868..8545112d4b 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call @@ -1,3 +1,3 @@ access: retry -data: '{"message": "could not authenticate user: could not load cached info: could not read token: open provider_cache_path/test-user@email.com.cache: no such file or directory"}' +data: '{"message":"could not authenticate user: could not load cached info: could not read token: open provider_cache_path/test-user@email.com.cache: no such file or directory"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/first_call index 9a155b08ed..0d8f437161 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/first_call @@ -1,3 +1,3 @@ access: retry -data: '{"message": "could not authenticate user: could not load cached info: could not unmarshal token: invalid character ''T'' looking for beginning of value"}' +data: '{"message":"could not authenticate user: could not load cached info: could not unmarshal token: invalid character ''T'' looking for beginning of value"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/first_call index 86c05dcda3..5c32b2e8ad 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/first_call @@ -1,5 +1,3 @@ access: retry -data: |- - {"message": "could not authenticate user: could not load cached info: could not refresh token: oauth2: cannot fetch token: 400 Bad Request - Response: "} +data: '{"message":"could not authenticate user: could not load cached info: could not refresh token: oauth2: cannot fetch token: 400 Bad Request\nResponse: "}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token/first_call index 9196360dd2..609e9da6a1 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token/first_call @@ -1,5 +1,3 @@ access: retry -data: |- - {"message": "could not authenticate user: oauth2: cannot fetch token: 503 Service Unavailable - Response: "} +data: '{"message":"could not authenticate user: oauth2: cannot fetch token: 503 Service Unavailable\nResponse: "}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_response_is_invalid/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_response_is_invalid/first_call index 288a94c9a5..daeaf044ae 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_response_is_invalid/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_response_is_invalid/first_call @@ -1,3 +1,3 @@ access: denied -data: '{"message": "could not get required response"}' +data: '{"message":"could not get required response"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/first_call index c010949675..010bc15efb 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/first_call @@ -1,3 +1,3 @@ access: retry -data: '{"message": "could not authenticate user: could not load cached info: could not deserialize token: cipher: message authentication failed"}' +data: '{"message":"could not authenticate user: could not load cached info: could not deserialize token: cipher: message authentication failed"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/cache/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/first_call new file mode 100644 index 0000000000..d0887a134f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/first_call @@ -0,0 +1,3 @@ +access: next +data: '{}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/second_call new file mode 100644 index 0000000000..c1ae6a98b7 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/second_call @@ -0,0 +1,3 @@ +access: denied +data: '{"message":"could not get user info: could not fetch user info: returned user \"test-user@email.com\" does not match the selected one \"not-matching\""}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call index bd0029412a..72fec9bc67 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call @@ -1,11 +1,3 @@ access: granted -data: |- - {"userinfo": { - "name": "test-user@email.com", - "uuid": "saved-user-id", - "gecos": "saved-user", - "dir": "/home/test-user@email.com", - "shell": "/usr/bin/bash", - "groups": [ {"name": "remote-group", "ugid": "12345"}, {"name": "linux-local-group", "ugid": ""} ] - }} +data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"saved-user","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/first_call index de73804437..d0887a134f 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/first_call @@ -1,3 +1,3 @@ access: next -data: "" +data: '{}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/second_call index 521a9376df..ba20e9e1f2 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/second_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/second_call @@ -1,11 +1,3 @@ access: granted -data: |- - {"userinfo": { - "name": "test-user@email.com", - "uuid": "test-user-id", - "gecos": "test-user", - "dir": "/home/test-user@email.com", - "shell": "/usr/bin/bash", - "groups": [ {"name": "remote-group", "ugid": "12345"}, {"name": "linux-local-group", "ugid": ""} ] - }} +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' err: diff --git a/internal/providers/group/info.go b/internal/providers/group/info.go index f6673d6fc9..73afa3b1d6 100644 --- a/internal/providers/group/info.go +++ b/internal/providers/group/info.go @@ -3,6 +3,6 @@ package group // Info represents the group information that is fetched by the broker. type Info struct { - Name string - UGID string + Name string `json:"name"` + UGID string `json:"ugid"` } From cc6184473e6a5a72744067a3c2073a338fb48113 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Wed, 3 Jul 2024 10:49:09 -0400 Subject: [PATCH 0070/1670] Fail authentication sooner if failed to fetch user info When first authenticating with QRCode, the user would have to also create its local password before knowing if the authentication failed or not. Now, the user info is fetched before the password creation prompt and authentication will now fail before it also. --- internal/broker/broker.go | 22 ++++++++++++------- internal/broker/broker_test.go | 3 ++- .../first_call | 4 ++-- .../second_call | 3 --- 4 files changed, 18 insertions(+), 14 deletions(-) delete mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/second_call diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 8fd9034cd6..1e3d2cc484 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -437,7 +437,13 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo return AuthDenied, errorMessage{Message: "could not get id_token"} } - session.authInfo["auth_info"] = authCachedInfo{Token: t, RawIDToken: rawIDToken} + authInfo = authCachedInfo{Token: t, RawIDToken: rawIDToken} + authInfo.UserInfo, err = b.fetchUserInfo(ctx, session, &authInfo) + if err != nil { + return AuthDenied, errorMessage{Message: fmt.Sprintf("could not get user info: %v", err)} + } + + session.authInfo["auth_info"] = authInfo return AuthNext, nil case "password": @@ -446,6 +452,13 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo return AuthRetry, errorMessage{Message: fmt.Sprintf("could not authenticate user: %v", err)} } + if authInfo.UserInfo.Name == "" { + authInfo.UserInfo, err = b.fetchUserInfo(ctx, session, &authInfo) + if err != nil { + return AuthDenied, errorMessage{Message: fmt.Sprintf("could not get user info: %v", err)} + } + } + if session.mode == "passwd" { session.authInfo["auth_info"] = authInfo return AuthNext, nil @@ -464,13 +477,6 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo } } - if authInfo.UserInfo.Name == "" { - authInfo.UserInfo, err = b.fetchUserInfo(ctx, session, &authInfo) - if err != nil { - return AuthDenied, errorMessage{Message: fmt.Sprintf("could not get user info: %v", err)} - } - } - if offline { return AuthGranted, userInfoMessage{UserInfo: authInfo.UserInfo} } diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 1e7183922b..21e856aeae 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -368,7 +368,8 @@ func TestIsAuthenticated(t *testing.T) { "token": (&oauth2.Token{}).WithExtra(map[string]interface{}{"id_token": "invalid"}), }, }, - "Error when selected username does not match the provider one": {username: "not-matching", firstChallenge: "-", wantSecondCall: true}, + // This test case also tests that errors with double quotes are marshaled to JSON correctly. + "Error when selected username does not match the provider one": {username: "not-matching", firstChallenge: "-"}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/first_call index d0887a134f..c1ae6a98b7 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/first_call @@ -1,3 +1,3 @@ -access: next -data: '{}' +access: denied +data: '{"message":"could not get user info: could not fetch user info: returned user \"test-user@email.com\" does not match the selected one \"not-matching\""}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/second_call deleted file mode 100644 index c1ae6a98b7..0000000000 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/second_call +++ /dev/null @@ -1,3 +0,0 @@ -access: denied -data: '{"message":"could not get user info: could not fetch user info: returned user \"test-user@email.com\" does not match the selected one \"not-matching\""}' -err: From 0744463be769cae9ce979d9dae12656ae1f546c3 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Wed, 3 Jul 2024 10:18:04 -0400 Subject: [PATCH 0071/1670] Update test helpers to improve broker creation --- internal/broker/broker_test.go | 20 ++++++++++++++------ internal/broker/helper_test.go | 33 ++++++++++++++++++++++++--------- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 1e7183922b..e37dd4a6d2 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -165,8 +165,8 @@ func TestGetAuthenticationModes(t *testing.T) { provider, stopServer = testutils.StartMockProvider(address, opts...) t.Cleanup(stopServer) } - b, sessionID, _ := newBrokerForTests(t, t.TempDir(), provider.URL, tc.sessionMode, "") - + b := newBrokerForTests(t, broker.Config{IssuerURL: provider.URL}) + sessionID, _ := newSessionForTests(t, b, "", tc.sessionMode) if tc.sessionID == "-" { sessionID = "" } @@ -257,7 +257,9 @@ func TestSelectAuthenticationMode(t *testing.T) { sessionType = "passwd" } - b, sessionID, _ := newBrokerForTests(t, t.TempDir(), provider.URL, sessionType, "") + b := newBrokerForTests(t, broker.Config{IssuerURL: provider.URL}) + sessionID, _ := newSessionForTests(t, b, "", sessionType) + if tc.tokenExists { err := os.MkdirAll(filepath.Dir(b.TokenPathForSession(sessionID)), 0700) require.NoError(t, err, "Setup: MkdirAll should not have returned an error") @@ -394,7 +396,9 @@ func TestIsAuthenticated(t *testing.T) { defer cleanup() provider = p } - b, sessionID, key := newBrokerForTests(t, cacheDir, provider.URL, tc.sessionMode, tc.username) + + b := newBrokerForTests(t, broker.Config{CachePath: cacheDir, IssuerURL: provider.URL}) + sessionID, key := newSessionForTests(t, b, tc.username, tc.sessionMode) if tc.preexistentToken != "" { tok := generateCachedInfo(t, tc.preexistentToken, provider.URL) @@ -629,7 +633,9 @@ func TestCancelIsAuthenticated(t *testing.T) { provider, cleanup := testutils.StartMockProvider("", testutils.WithHandler("/token", testutils.HangingHandler(ctx))) t.Cleanup(cleanup) - b, sessionID, _ := newBrokerForTests(t, t.TempDir(), provider.URL, "auth", "") + b := newBrokerForTests(t, broker.Config{IssuerURL: provider.URL}) + sessionID, _ := newSessionForTests(t, b, "", "") + updateAuthModes(t, b, sessionID, "device_auth") stopped := make(chan struct{}) @@ -650,7 +656,9 @@ func TestCancelIsAuthenticated(t *testing.T) { func TestEndSession(t *testing.T) { t.Parallel() - b, sessionID, _ := newBrokerForTests(t, t.TempDir(), defaultProvider.URL, "auth", "") + b := newBrokerForTests(t, broker.Config{IssuerURL: defaultProvider.URL}) + + sessionID, _ := newSessionForTests(t, b, "", "") // Try to end a session that does not exist err := b.EndSession("nonexistent") diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index bace85e4b2..5e9f4737c9 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -17,15 +17,19 @@ import ( "golang.org/x/oauth2" ) -// newBrokerForTests is a helper function to create a new broker for tests and already starts a session for the -// specified user. -func newBrokerForTests(t *testing.T, cachePath, issuerURL, mode, username string) (b *broker.Broker, id, key string) { +// newBrokerForTests is a helper function to create a new broker for tests with the specified configuration. +// +// Note that the IssuerURL is required in the configuration. +func newBrokerForTests(t *testing.T, cfg broker.Config) (b *broker.Broker) { t.Helper() - cfg := broker.Config{ - IssuerURL: issuerURL, - ClientID: "test-client-id", - CachePath: cachePath, + require.NotEmpty(t, cfg.IssuerURL, "Setup: issuerURL must not be empty") + + if cfg.CachePath == "" { + cfg.CachePath = t.TempDir() + } + if cfg.ClientID == "" { + cfg.ClientID = "test-client-id" } b, err := broker.New( @@ -39,14 +43,25 @@ func newBrokerForTests(t *testing.T, cachePath, issuerURL, mode, username string }), ) require.NoError(t, err, "Setup: New should not have returned an error") + return b +} + +// newSessionForTests is a helper function to easily create a new session for tests. +// If kept empty, username and mode will be assigned default values. +func newSessionForTests(t *testing.T, b *broker.Broker, username, mode string) (id, key string) { + t.Helper() if username == "" { username = "test-user@email.com" } - id, key, err = b.NewSession(username, "some lang", mode) + if mode == "" { + mode = "auth" + } + + id, key, err := b.NewSession(username, "some lang", mode) require.NoError(t, err, "Setup: NewSession should not have returned an error") - return b, id, key + return id, key } func encryptChallenge(t *testing.T, challenge, strKey string) string { From d6116da74ab51844b4b9055408afc634363c537e Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Wed, 3 Jul 2024 10:26:38 -0400 Subject: [PATCH 0072/1670] Add impl. for UserPreCheck Without prechecking, users would never be able to authenticate through SSH without first authenticating locally in the machine, which is not ideal. --- internal/broker/broker.go | 35 ++++++++++++++------- internal/broker/broker_test.go | 47 +++++++++++++++++++++++++++++ internal/dbusservice/consts.go | 2 ++ internal/dbusservice/dbusservice.go | 18 ++++++++--- internal/dbusservice/methods.go | 9 ++++++ snap/hooks/install | 4 +++ 6 files changed, 100 insertions(+), 15 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 8fd9034cd6..a76374f41e 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -30,17 +30,19 @@ const maxAuthAttempts = 3 // Config is the configuration for the broker. type Config struct { - IssuerURL string - ClientID string - CachePath string - HomeBaseDir string + IssuerURL string + ClientID string + CachePath string + HomeBaseDir string + AllowedSSHSuffixes []string } // Broker is the real implementation of the broker to track sessions and process oidc calls. type Broker struct { - providerInfo providers.ProviderInfoer - auth authConfig - homeDirPath string + providerInfo providers.ProviderInfoer + auth authConfig + homeDirPath string + allowedSSHSuffixes []string currentSessions map[string]sessionInfo currentSessionsMu sync.RWMutex @@ -147,10 +149,11 @@ func New(cfg Config, args ...Option) (b *Broker, err error) { } return &Broker{ - providerInfo: opts.providerInfo, - auth: authCfg, - homeDirPath: homeDirPath, - privateKey: privateKey, + providerInfo: opts.providerInfo, + auth: authCfg, + homeDirPath: homeDirPath, + allowedSSHSuffixes: cfg.AllowedSSHSuffixes, + privateKey: privateKey, currentSessions: make(map[string]sessionInfo), currentSessionsMu: sync.RWMutex{}, @@ -540,6 +543,16 @@ func (b *Broker) CancelIsAuthenticated(sessionID string) { } } +// UserPreCheck checks if the user is valid and can be allowed to authenticate. +func (b *Broker) UserPreCheck(username string) error { + for _, suffix := range b.allowedSSHSuffixes { + if strings.HasSuffix(username, suffix) { + return nil + } + } + return errors.New("username does not match the allowed suffixes") +} + // getSession returns the session information for the specified session ID or an error if the session is not active. func (b *Broker) getSession(sessionID string) (sessionInfo, error) { b.currentSessionsMu.RLock() diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index e37dd4a6d2..acb26c05d3 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -669,6 +669,53 @@ func TestEndSession(t *testing.T) { require.NoError(t, err, "EndSession should not have returned an error when ending an existent session") } +func TestUserPreCheck(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + username string + allowedSuffixes []string + + wantErr bool + }{ + "Successfully allow username with matching allowed suffix": { + username: "user@allowed", + allowedSuffixes: []string{"@allowed"}}, + "Successfully allow username that matches at least one allowed suffix": { + username: "user@allowed", + allowedSuffixes: []string{"@other", "@something", "@allowed"}, + }, + + "Error when username does not match allowed suffix": { + username: "user@notallowed", + allowedSuffixes: []string{"@allowed"}, + wantErr: true, + }, + "Error when username does not match any of the allowed suffixes": { + username: "user@notallowed", + allowedSuffixes: []string{"@other", "@something", "@allowed"}, + wantErr: true, + }, + "Error when no allowed suffixes are provided": { + username: "user@allowed", + wantErr: true, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + b := newBrokerForTests(t, broker.Config{IssuerURL: defaultProvider.URL, AllowedSSHSuffixes: tc.allowedSuffixes}) + + err := b.UserPreCheck(tc.username) + if tc.wantErr { + require.Error(t, err, "UserPreCheck should have returned an error") + return + } + require.NoError(t, err, "UserPreCheck should not have returned an error") + }) + } +} + func TestMain(m *testing.M) { testutils.InstallUpdateFlag() flag.Parse() diff --git a/internal/dbusservice/consts.go b/internal/dbusservice/consts.go index 845b657ec0..101dbd3fb4 100644 --- a/internal/dbusservice/consts.go +++ b/internal/dbusservice/consts.go @@ -17,4 +17,6 @@ const ( clientIDKey = "client_id" // homeDirKey is the key in the config file for the home directory prefix. homeDirKey = "home_base_dir" + // SSHSuffixKey is the key in the config file for the SSH allowed suffixes. + sshSuffixesKey = "ssh_allowed_suffixes" ) diff --git a/internal/dbusservice/dbusservice.go b/internal/dbusservice/dbusservice.go index cae9f1810c..52d013a568 100644 --- a/internal/dbusservice/dbusservice.go +++ b/internal/dbusservice/dbusservice.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "strings" "github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5/introspect" @@ -44,6 +45,9 @@ const intro = ` + + + ` + introspect.IntrospectDataString + ` ` // Service is the handler exposing our broker methods on the system bus. @@ -72,11 +76,17 @@ func New(_ context.Context, cfgPath, cachePath string) (s *Service, err error) { return nil, errors.New("missing required object path for dbus service") } + var allowedSSHSuffixes []string + if cfg[oidcSection][sshSuffixesKey] != "" { + allowedSSHSuffixes = strings.Split(cfg[oidcSection][sshSuffixesKey], ",") + } + bCfg := broker.Config{ - IssuerURL: cfg[oidcSection][issuerKey], - ClientID: cfg[oidcSection][clientIDKey], - HomeBaseDir: cfg[oidcSection][homeDirKey], - CachePath: cachePath, + IssuerURL: cfg[oidcSection][issuerKey], + ClientID: cfg[oidcSection][clientIDKey], + HomeBaseDir: cfg[oidcSection][homeDirKey], + AllowedSSHSuffixes: allowedSSHSuffixes, + CachePath: cachePath, } b, err := broker.New(bCfg) if err != nil { diff --git a/internal/dbusservice/methods.go b/internal/dbusservice/methods.go index 584c47ea40..e5d95d51b9 100644 --- a/internal/dbusservice/methods.go +++ b/internal/dbusservice/methods.go @@ -54,3 +54,12 @@ func (s *Service) CancelIsAuthenticated(sessionID string) (dbusErr *dbus.Error) s.broker.CancelIsAuthenticated(sessionID) return nil } + +// UserPreCheck is the method through which the broker and the daemon will communicate once dbusInterface.UserPreCheck is called. +func (s *Service) UserPreCheck(username string) (dbusErr *dbus.Error) { + err := s.broker.UserPreCheck(username) + if err != nil { + return dbus.MakeFailedError(err) + } + return nil +} diff --git a/snap/hooks/install b/snap/hooks/install index c02d66a9c3..2b0a75479b 100644 --- a/snap/hooks/install +++ b/snap/hooks/install @@ -21,4 +21,8 @@ client_id = {client_id} # The user home directory will be created in the format of {home_base_dir}/{username} # home_base_dir = /home +# The username suffixes that are allowed to login via ssh without existing previously in the system. +# The suffixes must be separated by commas. +# ssh_allowed_suffixes = @example.com,@anotherexample.com + EOF From 68cc461bc0eab489a1cfd96c4a38e8893ae3ddb5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 08:56:53 +0000 Subject: [PATCH 0073/1670] deps(go): bump golang.org/x/crypto from 0.24.0 to 0.25.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.24.0 to 0.25.0. - [Commits](https://github.com/golang/crypto/compare/v0.24.0...v0.25.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 0159ecb986..f66d6c5ca8 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/stretchr/testify v1.9.0 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 - golang.org/x/crypto v0.24.0 + golang.org/x/crypto v0.25.0 golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 golang.org/x/oauth2 v0.21.0 gopkg.in/ini.v1 v1.67.0 @@ -62,6 +62,6 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/net v0.26.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.21.0 // indirect + golang.org/x/sys v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect ) diff --git a/go.sum b/go.sum index 9b9ff7ce15..6196402d4e 100644 --- a/go.sum +++ b/go.sum @@ -121,8 +121,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -143,8 +143,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc 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.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 3410119b938c128367d1f2b8d8ccd088906e4650 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Mon, 8 Jul 2024 10:46:23 +0200 Subject: [PATCH 0074/1670] Compiled dbus and object path names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move dbus and object path name at compile-time values rather than reading from a configuration file. That way, we don’t need access to this config file shared with the system (under /etc), and we will able to relax the constraints once we release the corresponding snap. --- internal/consts/authd.go | 8 ++++++++ internal/dbusservice/consts.go | 7 ------- internal/dbusservice/dbusservice.go | 15 ++++----------- 3 files changed, 12 insertions(+), 18 deletions(-) create mode 100644 internal/consts/authd.go diff --git a/internal/consts/authd.go b/internal/consts/authd.go new file mode 100644 index 0000000000..ebb27bf5b8 --- /dev/null +++ b/internal/consts/authd.go @@ -0,0 +1,8 @@ +package consts + +const ( + // DbusName owned by the broker for authd to contact us. + DbusName = "com.ubuntu.authd.Oidc" + // DbusObject main object path for authd to contact us. + DbusObject = "/com/ubuntu/authd/Oidc" +) diff --git a/internal/dbusservice/consts.go b/internal/dbusservice/consts.go index 101dbd3fb4..5d0f634ee3 100644 --- a/internal/dbusservice/consts.go +++ b/internal/dbusservice/consts.go @@ -2,13 +2,6 @@ package dbusservice // Configuration sections and keys. const ( - // authdSection is the section name in the config file for the authentication daemon specific configuration. - authdSection = "authd" - // dbusNameKey is the key in the config file for the dbus name of the authentication daemon. - dbusNameKey = "dbus_name" - // dbusObjectKey is the key in the config file for the dbus object of the authentication daemon. - dbusObjectKey = "dbus_object" - // oidcSection is the section name in the config file for the OIDC specific configuration. oidcSection = "oidc" // issuerKey is the key in the config file for the issuer. diff --git a/internal/dbusservice/dbusservice.go b/internal/dbusservice/dbusservice.go index 52d013a568..69d625cf49 100644 --- a/internal/dbusservice/dbusservice.go +++ b/internal/dbusservice/dbusservice.go @@ -66,16 +66,6 @@ func New(_ context.Context, cfgPath, cachePath string) (s *Service, err error) { return nil, err } - iface := "com.ubuntu.authd.Broker" - name := cfg[authdSection][dbusNameKey] - object := dbus.ObjectPath(cfg[authdSection][dbusObjectKey]) - if name == "" { - return nil, errors.New("missing required name for dbus service") - } - if object == "" { - return nil, errors.New("missing required object path for dbus service") - } - var allowedSSHSuffixes []string if cfg[oidcSection][sshSuffixesKey] != "" { allowedSSHSuffixes = strings.Split(cfg[oidcSection][sshSuffixesKey], ",") @@ -93,6 +83,9 @@ func New(_ context.Context, cfgPath, cachePath string) (s *Service, err error) { return nil, err } + name := consts.DbusName + object := dbus.ObjectPath(consts.DbusObject) + iface := "com.ubuntu.authd.Broker" s = &Service{ name: name, broker: b, @@ -111,7 +104,7 @@ func New(_ context.Context, cfgPath, cachePath string) (s *Service, err error) { return nil, err } - reply, err := conn.RequestName(name, dbus.NameFlagDoNotQueue) + reply, err := conn.RequestName(consts.DbusName, dbus.NameFlagDoNotQueue) if err != nil { s.disconnect() return nil, err From ac7a7ec7d70c6b94410a221f46986b04c46c40e5 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Mon, 8 Jul 2024 10:55:00 +0200 Subject: [PATCH 0075/1670] Ship authd configuration as artefact of the snap package itself This file will be copied on the destination system directory and is not used in the broker itself directly anymore. Consequently, the plug is now useless. --- conf/authd.conf | 7 +++++++ snap/snapcraft.yaml | 13 ++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) create mode 100644 conf/authd.conf diff --git a/conf/authd.conf b/conf/authd.conf new file mode 100644 index 0000000000..42300355fc --- /dev/null +++ b/conf/authd.conf @@ -0,0 +1,7 @@ +# This section is used by authd to identify and communicate with the broker. +# It should not be edited. +[authd] +name = OIDC +brand_icon = ${snap_base_dir}/current/broker_icon.png +dbus_name = com.ubuntu.authd.Oidc +dbus_object = /com/ubuntu/authd/Oidc diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 2b5d1b9d59..1ee01ec5c7 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -16,7 +16,6 @@ apps: - dbus-authd plugs: - network - - config-file restart-condition: always slots: @@ -25,12 +24,6 @@ slots: bus: system name: com.ubuntu.authd.Oidc -plugs: - config-file: - interface: system-files - read: - - /etc/authd/brokers.d/oidc - parts: broker: source: . @@ -38,3 +31,9 @@ parts: plugin: go build-snaps: - go + config: + source: conf/ + source-type: local + plugin: dump + organize: + "authd.conf": "conf/authd/oidc.conf" From f7bc33c4ea23ac2923d81422adda0169a8ce2919 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Mon, 8 Jul 2024 12:55:46 +0200 Subject: [PATCH 0076/1670] Generate oidc connection info from config To avoid diverging between the oidc connection info from the conf file to the code run into production, create and use a generator for this. --- internal/consts/authd.go | 3 ++ internal/consts/generate.go | 82 +++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 internal/consts/generate.go diff --git a/internal/consts/authd.go b/internal/consts/authd.go index ebb27bf5b8..504beaa653 100644 --- a/internal/consts/authd.go +++ b/internal/consts/authd.go @@ -1,5 +1,8 @@ +// Code generated by internal/consts/generate.go. DO NOT EDIT. + package consts +//go:generate go run generate.go const ( // DbusName owned by the broker for authd to contact us. DbusName = "com.ubuntu.authd.Oidc" diff --git a/internal/consts/generate.go b/internal/consts/generate.go new file mode 100644 index 0000000000..58a788f8ea --- /dev/null +++ b/internal/consts/generate.go @@ -0,0 +1,82 @@ +//go:build generate + +package main + +import ( + "html/template" + "log" + "os" + "path/filepath" + "runtime" + + "gopkg.in/ini.v1" +) + +func main() { + _, currentFilePath, _, ok := runtime.Caller(0) + if !ok { + log.Fatal("Could not get current source file path") + } + currentDir := filepath.Dir(currentFilePath) + + addr, objectPath := getDbusInfo(filepath.Join(currentDir, "../../conf/authd.conf")) + + generateAuthdConst(filepath.Join(currentDir, "authd.go"), addr, objectPath) +} + +func getDbusInfo(path string) (addr string, objectPath string) { + f, err := ini.Load(path) + if err != nil { + log.Fatalf("Error opening ini file: %v", err) + } + + authdSection := f.Section("authd") + addrEntry, err := authdSection.GetKey("dbus_name") + if err != nil { + log.Fatalf("Can't read dbus_name found in ini file: %v", err) + } + objectPathEntry, err := authdSection.GetKey("dbus_object") + if err != nil { + log.Fatalf("Can't read dbus_object found in ini file: %v", err) + } + + return addrEntry.String(), objectPathEntry.String() +} + +func generateAuthdConst(dest string, addr string, objectPath string) { + tmp := dest + ".new" + f, err := os.Create(tmp) + if err != nil { + log.Fatalf("Can't open const file to generate: %v", err) + } + defer f.Close() + + t, err := template.New("authdConst").Parse( + `// Code generated by internal/consts/generate.go. DO NOT EDIT. + +package consts + +//go:generate go run generate.go +const ( + // DbusName owned by the broker for authd to contact us. + DbusName = "{{.Addr}}" + // DbusObject main object path for authd to contact us. + DbusObject = "{{.ObjectPath}}" +) +`) + if err != nil { + log.Fatalf("Invalid template: %v", err) + } + + if err := t.Execute(f, struct { + Addr string + ObjectPath string + }{addr, objectPath}); err != nil { + log.Fatalf("Error executing template: %v", err) + } + f.Close() + + if err := os.Rename(tmp, dest); err != nil { + log.Fatalf("Error renaming file: %v", err) + } +} From 218b64b66830fb8979747490a6df17eff88cd689 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Mon, 8 Jul 2024 12:56:57 +0200 Subject: [PATCH 0077/1670] Run go generate during broker file workflow run We need to rerun go generate everytime we merge the incoming main branch to the destination one so that the consts in atuhd.go are updated. --- .github/workflows/auto-updates.yml | 38 ++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/.github/workflows/auto-updates.yml b/.github/workflows/auto-updates.yml index 3ed53f5cfe..cae40cece8 100644 --- a/.github/workflows/auto-updates.yml +++ b/.github/workflows/auto-updates.yml @@ -47,6 +47,41 @@ jobs: fi echo "has_conflicts=${has_conflicts}" >> $GITHUB_OUTPUT + + - uses: actions/setup-go@v5 + if: ${{ steps.merge.outputs.has_conflicts == 'false' }} + with: + go-version-file: go.mod + - name: Regenerate consts for this broker + if: ${{ steps.merge.outputs.has_conflicts == 'false' }} + run: | + set -eux + # Regenerate the consts for the broker + go generate ./internal/consts/ + - name: Find generated changes + if: ${{ steps.merge.outputs.has_conflicts == 'false' }} + id: check-diff + uses: canonical/desktop-engineering/gh-actions/common/has-diff@main + with: + working-directory: ./internal/consts/ + fail-on-diff: false + - name: Commit generated changes + if: ${{ steps.merge.outputs.has_conflicts == 'false' && steps.check-diff.outputs.diff == 'true' }} + run: | + set -eux + git add ./internal/consts/ + git commit -m "Regenerate consts for ${{ matrix.branch_name }}" + - name: Create Pull Request + if: ${{ steps.merge.outputs.has_conflicts == 'false' }} + uses: peter-evans/create-pull-request@v6 + with: + commit-message: Auto update ${{ matrix.branch_name }} branch + title: Auto update ${{ matrix.branch_name }} branch + body: | + Automated merge from main of ${{ matrix.branch_name }}. + branch: update-${{ matrix.branch_name }} + delete-branch: true + token: ${{ secrets.GITHUB_TOKEN }} - name: Push branch if: ${{ steps.merge.outputs.has_conflicts == 'false' }} run: | @@ -55,6 +90,7 @@ jobs: # Potentially existing PR with conflicts is not valid anymore: we just automerged. git push origin --delete update-${{ matrix.branch_name }} || true + - name: Restore and prepare branch if: ${{ steps.merge.outputs.has_conflicts == 'true' }} run: | @@ -71,6 +107,8 @@ jobs: title: Auto update ${{ matrix.branch_name }} branch body: | Pull request created due to conflicts found when merging main into ${{ matrix.branch_name }}. + + Remember to run go generate ./internal/consts/ and committing those changes before merging this PR. branch: update-${{ matrix.branch_name }} delete-branch: true token: ${{ secrets.GITHUB_TOKEN }} From 1cf9d9d151fa28684f804dcc6dd58bd1aedb8d77 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Mon, 8 Jul 2024 12:59:49 +0200 Subject: [PATCH 0078/1670] Add a new section for the broker configuration files Name this section "users" to have 2 sections in the broker configuration file: one for oidc and another one for the users part. --- internal/dbusservice/consts.go | 3 +++ internal/dbusservice/dbusservice.go | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/internal/dbusservice/consts.go b/internal/dbusservice/consts.go index 5d0f634ee3..fe4bea98f4 100644 --- a/internal/dbusservice/consts.go +++ b/internal/dbusservice/consts.go @@ -8,6 +8,9 @@ const ( issuerKey = "issuer" // clientIDKey is the key in the config file for the client ID. clientIDKey = "client_id" + + // usersSection is the section name in the config file for the users and broker specific configuration. + usersSection = "users" // homeDirKey is the key in the config file for the home directory prefix. homeDirKey = "home_base_dir" // SSHSuffixKey is the key in the config file for the SSH allowed suffixes. diff --git a/internal/dbusservice/dbusservice.go b/internal/dbusservice/dbusservice.go index 69d625cf49..306ec121a7 100644 --- a/internal/dbusservice/dbusservice.go +++ b/internal/dbusservice/dbusservice.go @@ -3,13 +3,13 @@ package dbusservice import ( "context" - "errors" "fmt" "strings" "github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5/introspect" "github.com/ubuntu/authd-oidc-brokers/internal/broker" + "github.com/ubuntu/authd-oidc-brokers/internal/consts" "gopkg.in/ini.v1" ) @@ -67,14 +67,14 @@ func New(_ context.Context, cfgPath, cachePath string) (s *Service, err error) { } var allowedSSHSuffixes []string - if cfg[oidcSection][sshSuffixesKey] != "" { - allowedSSHSuffixes = strings.Split(cfg[oidcSection][sshSuffixesKey], ",") + if cfg[usersSection][sshSuffixesKey] != "" { + allowedSSHSuffixes = strings.Split(cfg[usersSection][sshSuffixesKey], ",") } bCfg := broker.Config{ IssuerURL: cfg[oidcSection][issuerKey], ClientID: cfg[oidcSection][clientIDKey], - HomeBaseDir: cfg[oidcSection][homeDirKey], + HomeBaseDir: cfg[usersSection][homeDirKey], AllowedSSHSuffixes: allowedSSHSuffixes, CachePath: cachePath, } From 5b284b21918fcb8e362e098718d0f8b6b8b4389d Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Mon, 8 Jul 2024 13:01:29 +0200 Subject: [PATCH 0079/1670] Ship the configuration file as brokers.conf in SNAP_DATA We ship the broker configuration file now under current/ to have it per version. The hook is copying from the original file to this one if it does not exists, then the sysadmin can modify the broker configuration. --- cmd/authd-oidc/daemon/daemon.go | 5 +++-- cmd/authd-oidc/daemon/daemon_test.go | 2 +- conf/broker.conf | 13 +++++++++++++ internal/consts/consts.go | 3 --- snap/hooks/install | 26 +------------------------- snap/snapcraft.yaml | 1 + 6 files changed, 19 insertions(+), 31 deletions(-) create mode 100644 conf/broker.conf diff --git a/cmd/authd-oidc/daemon/daemon.go b/cmd/authd-oidc/daemon/daemon.go index 339f842bd7..27b9a5ff35 100644 --- a/cmd/authd-oidc/daemon/daemon.go +++ b/cmd/authd-oidc/daemon/daemon.go @@ -11,7 +11,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/ubuntu/authd-oidc-brokers/internal/consts" "github.com/ubuntu/authd-oidc-brokers/internal/daemon" "github.com/ubuntu/authd-oidc-brokers/internal/dbusservice" ) @@ -54,12 +53,14 @@ func New(name string) *App { // Set config defaults systemCache := filepath.Join("/var", "lib", name) + configDir := "." if snapData := os.Getenv("SNAP_DATA"); snapData != "" { systemCache = filepath.Join(snapData, "cache") + configDir = snapData } a.config = daemonConfig{ Paths: systemPaths{ - BrokerConf: filepath.Join(consts.DefaultBrokersConfPath, name), + BrokerConf: filepath.Join(configDir, "broker.conf"), Cache: systemCache, }, } diff --git a/cmd/authd-oidc/daemon/daemon_test.go b/cmd/authd-oidc/daemon/daemon_test.go index c30184441a..2421cd617a 100644 --- a/cmd/authd-oidc/daemon/daemon_test.go +++ b/cmd/authd-oidc/daemon/daemon_test.go @@ -296,7 +296,7 @@ func TestNoConfigSetDefaults(t *testing.T) { require.NoError(t, err, "Run should not return an error") require.Equal(t, 0, a.Config().Verbosity, "Default Verbosity") - require.Equal(t, filepath.Join(consts.DefaultBrokersConfPath, t.Name()), a.Config().Paths.BrokerConf, "Default broker configuration path") + require.Equal(t, filepath.Join(tmpDir, "broker.conf"), a.Config().Paths.BrokerConf, "Default broker configuration path") require.Equal(t, filepath.Join(tmpDir, "cache"), a.Config().Paths.Cache, "Default cache directory") } diff --git a/conf/broker.conf b/conf/broker.conf new file mode 100644 index 0000000000..9f7e7e1852 --- /dev/null +++ b/conf/broker.conf @@ -0,0 +1,13 @@ +[oidc] +issuer = https://{issuer_url} +client_id = {client_id} + +[users] +# The directory where the home directory will be created for new users. +# Existing users will keep their current directory. +# The user home directory will be created in the format of {home_base_dir}/{username} +# home_base_dir = /home + +# The username suffixes that are allowed to login via ssh without existing previously in the system. +# The suffixes must be separated by commas. +# ssh_allowed_suffixes = @example.com,@anotherexample.com diff --git a/internal/consts/consts.go b/internal/consts/consts.go index 9c63dcdfcd..d1d7f56b9b 100644 --- a/internal/consts/consts.go +++ b/internal/consts/consts.go @@ -14,7 +14,4 @@ const ( // DefaultLevelLog is the default logging level selected without any option. DefaultLevelLog = slog.LevelWarn - - // DefaultBrokersConfPath is the default configuration directory for the brokers. - DefaultBrokersConfPath = "/etc/authd/brokers.d/" ) diff --git a/snap/hooks/install b/snap/hooks/install index 2b0a75479b..077b3cbdac 100644 --- a/snap/hooks/install +++ b/snap/hooks/install @@ -1,28 +1,4 @@ #!/bin/sh set -eu -snap_base_dir=$(dirname ${SNAP}) - -cat < ${SNAP_COMMON}/oidc -[authd] -# This section is used by authd to identify and communicate with the broker. -# It should not be edited. -name = OIDC -brand_icon = ${snap_base_dir}/current/broker_icon.png -dbus_name = com.ubuntu.authd.Oidc -dbus_object = /com/ubuntu/authd/Oidc - -[oidc] -issuer = https://{issuer_url} -client_id = {client_id} - -# The directory where the home directory will be created for new users. -# Existing users will keep their current directory. -# The user home directory will be created in the format of {home_base_dir}/{username} -# home_base_dir = /home - -# The username suffixes that are allowed to login via ssh without existing previously in the system. -# The suffixes must be separated by commas. -# ssh_allowed_suffixes = @example.com,@anotherexample.com - -EOF +cp --update=none ${SNAP}/conf/broker.conf.orig ${SNAP_DATA}/broker.conf diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 1ee01ec5c7..860c7d9333 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -37,3 +37,4 @@ parts: plugin: dump organize: "authd.conf": "conf/authd/oidc.conf" + "broker.conf": "conf/broker.conf.orig" From bf2dcc7c3013addb745f08173392cc452411c3ea Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Mon, 8 Jul 2024 16:10:36 +0200 Subject: [PATCH 0080/1670] Change snap brand icon path to possible location MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As can’t change that at build-time for now, use the path that may be used once we display the broker icon itself. --- conf/authd.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/authd.conf b/conf/authd.conf index 42300355fc..4475af5569 100644 --- a/conf/authd.conf +++ b/conf/authd.conf @@ -2,6 +2,6 @@ # It should not be edited. [authd] name = OIDC -brand_icon = ${snap_base_dir}/current/broker_icon.png +brand_icon = /snap/authd-oidc/current/broker_icon.png dbus_name = com.ubuntu.authd.Oidc dbus_object = /com/ubuntu/authd/Oidc From 230b74d88732fb6900903dab7e8936418600cc08 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Wed, 10 Jul 2024 16:07:07 +0200 Subject: [PATCH 0081/1670] Use usual markups for templated values --- conf/broker.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conf/broker.conf b/conf/broker.conf index 9f7e7e1852..0c60684123 100644 --- a/conf/broker.conf +++ b/conf/broker.conf @@ -1,6 +1,6 @@ [oidc] -issuer = https://{issuer_url} -client_id = {client_id} +issuer = https:// +client_id = [users] # The directory where the home directory will be created for new users. From 420e833131b4075e29569c52fe04ae4c47829438 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Tue, 16 Jul 2024 11:34:31 +0200 Subject: [PATCH 0082/1670] Remove TODOs from README Link mostly to authd documentation --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ea0895c02f..371cb7f1d3 100644 --- a/README.md +++ b/README.md @@ -20,17 +20,15 @@ [![User Documentation][user-documentation-image]][user-documentation-url] [![Go Report Card][goreport-image]][goreport-url] -This is the code repository for Authd OpenID Connect (OIDC) brokers. It is used in conjunction with Ubuntu authentication daemon authd. +This is the code repository for Authd OpenID Connect (OIDC) brokers. It is used in conjunction with Ubuntu authentication daemon [authd](https://github.com/ubuntu/authd). -This project contains specific code for different OpenID Connect providers. We build different binaries based on build tags. +This project contains specific code for different OpenID Connect providers. We build one binary for each and snap them based on build tags to integrate with the Ubuntu authentication daemon. -TODO: More general description about the project. - -For general details, including [installation](TODO link to installation instruction) and [Getting started](TODO link to getting started instructions) guides, head over to our [PROJECT_TODO documentation](link to project documentation). +For general details, check the authd [getting started guide](https://github.com/ubuntu/authd/wiki/01---Get-started-with-authd). The same documentation reference hosts [installation](https://github.com/ubuntu/authd/wiki/02---Installation) and [configuration](https://github.com/ubuntu/authd/wiki/03---Configuration) instructions. ## Troubleshooting -TODO: Add details on how to debug this project, where to increase verbosity, how to find logs, how to run in debug mode. +More details on troubleshooting one of the OIDC brokers is available on the [authd documentation](https://github.com/ubuntu/authd/wiki/05--Troubleshooting). ## Get involved From f715341a77983f64a0cd58993af03ba63a90df89 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Tue, 16 Jul 2024 11:34:48 +0200 Subject: [PATCH 0083/1670] Update report templates for bugs and features Remove TODOs in them too. --- .github/ISSUE_TEMPLATE/bug_report.yml | 41 ++++++++++++++-------- .github/ISSUE_TEMPLATE/feature_request.yml | 41 ++++++++++++++-------- 2 files changed, 54 insertions(+), 28 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 9922a93518..b85fd8abd8 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -51,39 +51,52 @@ body: required: true - type: textarea attributes: - label: "Ubuntu users: System information and logs" - description: > - Ubuntu users can run `ubuntu-bug authd-oidc --save=/tmp/report.txt` - and drag the file below. - - It will contain useful information pertaining to the system and the packages installed. - - type: textarea - attributes: - label: "Non Ubuntu users: System information and logs" + label: "System information and logs" description: | - For users of distributions other than Ubuntu, provide details about the environment you experienced the issue in: + Provide details about the environment you experienced the issue in. Change msentraid for the provider you are using: value: | ### Environment - * authd-oidc version: please run //TODO + * broker version: please run `snap info authd-msentraid` + * authd version: please run `/usr/libexec/authd version` * Distribution: (**NAME** in `/etc/os-release`) * Distribution version: (**VERSION_ID** on `/etc/os-release`): ### Log files Please redact/remove sensitive information: + + #### Authd entries: + + ```raw + journalctl -u authd.service + ``` + + #### MS Entra ID broker entries: + ```raw - authd-oidc logs can be found in //TODO + journalctl -u snap.authd-msentraid.authd-msentraid.service ``` ### Application settings + Please redact/remove sensitive information: + + #### Broker configuration: + ```raw - You can get the configuration file from //TODO + cat /var/snap/authd-msentraid/current/broker.conf ``` + + #### Broker authd configuration: + + ```raw + cat /etc/authd/brokers.d/msentraid.conf + ``` + - type: textarea attributes: label: Relevant information description: > - Please look at our [Troubleshooting guide](../#troubleshooting) and provide logs for the authd-oidc package. + Please look at our [Troubleshooting guide](https://github.com/ubuntu/authd/wiki/05--Troubleshooting) and provide logs for both authd and the oidc provider package. placeholder: Remember to redact any sensitive information from them. - type: checkboxes attributes: diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 97739d69f7..974358c501 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -47,39 +47,52 @@ body: place. - type: textarea attributes: - label: "Ubuntu users: System information and logs" - description: > - Ubuntu users can run `ubuntu-bug authd-oidc --save=/tmp/report.txt` - and drag the file below. - - It will contain useful information pertaining to the system and the packages installed. - - type: textarea - attributes: - label: "Non Ubuntu users: System information and logs" + label: "System information and logs" description: | - For users of distributions other than Ubuntu, provide details about the environment you experienced the issue in: + Provide details about the environment you experienced the issue in. Change msentraid for the provider you are using: value: | ### Environment - * authd-oidc version: please run //TODO + * broker version: please run `snap info authd-msentraid` + * authd version: please run `/usr/libexec/authd version` * Distribution: (**NAME** in `/etc/os-release`) * Distribution version: (**VERSION_ID** on `/etc/os-release`): ### Log files Please redact/remove sensitive information: + + #### Authd entries: + + ```raw + journalctl -u authd.service + ``` + + #### MS Entra ID broker entries: + ```raw - authd-oidc logs can be found in //TODO + journalctl -u snap.authd-msentraid.authd-msentraid.service ``` ### Application settings + Please redact/remove sensitive information: + + #### Broker configuration: + ```raw - You can get the configuration file from //TODO + cat /var/snap/authd-msentraid/current/broker.conf ``` + + #### Broker authd configuration: + + ```raw + cat /etc/authd/brokers.d/msentraid.conf + ``` + - type: textarea attributes: label: Relevant information description: > - Please look at our [Troubleshooting guide](../#troubleshooting) and provide logs for the authd-oidc package. + Please look at our [Troubleshooting guide](https://github.com/ubuntu/authd/wiki/05--Troubleshooting) and provide logs for both authd and the oidc provider package. placeholder: Remember to redact any sensitive information from them. - type: checkboxes attributes: From 9fea4e7272e006d969c15058458a8754778155c0 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Mon, 15 Jul 2024 10:28:49 -0400 Subject: [PATCH 0084/1670] Update UserPreCheck with API changes in authd Refer to [authd#430](https://github.com/ubuntu/authd/pull/430) for the rationale of the changes. --- internal/broker/broker.go | 17 ++++++++++++++--- internal/broker/broker_test.go | 13 +++++++++++-- ...userinfo_with_correct_homedir_after_precheck | 1 + ...ame_that_matches_at_least_one_allowed_suffix | 1 + ..._allow_username_with_matching_allowed_suffix | 1 + internal/dbusservice/methods.go | 8 ++++---- 6 files changed, 32 insertions(+), 9 deletions(-) create mode 100644 internal/broker/testdata/TestUserPreCheck/golden/return_userinfo_with_correct_homedir_after_precheck create mode 100644 internal/broker/testdata/TestUserPreCheck/golden/successfully_allow_username_that_matches_at_least_one_allowed_suffix create mode 100644 internal/broker/testdata/TestUserPreCheck/golden/successfully_allow_username_with_matching_allowed_suffix diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 6bc367546a..37b0d8adb0 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -550,13 +550,24 @@ func (b *Broker) CancelIsAuthenticated(sessionID string) { } // UserPreCheck checks if the user is valid and can be allowed to authenticate. -func (b *Broker) UserPreCheck(username string) error { +func (b *Broker) UserPreCheck(username string) (string, error) { + found := false for _, suffix := range b.allowedSSHSuffixes { if strings.HasSuffix(username, suffix) { - return nil + found = true + break } } - return errors.New("username does not match the allowed suffixes") + + if !found { + return "", errors.New("username does not match the allowed suffixes") + } + + encoded, err := json.Marshal(b.userInfoFromClaims(claims{Email: username, Name: username}, nil)) + if err != nil { + return "", fmt.Errorf("could not marshal user info: %v", err) + } + return string(encoded), nil } // getSession returns the session information for the specified session ID or an error if the session is not active. diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index cec2ee8de7..e658cc505c 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -676,6 +676,7 @@ func TestUserPreCheck(t *testing.T) { tests := map[string]struct { username string allowedSuffixes []string + homePrefix string wantErr bool }{ @@ -686,6 +687,11 @@ func TestUserPreCheck(t *testing.T) { username: "user@allowed", allowedSuffixes: []string{"@other", "@something", "@allowed"}, }, + "Return userinfo with correct homedir after precheck": { + username: "user@allowed", + allowedSuffixes: []string{"@allowed"}, + homePrefix: "/home/allowed/", + }, "Error when username does not match allowed suffix": { username: "user@notallowed", @@ -705,14 +711,17 @@ func TestUserPreCheck(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { t.Parallel() - b := newBrokerForTests(t, broker.Config{IssuerURL: defaultProvider.URL, AllowedSSHSuffixes: tc.allowedSuffixes}) + b := newBrokerForTests(t, broker.Config{IssuerURL: defaultProvider.URL, AllowedSSHSuffixes: tc.allowedSuffixes, HomeBaseDir: tc.homePrefix}) - err := b.UserPreCheck(tc.username) + got, err := b.UserPreCheck(tc.username) if tc.wantErr { require.Error(t, err, "UserPreCheck should have returned an error") return } require.NoError(t, err, "UserPreCheck should not have returned an error") + + want := testutils.LoadWithUpdateFromGolden(t, got) + require.Equal(t, want, got, "UserPreCheck should have returned the expected value") }) } } diff --git a/internal/broker/testdata/TestUserPreCheck/golden/return_userinfo_with_correct_homedir_after_precheck b/internal/broker/testdata/TestUserPreCheck/golden/return_userinfo_with_correct_homedir_after_precheck new file mode 100644 index 0000000000..fadbc5c469 --- /dev/null +++ b/internal/broker/testdata/TestUserPreCheck/golden/return_userinfo_with_correct_homedir_after_precheck @@ -0,0 +1 @@ +{"name":"user@allowed","uuid":"","dir":"/home/allowed/user@allowed","shell":"/usr/bin/bash","gecos":"user@allowed","groups":null} \ No newline at end of file diff --git a/internal/broker/testdata/TestUserPreCheck/golden/successfully_allow_username_that_matches_at_least_one_allowed_suffix b/internal/broker/testdata/TestUserPreCheck/golden/successfully_allow_username_that_matches_at_least_one_allowed_suffix new file mode 100644 index 0000000000..1c1ddfb545 --- /dev/null +++ b/internal/broker/testdata/TestUserPreCheck/golden/successfully_allow_username_that_matches_at_least_one_allowed_suffix @@ -0,0 +1 @@ +{"name":"user@allowed","uuid":"","dir":"/home/user@allowed","shell":"/usr/bin/bash","gecos":"user@allowed","groups":null} \ No newline at end of file diff --git a/internal/broker/testdata/TestUserPreCheck/golden/successfully_allow_username_with_matching_allowed_suffix b/internal/broker/testdata/TestUserPreCheck/golden/successfully_allow_username_with_matching_allowed_suffix new file mode 100644 index 0000000000..1c1ddfb545 --- /dev/null +++ b/internal/broker/testdata/TestUserPreCheck/golden/successfully_allow_username_with_matching_allowed_suffix @@ -0,0 +1 @@ +{"name":"user@allowed","uuid":"","dir":"/home/user@allowed","shell":"/usr/bin/bash","gecos":"user@allowed","groups":null} \ No newline at end of file diff --git a/internal/dbusservice/methods.go b/internal/dbusservice/methods.go index e5d95d51b9..06744a7d08 100644 --- a/internal/dbusservice/methods.go +++ b/internal/dbusservice/methods.go @@ -56,10 +56,10 @@ func (s *Service) CancelIsAuthenticated(sessionID string) (dbusErr *dbus.Error) } // UserPreCheck is the method through which the broker and the daemon will communicate once dbusInterface.UserPreCheck is called. -func (s *Service) UserPreCheck(username string) (dbusErr *dbus.Error) { - err := s.broker.UserPreCheck(username) +func (s *Service) UserPreCheck(username string) (userinfo string, dbusErr *dbus.Error) { + userinfo, err := s.broker.UserPreCheck(username) if err != nil { - return dbus.MakeFailedError(err) + return "", dbus.MakeFailedError(err) } - return nil + return userinfo, nil } From e931318179289c4bf3055471b4485dbe8fb684b1 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Wed, 17 Jul 2024 07:23:33 -0400 Subject: [PATCH 0085/1670] No longer run daemon tests in parallel After the changes introduced in #52, the broker bus is always exported with the same name. This means that we can't run multiple instances of the daemon as they would fail when competing for the same bus name. Since we can't run multiple daemons in production as well (for the same reason), it's fine to drop the parallel testing and run them sequentially instead. --- cmd/authd-oidc/daemon/daemon_test.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/cmd/authd-oidc/daemon/daemon_test.go b/cmd/authd-oidc/daemon/daemon_test.go index 2421cd617a..96eb24f44a 100644 --- a/cmd/authd-oidc/daemon/daemon_test.go +++ b/cmd/authd-oidc/daemon/daemon_test.go @@ -67,8 +67,6 @@ func TestNoUsageError(t *testing.T) { } func TestUsageError(t *testing.T) { - t.Parallel() - a := daemon.NewForTests(t, nil, mockProvider.URL, "doesnotexist") err := a.Run() @@ -78,8 +76,6 @@ func TestUsageError(t *testing.T) { } func TestCanQuitWhenExecute(t *testing.T) { - t.Parallel() - a, wait := startDaemon(t, nil) defer wait() @@ -87,8 +83,6 @@ func TestCanQuitWhenExecute(t *testing.T) { } func TestCanQuitTwice(t *testing.T) { - t.Parallel() - a, wait := startDaemon(t, nil) a.Quit() @@ -100,8 +94,6 @@ func TestCanQuitTwice(t *testing.T) { func TestAppCanQuitWithoutExecute(t *testing.T) { t.Skipf("This test is skipped because it is flaky. There is no way to guarantee Quit has been called before run.") - t.Parallel() - a := daemon.NewForTests(t, nil, mockProvider.URL) requireGoroutineStarted(t, a.Quit) @@ -112,7 +104,6 @@ func TestAppCanQuitWithoutExecute(t *testing.T) { } func TestAppRunFailsOnComponentsCreationAndQuit(t *testing.T) { - t.Parallel() const ( // Cache errors dirIsFile = iota @@ -130,8 +121,6 @@ func TestAppRunFailsOnComponentsCreationAndQuit(t *testing.T) { } for name, tc := range tests { t.Run(name, func(t *testing.T) { - t.Parallel() - tmpDir := t.TempDir() cachePath := filepath.Join(tmpDir, "cache") @@ -226,15 +215,11 @@ func TestAppCanSigHupWithoutExecute(t *testing.T) { } func TestAppGetRootCmd(t *testing.T) { - t.Parallel() - a := daemon.NewForTests(t, nil, mockProvider.URL) require.NotNil(t, a.RootCmd(), "Returns root command") } func TestConfigLoad(t *testing.T) { - t.Parallel() - tmpDir := t.TempDir() config := daemon.DaemonConfig{ Verbosity: 1, From 21e723ce9242c3754ace4637b748096c70bae3d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Jul 2024 16:05:29 +0000 Subject: [PATCH 0086/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.45.0 to 1.46.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.45.0...v1.46.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index f66d6c5ca8..a6e0221e3c 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/godbus/dbus/v5 v5.1.0 github.com/google/uuid v1.6.0 - github.com/microsoftgraph/msgraph-sdk-go v1.45.0 + github.com/microsoftgraph/msgraph-sdk-go v1.46.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.1.0 github.com/otiai10/copy v1.14.0 github.com/spf13/cobra v1.8.1 @@ -37,7 +37,7 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/microsoft/kiota-abstractions-go v1.6.0 // indirect + github.com/microsoft/kiota-abstractions-go v1.6.1 // indirect github.com/microsoft/kiota-authentication-azure-go v1.0.2 // indirect github.com/microsoft/kiota-http-go v1.3.2 // indirect github.com/microsoft/kiota-serialization-form-go v1.0.0 // indirect @@ -54,7 +54,7 @@ require ( github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/std-uritemplate/std-uritemplate/go v0.0.55 // indirect + github.com/std-uritemplate/std-uritemplate/go v0.0.57 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect diff --git a/go.sum b/go.sum index 6196402d4e..59e51334ec 100644 --- a/go.sum +++ b/go.sum @@ -42,8 +42,8 @@ github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 h1:9ZM/t5 github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069/go.mod h1:AT4NpQrOmyj1L/+hLja6aR0lk81yYYL4ePnj2kp7d6M= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/microsoft/kiota-abstractions-go v1.6.0 h1:qbGBNMU0/o5myKbikCBXJFohVCFrrpx2cO15Rta2WyA= -github.com/microsoft/kiota-abstractions-go v1.6.0/go.mod h1:7YH20ZbRWXGfHSSvdHkdztzgCB9mRdtFx13+hrYIEpo= +github.com/microsoft/kiota-abstractions-go v1.6.1 h1:NXK50S3BwJn9Wj6bO0YFuAig7y2WVgdQ/ie1ktMl2J4= +github.com/microsoft/kiota-abstractions-go v1.6.1/go.mod h1:FI1I2OHg0E7bK5t8DPnw+9C/CHVyLP6XeqDBT+95pTE= github.com/microsoft/kiota-authentication-azure-go v1.0.2 h1:tClGeyFZJ+4Bakf8u0euPM4wqy4ethycdOgx3jyH3pI= github.com/microsoft/kiota-authentication-azure-go v1.0.2/go.mod h1:aTcti0bUJEcq7kBfQG4Sr4ElvRNuaalXcFEu4iEyQ6M= github.com/microsoft/kiota-http-go v1.3.2 h1:HSrbw6MDMmgLaqU1qT3SgdmD/18KPW2usfyUtE017Lw= @@ -56,8 +56,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.0.0 h1:3O5sb5Zj+moLBiJy github.com/microsoft/kiota-serialization-multipart-go v1.0.0/go.mod h1:yauLeBTpANk4L03XD985akNysG24SnRJGaveZf+p4so= github.com/microsoft/kiota-serialization-text-go v1.0.0 h1:XOaRhAXy+g8ZVpcq7x7a0jlETWnWrEum0RhmbYrTFnA= github.com/microsoft/kiota-serialization-text-go v1.0.0/go.mod h1:sM1/C6ecnQ7IquQOGUrUldaO5wj+9+v7G2W3sQ3fy6M= -github.com/microsoftgraph/msgraph-sdk-go v1.45.0 h1:PRE3nsGDlASfoM1h6QNCBXmU34LTiReg5LMBjRKOL3k= -github.com/microsoftgraph/msgraph-sdk-go v1.45.0/go.mod h1:MSMgjuMPKAsIz8XfH5l+e781fkWjUxc1XXhb2eoSdc0= +github.com/microsoftgraph/msgraph-sdk-go v1.46.0 h1:5AT0FZkPFTiLZ9nZym1TLUFlcHFVhAAvqNb9B52Xvn0= +github.com/microsoftgraph/msgraph-sdk-go v1.46.0/go.mod h1:Rf2aiCdmTvY325XfhxnUiRIuIyj66SYdszaovW091S0= github.com/microsoftgraph/msgraph-sdk-go-core v1.1.0 h1:NB7c/n4Knj+TLaLfjsahhSqoUqoN/CtyNB0XIe/nJnM= github.com/microsoftgraph/msgraph-sdk-go-core v1.1.0/go.mod h1:M3w/5IFJ1u/DpwOyjsjNSVEA43y1rLOeX58suyfBhGk= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -92,8 +92,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= -github.com/std-uritemplate/std-uritemplate/go v0.0.55 h1:muSH037g97K7U2f94G9LUuE8tZlJsoSSrPsO9V281WY= -github.com/std-uritemplate/std-uritemplate/go v0.0.55/go.mod h1:rG/bqh/ThY4xE5de7Rap3vaDkYUT76B0GPJ0loYeTTc= +github.com/std-uritemplate/std-uritemplate/go v0.0.57 h1:GHGjptrsmazP4IVDlUprssiEf9ESVkbjx15xQXXzvq4= +github.com/std-uritemplate/std-uritemplate/go v0.0.57/go.mod h1:rG/bqh/ThY4xE5de7Rap3vaDkYUT76B0GPJ0loYeTTc= github.com/stretchr/objx v0.1.0/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= From 98a82f50f8dcc3e0602e9b2415c5937048fa5319 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Jul 2024 16:05:40 +0000 Subject: [PATCH 0087/1670] deps(go): bump github.com/coreos/go-oidc/v3 from 3.10.0 to 3.11.0 Bumps [github.com/coreos/go-oidc/v3](https://github.com/coreos/go-oidc) from 3.10.0 to 3.11.0. - [Release notes](https://github.com/coreos/go-oidc/releases) - [Commits](https://github.com/coreos/go-oidc/compare/v3.10.0...v3.11.0) --- updated-dependencies: - dependency-name: github.com/coreos/go-oidc/v3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index f66d6c5ca8..5abfbfdf15 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.22.5 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 - github.com/coreos/go-oidc/v3 v3.10.0 + github.com/coreos/go-oidc/v3 v3.11.0 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/godbus/dbus/v5 v5.1.0 github.com/google/uuid v1.6.0 @@ -30,7 +30,7 @@ require ( github.com/cjlapao/common-go v0.0.39 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-jose/go-jose/v4 v4.0.1 // indirect + github.com/go-jose/go-jose/v4 v4.0.2 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect @@ -60,7 +60,7 @@ require ( go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.26.0 // indirect + golang.org/x/net v0.27.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect diff --git a/go.sum b/go.sum index 6196402d4e..ca8f6addfc 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 h1:H+U3Gk9zY56G3u872L82bk4 github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0/go.mod h1:mgrmMSgaLp9hmax62XQTd0N4aAqSE5E0DulSpVYK7vc= github.com/cjlapao/common-go v0.0.39 h1:bAAUrj2B9v0kMzbAOhzjSmiyDy+rd56r2sy7oEiQLlA= github.com/cjlapao/common-go v0.0.39/go.mod h1:M3dzazLjTjEtZJbbxoA5ZDiGCiHmpwqW9l4UWaddwOA= -github.com/coreos/go-oidc/v3 v3.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoEaJU= -github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac= +github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI= +github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -17,8 +17,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 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/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U= -github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= +github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk= +github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -129,8 +129,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 014d70157dd947ef9fd9bea35293ad72a0769bf6 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Thu, 18 Jul 2024 11:28:19 +0200 Subject: [PATCH 0088/1670] Try to check if the issue is coming from the graphical or terminal only This will help debugging further. --- .github/ISSUE_TEMPLATE/bug_report.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index b85fd8abd8..d2df7d8110 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -32,8 +32,20 @@ body: label: Describe the issue description: > Provide a clear and concise description of what the issue is, including what you expected to happen. + + Please precise if the option is in the graphical display manager or in the terminal. If it is with the graphical + display manager, try to login with another local user, and open a terminal. Then, try to login with "login" + and see if you can reproduce the issue there too. State so in the description. validations: required: true + - type: checkboxes + attributes: + label: Where does the issue happens + options: + - label: I can reproduce the issue in the graphical display manager + required: false + - label: I can reproduce the issue on a terminal with "login" + required: false - type: textarea attributes: label: Steps to reproduce it From 01ca0d021c49b99c5837ce59751f69008b26fd86 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Thu, 18 Jul 2024 11:28:45 +0200 Subject: [PATCH 0089/1670] Request the version for GNOME Shell --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + .github/ISSUE_TEMPLATE/feature_request.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index d2df7d8110..b3c44f8066 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -70,6 +70,7 @@ body: ### Environment * broker version: please run `snap info authd-msentraid` * authd version: please run `/usr/libexec/authd version` + * gnome shell version: please run `apt policy gnome-shell` * Distribution: (**NAME** in `/etc/os-release`) * Distribution version: (**VERSION_ID** on `/etc/os-release`): diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 974358c501..d070930334 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -54,6 +54,7 @@ body: ### Environment * broker version: please run `snap info authd-msentraid` * authd version: please run `/usr/libexec/authd version` + * gnome shell version: please run `apt policy gnome-shell` * Distribution: (**NAME** in `/etc/os-release`) * Distribution version: (**VERSION_ID** on `/etc/os-release`): From 3976c142ec5f2efce25473bb30b902d9256af9a7 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Thu, 18 Jul 2024 07:48:28 -0400 Subject: [PATCH 0090/1670] Print object if can not parse group object --- go.mod | 4 ++++ go.sum | 10 ++++++++++ .../providers/microsoft_entra_id/microsoft-entra-id.go | 3 +++ 3 files changed, 17 insertions(+) diff --git a/go.mod b/go.mod index e408987b40..9ae8a633cf 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/godbus/dbus/v5 v5.1.0 github.com/google/uuid v1.6.0 + github.com/k0kubun/pp v3.0.1+incompatible github.com/microsoftgraph/msgraph-sdk-go v1.46.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.1.0 github.com/otiai10/copy v1.14.0 @@ -35,8 +36,11 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/microsoft/kiota-abstractions-go v1.6.1 // indirect github.com/microsoft/kiota-authentication-azure-go v1.0.2 // indirect github.com/microsoft/kiota-http-go v1.3.2 // indirect diff --git a/go.sum b/go.sum index 85ca54d860..17525da94c 100644 --- a/go.sum +++ b/go.sum @@ -34,6 +34,10 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/k0kubun/pp v3.0.1+incompatible h1:3tqvf7QgUnZ5tXO6pNAZlrvHgl6DvifjDrd9g2S9Z40= +github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -42,6 +46,11 @@ github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 h1:9ZM/t5 github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069/go.mod h1:AT4NpQrOmyj1L/+hLja6aR0lk81yYYL4ePnj2kp7d6M= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/microsoft/kiota-abstractions-go v1.6.1 h1:NXK50S3BwJn9Wj6bO0YFuAig7y2WVgdQ/ie1ktMl2J4= github.com/microsoft/kiota-abstractions-go v1.6.1/go.mod h1:FI1I2OHg0E7bK5t8DPnw+9C/CHVyLP6XeqDBT+95pTE= github.com/microsoft/kiota-authentication-azure-go v1.0.2 h1:tClGeyFZJ+4Bakf8u0euPM4wqy4ethycdOgx3jyH3pI= @@ -143,6 +152,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc 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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/internal/providers/microsoft_entra_id/microsoft-entra-id.go b/internal/providers/microsoft_entra_id/microsoft-entra-id.go index a2a211bf6a..acc8775d56 100644 --- a/internal/providers/microsoft_entra_id/microsoft-entra-id.go +++ b/internal/providers/microsoft_entra_id/microsoft-entra-id.go @@ -7,11 +7,13 @@ import ( "context" "errors" "fmt" + "log/slog" "strings" "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/coreos/go-oidc/v3/oidc" + "github.com/k0kubun/pp" msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go" msauth "github.com/microsoftgraph/msgraph-sdk-go-core/authentication" "github.com/ubuntu/authd-oidc-brokers/internal/providers/group" @@ -63,6 +65,7 @@ func (p MSEntraIDProvider) GetGroups(token *oauth2.Token) ([]group.Info, error) } name, ok = v.(*string) if !ok || name == nil { + slog.Warn(pp.Sprintf("Invalid group found: %v", obj)) return nil, errors.New("could not parse group name") } From 2350f82192a226d51c7a5cdf415749d8feacb86d Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Thu, 18 Jul 2024 11:31:41 -0400 Subject: [PATCH 0091/1670] Ignore non-group objects from graphsdk --- go.mod | 4 --- go.sum | 10 ------ .../microsoft_entra_id/microsoft-entra-id.go | 31 +++++++++++++------ 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/go.mod b/go.mod index 9ae8a633cf..e408987b40 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ require ( github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/godbus/dbus/v5 v5.1.0 github.com/google/uuid v1.6.0 - github.com/k0kubun/pp v3.0.1+incompatible github.com/microsoftgraph/msgraph-sdk-go v1.46.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.1.0 github.com/otiai10/copy v1.14.0 @@ -36,11 +35,8 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect github.com/microsoft/kiota-abstractions-go v1.6.1 // indirect github.com/microsoft/kiota-authentication-azure-go v1.0.2 // indirect github.com/microsoft/kiota-http-go v1.3.2 // indirect diff --git a/go.sum b/go.sum index 17525da94c..85ca54d860 100644 --- a/go.sum +++ b/go.sum @@ -34,10 +34,6 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= -github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= -github.com/k0kubun/pp v3.0.1+incompatible h1:3tqvf7QgUnZ5tXO6pNAZlrvHgl6DvifjDrd9g2S9Z40= -github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -46,11 +42,6 @@ github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 h1:9ZM/t5 github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069/go.mod h1:AT4NpQrOmyj1L/+hLja6aR0lk81yYYL4ePnj2kp7d6M= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/microsoft/kiota-abstractions-go v1.6.1 h1:NXK50S3BwJn9Wj6bO0YFuAig7y2WVgdQ/ie1ktMl2J4= github.com/microsoft/kiota-abstractions-go v1.6.1/go.mod h1:FI1I2OHg0E7bK5t8DPnw+9C/CHVyLP6XeqDBT+95pTE= github.com/microsoft/kiota-authentication-azure-go v1.0.2 h1:tClGeyFZJ+4Bakf8u0euPM4wqy4ethycdOgx3jyH3pI= @@ -152,7 +143,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc 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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/internal/providers/microsoft_entra_id/microsoft-entra-id.go b/internal/providers/microsoft_entra_id/microsoft-entra-id.go index acc8775d56..249ec0aaee 100644 --- a/internal/providers/microsoft_entra_id/microsoft-entra-id.go +++ b/internal/providers/microsoft_entra_id/microsoft-entra-id.go @@ -13,9 +13,9 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/coreos/go-oidc/v3/oidc" - "github.com/k0kubun/pp" msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go" msauth "github.com/microsoftgraph/msgraph-sdk-go-core/authentication" + "github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/ubuntu/authd-oidc-brokers/internal/providers/group" "golang.org/x/oauth2" ) @@ -55,20 +55,33 @@ func (p MSEntraIDProvider) GetGroups(token *oauth2.Token) ([]group.Info, error) return nil, err } - var ok bool - var name, id *string var groups []group.Info for _, obj := range m.GetValue() { - v, err := obj.GetBackingStore().Get("displayName") + msGroup, ok := obj.(*models.Group) + if !ok { + unknown := "Unknown" + id, oType := obj.GetId(), obj.GetOdataType() + if id == nil { + id = &unknown + } + if oType == nil { + oType = &unknown + } + slog.Debug(fmt.Sprintf( + "Found non-group object with ID: %q of type: %q in graphsdk response. Ignoring it", + *id, *oType, + )) + continue + } + + v, err := msGroup.GetBackingStore().Get("displayName") if err != nil { return nil, err } - name, ok = v.(*string) + name, ok := v.(*string) if !ok || name == nil { - slog.Warn(pp.Sprintf("Invalid group found: %v", obj)) return nil, errors.New("could not parse group name") } - groupName := strings.ToLower(*name) // Local group @@ -78,11 +91,11 @@ func (p MSEntraIDProvider) GetGroups(token *oauth2.Token) ([]group.Info, error) continue } - v, err = obj.GetBackingStore().Get("id") + v, err = msGroup.GetBackingStore().Get("id") if err != nil { return nil, err } - id, ok = v.(*string) + id, ok := v.(*string) if !ok || id == nil { return nil, errors.New("could not parse group id") } From 3bfcb338c3237e619e7c1f3790fae2dca83eb892 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Fri, 19 Jul 2024 08:54:23 +0200 Subject: [PATCH 0092/1670] Fix autoupdate brokers specific branch when there is no conflict We were not pushing the local branch but a local branch with the same name. --- .github/workflows/auto-updates.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto-updates.yml b/.github/workflows/auto-updates.yml index cae40cece8..7ff30edf96 100644 --- a/.github/workflows/auto-updates.yml +++ b/.github/workflows/auto-updates.yml @@ -86,7 +86,7 @@ jobs: if: ${{ steps.merge.outputs.has_conflicts == 'false' }} run: | set -eux - git push origin ${{ matrix.branch_name }} + git push origin :${{ matrix.branch_name }} # Potentially existing PR with conflicts is not valid anymore: we just automerged. git push origin --delete update-${{ matrix.branch_name }} || true From ae1784d526b0966cd9d76623e9a8456509cc4071 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Fri, 19 Jul 2024 09:15:56 +0200 Subject: [PATCH 0093/1670] Really fix automerge We need to specify the original branch and not only the destination, otherwise the branch is removed. We already mark the original branch to be autodeleted on the PR, no need to force it too. --- .github/workflows/auto-updates.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/auto-updates.yml b/.github/workflows/auto-updates.yml index 7ff30edf96..5359f89232 100644 --- a/.github/workflows/auto-updates.yml +++ b/.github/workflows/auto-updates.yml @@ -86,10 +86,7 @@ jobs: if: ${{ steps.merge.outputs.has_conflicts == 'false' }} run: | set -eux - git push origin :${{ matrix.branch_name }} - - # Potentially existing PR with conflicts is not valid anymore: we just automerged. - git push origin --delete update-${{ matrix.branch_name }} || true + git push origin update-${{ matrix.branch_name }}:${{ matrix.branch_name }} - name: Restore and prepare branch if: ${{ steps.merge.outputs.has_conflicts == 'true' }} From f011f6471fe0fe79a7034cc2ac0523c5231a225f Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Fri, 19 Jul 2024 09:16:56 +0200 Subject: [PATCH 0094/1670] Allow manual workflow triggers We will certainly need it more than once. --- .github/workflows/auto-updates.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/auto-updates.yml b/.github/workflows/auto-updates.yml index 5359f89232..5c34c0cfe1 100644 --- a/.github/workflows/auto-updates.yml +++ b/.github/workflows/auto-updates.yml @@ -3,6 +3,7 @@ on: push: branches: - main + workflow_dispatch: concurrency: auto-update-broker-variants permissions: From e7d31fa6d5da867d55ba9eb5b27b21962681277b Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Fri, 19 Jul 2024 11:08:01 -0400 Subject: [PATCH 0095/1670] Log group information on parsing error --- .../providers/microsoft_entra_id/microsoft-entra-id.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/internal/providers/microsoft_entra_id/microsoft-entra-id.go b/internal/providers/microsoft_entra_id/microsoft-entra-id.go index 249ec0aaee..9ba870aa0f 100644 --- a/internal/providers/microsoft_entra_id/microsoft-entra-id.go +++ b/internal/providers/microsoft_entra_id/microsoft-entra-id.go @@ -57,9 +57,9 @@ func (p MSEntraIDProvider) GetGroups(token *oauth2.Token) ([]group.Info, error) var groups []group.Info for _, obj := range m.GetValue() { + unknown := "Unknown" msGroup, ok := obj.(*models.Group) if !ok { - unknown := "Unknown" id, oType := obj.GetId(), obj.GetOdataType() if id == nil { id = &unknown @@ -80,6 +80,11 @@ func (p MSEntraIDProvider) GetGroups(token *oauth2.Token) ([]group.Info, error) } name, ok := v.(*string) if !ok || name == nil { + id := msGroup.GetId() + if id == nil { + id = &unknown + } + slog.Warn(fmt.Sprint("Invalid group object (ID: %s) found: %v", id, msGroup)) return nil, errors.New("could not parse group name") } groupName := strings.ToLower(*name) @@ -97,6 +102,7 @@ func (p MSEntraIDProvider) GetGroups(token *oauth2.Token) ([]group.Info, error) } id, ok := v.(*string) if !ok || id == nil { + slog.Warn(fmt.Sprint("Could not get ID for group %q: %v", groupName, msGroup)) return nil, errors.New("could not parse group id") } From c47f5ed641fb4c026186ab24dd28736dcf935f55 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jul 2024 08:23:12 +0000 Subject: [PATCH 0096/1670] deps(go): bump github.com/Azure/azure-sdk-for-go/sdk/azcore Bumps [github.com/Azure/azure-sdk-for-go/sdk/azcore](https://github.com/Azure/azure-sdk-for-go) from 1.12.0 to 1.13.0. - [Release notes](https://github.com/Azure/azure-sdk-for-go/releases) - [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/release.md) - [Commits](https://github.com/Azure/azure-sdk-for-go/compare/sdk/azcore/v1.12.0...sdk/azcore/v1.13.0) --- updated-dependencies: - dependency-name: github.com/Azure/azure-sdk-for-go/sdk/azcore dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index e408987b40..dc9301d997 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.0 toolchain go1.22.5 require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 github.com/coreos/go-oidc/v3 v3.11.0 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/godbus/dbus/v5 v5.1.0 @@ -26,7 +26,7 @@ require ( ) require ( - github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/cjlapao/common-go v0.0.39 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect diff --git a/go.sum b/go.sum index 85ca54d860..b4ebd0ba49 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 h1:1nGuui+4POelzDwI7RG56yfQJHCnKvwfMoU7VsEp+Zg= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0/go.mod h1:99EvauvlcJ1U06amZiksfYz/3aFGyIhWGHVyiZXtBAI= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 h1:H+U3Gk9zY56G3u872L82bk4thcsy2Gghb9ExT4Zvm1o= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0/go.mod h1:mgrmMSgaLp9hmax62XQTd0N4aAqSE5E0DulSpVYK7vc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 h1:GJHeeA2N7xrG3q30L2UXDyuWRzDM900/65j70wcM4Ww= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= github.com/cjlapao/common-go v0.0.39 h1:bAAUrj2B9v0kMzbAOhzjSmiyDy+rd56r2sy7oEiQLlA= github.com/cjlapao/common-go v0.0.39/go.mod h1:M3dzazLjTjEtZJbbxoA5ZDiGCiHmpwqW9l4UWaddwOA= github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI= From 6b11d31168fc83f5115b392c950fb2e05345f74d Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Mon, 22 Jul 2024 11:44:52 +0200 Subject: [PATCH 0097/1670] Fix invalid logging for unexpected group * Fix Print -> Printf * Ensure we print the value of the pointer and not the pointer itself * Use pretty (which has less dependencies than pp, due to no color support) to print the actual group content for debugging. --- internal/providers/microsoft_entra_id/microsoft-entra-id.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/providers/microsoft_entra_id/microsoft-entra-id.go b/internal/providers/microsoft_entra_id/microsoft-entra-id.go index 9ba870aa0f..1d30cf4cbe 100644 --- a/internal/providers/microsoft_entra_id/microsoft-entra-id.go +++ b/internal/providers/microsoft_entra_id/microsoft-entra-id.go @@ -13,6 +13,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/coreos/go-oidc/v3/oidc" + "github.com/kr/pretty" msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go" msauth "github.com/microsoftgraph/msgraph-sdk-go-core/authentication" "github.com/microsoftgraph/msgraph-sdk-go/models" @@ -84,7 +85,7 @@ func (p MSEntraIDProvider) GetGroups(token *oauth2.Token) ([]group.Info, error) if id == nil { id = &unknown } - slog.Warn(fmt.Sprint("Invalid group object (ID: %s) found: %v", id, msGroup)) + slog.Warn(pretty.Sprintf("Could not get displayName from group object (ID: %s) found: %v", *id, msGroup)) return nil, errors.New("could not parse group name") } groupName := strings.ToLower(*name) @@ -102,7 +103,7 @@ func (p MSEntraIDProvider) GetGroups(token *oauth2.Token) ([]group.Info, error) } id, ok := v.(*string) if !ok || id == nil { - slog.Warn(fmt.Sprint("Could not get ID for group %q: %v", groupName, msGroup)) + slog.Warn(pretty.Sprintf("Could not get ID for group %q: %v", groupName, msGroup)) return nil, errors.New("could not parse group id") } From 3fdde55846b552def89b894d444455dc7aa7901b Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Mon, 22 Jul 2024 11:46:11 +0200 Subject: [PATCH 0098/1670] Add pretty as a depdendency --- go.mod | 3 +++ go.sum | 3 +++ 2 files changed, 6 insertions(+) diff --git a/go.mod b/go.mod index e408987b40..e30d58f709 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/godbus/dbus/v5 v5.1.0 github.com/google/uuid v1.6.0 + github.com/kr/pretty v0.3.1 github.com/microsoftgraph/msgraph-sdk-go v1.46.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.1.0 github.com/otiai10/copy v1.14.0 @@ -35,6 +36,7 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/kr/text v0.2.0 // indirect github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/microsoft/kiota-abstractions-go v1.6.1 // indirect @@ -47,6 +49,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect diff --git a/go.sum b/go.sum index 85ca54d860..4008a0e126 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,7 @@ github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDh github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -68,9 +69,11 @@ github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= From a0c6309842657ca7d28357402cc7260c4bcc118d Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Mon, 22 Jul 2024 14:11:24 +0200 Subject: [PATCH 0099/1670] Only include build-time when you import the package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Let’s trust ourself to import it when needed, that way, we have the linter running when it’s needed. Now, only withmsentraid.go have the build-tag to import it. --- internal/providers/microsoft_entra_id/microsoft-entra-id.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/providers/microsoft_entra_id/microsoft-entra-id.go b/internal/providers/microsoft_entra_id/microsoft-entra-id.go index 1d30cf4cbe..cfe0a85c54 100644 --- a/internal/providers/microsoft_entra_id/microsoft-entra-id.go +++ b/internal/providers/microsoft_entra_id/microsoft-entra-id.go @@ -1,5 +1,3 @@ -//go:build withmsentraid - // Package microsoft_entra_id is the Microsoft Entra ID specific extension. package microsoft_entra_id From 8a061ad20b89192c909f47c5559a3e630a1f8732 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Mon, 22 Jul 2024 14:19:58 +0200 Subject: [PATCH 0100/1670] Rename the package to msentraid The linter is now linting the ex microsoft_entra_id package and is unhappy about the namng. --- .../microsoft-entra-id.go => msentraid/msentraid.go} | 4 ++-- internal/providers/withmsentraid.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename internal/providers/{microsoft_entra_id/microsoft-entra-id.go => msentraid/msentraid.go} (97%) diff --git a/internal/providers/microsoft_entra_id/microsoft-entra-id.go b/internal/providers/msentraid/msentraid.go similarity index 97% rename from internal/providers/microsoft_entra_id/microsoft-entra-id.go rename to internal/providers/msentraid/msentraid.go index cfe0a85c54..b1286d21e0 100644 --- a/internal/providers/microsoft_entra_id/microsoft-entra-id.go +++ b/internal/providers/msentraid/msentraid.go @@ -1,5 +1,5 @@ -// Package microsoft_entra_id is the Microsoft Entra ID specific extension. -package microsoft_entra_id +// Package msentraid is the Microsoft Entra ID specific extension. +package msentraid import ( "context" diff --git a/internal/providers/withmsentraid.go b/internal/providers/withmsentraid.go index e8e4cafd68..28026fd3ac 100644 --- a/internal/providers/withmsentraid.go +++ b/internal/providers/withmsentraid.go @@ -3,10 +3,10 @@ package providers import ( - "github.com/ubuntu/authd-oidc-brokers/internal/providers/microsoft_entra_id" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/msentraid" ) // CurrentProviderInfo returns a Microsoft Entra ID provider implementation. func CurrentProviderInfo() ProviderInfoer { - return microsoft_entra_id.MSEntraIDProvider{} + return msentraid.MSEntraIDProvider{} } From bd5d3426c6abf427a91934df77f9d68b0a091ed0 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Mon, 22 Jul 2024 14:29:39 +0200 Subject: [PATCH 0101/1670] Fix newly created stuttering --- internal/providers/msentraid/msentraid.go | 12 ++++++------ internal/providers/withmsentraid.go | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index b1286d21e0..885da86135 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -21,21 +21,21 @@ import ( const localGroupPrefix = "linux-" -// MSEntraIDProvider is the Microsoft Entra ID provider implementation. -type MSEntraIDProvider struct{} +// Provider is the Microsoft Entra ID provider implementation. +type Provider struct{} // AdditionalScopes returns the generic scopes required by the EntraID provider. -func (p MSEntraIDProvider) AdditionalScopes() []string { +func (p Provider) AdditionalScopes() []string { return []string{oidc.ScopeOfflineAccess} } // AuthOptions returns the generic auth options required by the EntraID provider. -func (p MSEntraIDProvider) AuthOptions() []oauth2.AuthCodeOption { +func (p Provider) AuthOptions() []oauth2.AuthCodeOption { return []oauth2.AuthCodeOption{} } // GetGroups access the Microsoft Graph API to get the groups the user is a member of. -func (p MSEntraIDProvider) GetGroups(token *oauth2.Token) ([]group.Info, error) { +func (p Provider) GetGroups(token *oauth2.Token) ([]group.Info, error) { cred := azureTokenCredential{token: token} auth, err := msauth.NewAzureIdentityAuthenticationProvider(cred) if err != nil { @@ -114,7 +114,7 @@ func (p MSEntraIDProvider) GetGroups(token *oauth2.Token) ([]group.Info, error) // CurrentAuthenticationModesOffered returns the generic authentication modes supported by the provider. // // Token validity is not considered, only the presence of a token. -func (p MSEntraIDProvider) CurrentAuthenticationModesOffered( +func (p Provider) CurrentAuthenticationModesOffered( sessionMode string, supportedAuthModes map[string]string, tokenExists bool, diff --git a/internal/providers/withmsentraid.go b/internal/providers/withmsentraid.go index 28026fd3ac..a45dcc8b16 100644 --- a/internal/providers/withmsentraid.go +++ b/internal/providers/withmsentraid.go @@ -8,5 +8,5 @@ import ( // CurrentProviderInfo returns a Microsoft Entra ID provider implementation. func CurrentProviderInfo() ProviderInfoer { - return msentraid.MSEntraIDProvider{} + return msentraid.Provider{} } From b8e1323bd6c1f01fd84447c00ca64685426e2430 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Mon, 22 Jul 2024 18:02:07 +0200 Subject: [PATCH 0102/1670] Dereferencing msGroup when logging --- internal/providers/msentraid/msentraid.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 885da86135..8bd5252779 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -83,7 +83,7 @@ func (p Provider) GetGroups(token *oauth2.Token) ([]group.Info, error) { if id == nil { id = &unknown } - slog.Warn(pretty.Sprintf("Could not get displayName from group object (ID: %s) found: %v", *id, msGroup)) + slog.Warn(pretty.Sprintf("Could not get displayName from group object (ID: %s) found: %v", *id, *msGroup)) return nil, errors.New("could not parse group name") } groupName := strings.ToLower(*name) @@ -101,7 +101,7 @@ func (p Provider) GetGroups(token *oauth2.Token) ([]group.Info, error) { } id, ok := v.(*string) if !ok || id == nil { - slog.Warn(pretty.Sprintf("Could not get ID for group %q: %v", groupName, msGroup)) + slog.Warn(pretty.Sprintf("Could not get ID for group %q: %v", groupName, *msGroup)) return nil, errors.New("could not parse group id") } From aeea04c2ba98f39e9e64cc6a8c6b89cfe7c4e636 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Tue, 23 Jul 2024 11:01:27 +0200 Subject: [PATCH 0103/1670] Switch to pp to handle nested pointers The struct has nested pointer, use then pp to access them recursively, but disable coloring. --- internal/providers/msentraid/msentraid.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 8bd5252779..449cbea3b6 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -11,7 +11,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/coreos/go-oidc/v3/oidc" - "github.com/kr/pretty" + "github.com/k0kubun/pp" msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go" msauth "github.com/microsoftgraph/msgraph-sdk-go-core/authentication" "github.com/microsoftgraph/msgraph-sdk-go/models" @@ -19,6 +19,10 @@ import ( "golang.org/x/oauth2" ) +func init() { + pp.ColoringEnabled = false +} + const localGroupPrefix = "linux-" // Provider is the Microsoft Entra ID provider implementation. @@ -83,7 +87,7 @@ func (p Provider) GetGroups(token *oauth2.Token) ([]group.Info, error) { if id == nil { id = &unknown } - slog.Warn(pretty.Sprintf("Could not get displayName from group object (ID: %s) found: %v", *id, *msGroup)) + slog.Warn(pp.Sprintf("Could not get displayName from group object (ID: %s) found: %v", *id, *msGroup)) return nil, errors.New("could not parse group name") } groupName := strings.ToLower(*name) @@ -101,7 +105,7 @@ func (p Provider) GetGroups(token *oauth2.Token) ([]group.Info, error) { } id, ok := v.(*string) if !ok || id == nil { - slog.Warn(pretty.Sprintf("Could not get ID for group %q: %v", groupName, *msGroup)) + slog.Warn(pp.Sprintf("Could not get ID for group %q: %v", groupName, *msGroup)) return nil, errors.New("could not parse group id") } From 42f38eba1b1bfed683a54c41bdb047a27e9d22fc Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Tue, 23 Jul 2024 11:02:28 +0200 Subject: [PATCH 0104/1670] Update go.* to add pp --- go.mod | 7 ++++--- go.sum | 13 ++++++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index e30d58f709..9ae8a633cf 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/godbus/dbus/v5 v5.1.0 github.com/google/uuid v1.6.0 - github.com/kr/pretty v0.3.1 + github.com/k0kubun/pp v3.0.1+incompatible github.com/microsoftgraph/msgraph-sdk-go v1.46.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.1.0 github.com/otiai10/copy v1.14.0 @@ -36,9 +36,11 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/kr/text v0.2.0 // indirect + github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/microsoft/kiota-abstractions-go v1.6.1 // indirect github.com/microsoft/kiota-authentication-azure-go v1.0.2 // indirect github.com/microsoft/kiota-http-go v1.3.2 // indirect @@ -49,7 +51,6 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect diff --git a/go.sum b/go.sum index 4008a0e126..17525da94c 100644 --- a/go.sum +++ b/go.sum @@ -9,7 +9,6 @@ github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDh github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -35,6 +34,10 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/k0kubun/pp v3.0.1+incompatible h1:3tqvf7QgUnZ5tXO6pNAZlrvHgl6DvifjDrd9g2S9Z40= +github.com/k0kubun/pp v3.0.1+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -43,6 +46,11 @@ github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 h1:9ZM/t5 github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069/go.mod h1:AT4NpQrOmyj1L/+hLja6aR0lk81yYYL4ePnj2kp7d6M= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/microsoft/kiota-abstractions-go v1.6.1 h1:NXK50S3BwJn9Wj6bO0YFuAig7y2WVgdQ/ie1ktMl2J4= github.com/microsoft/kiota-abstractions-go v1.6.1/go.mod h1:FI1I2OHg0E7bK5t8DPnw+9C/CHVyLP6XeqDBT+95pTE= github.com/microsoft/kiota-authentication-azure-go v1.0.2 h1:tClGeyFZJ+4Bakf8u0euPM4wqy4ethycdOgx3jyH3pI= @@ -69,11 +77,9 @@ github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -146,6 +152,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc 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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= From d85102b4f657c65fdb04e5ad5baa7862bfdbf455 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Jul 2024 09:17:44 +0000 Subject: [PATCH 0105/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go-core Bumps [github.com/microsoftgraph/msgraph-sdk-go-core](https://github.com/microsoftgraph/msgraph-sdk-go-core) from 1.1.0 to 1.2.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go-core/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go-core/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go-core/compare/v1.1.0...v1.2.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go-core dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 9ae8a633cf..bb04f4d931 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible github.com/microsoftgraph/msgraph-sdk-go v1.46.0 - github.com/microsoftgraph/msgraph-sdk-go-core v1.1.0 + github.com/microsoftgraph/msgraph-sdk-go-core v1.2.0 github.com/otiai10/copy v1.14.0 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 @@ -43,7 +43,7 @@ require ( github.com/mattn/go-isatty v0.0.17 // indirect github.com/microsoft/kiota-abstractions-go v1.6.1 // indirect github.com/microsoft/kiota-authentication-azure-go v1.0.2 // indirect - github.com/microsoft/kiota-http-go v1.3.2 // indirect + github.com/microsoft/kiota-http-go v1.4.1 // indirect github.com/microsoft/kiota-serialization-form-go v1.0.0 // indirect github.com/microsoft/kiota-serialization-json-go v1.0.7 // indirect github.com/microsoft/kiota-serialization-multipart-go v1.0.0 // indirect diff --git a/go.sum b/go.sum index 17525da94c..8f4fa80682 100644 --- a/go.sum +++ b/go.sum @@ -55,8 +55,8 @@ github.com/microsoft/kiota-abstractions-go v1.6.1 h1:NXK50S3BwJn9Wj6bO0YFuAig7y2 github.com/microsoft/kiota-abstractions-go v1.6.1/go.mod h1:FI1I2OHg0E7bK5t8DPnw+9C/CHVyLP6XeqDBT+95pTE= github.com/microsoft/kiota-authentication-azure-go v1.0.2 h1:tClGeyFZJ+4Bakf8u0euPM4wqy4ethycdOgx3jyH3pI= github.com/microsoft/kiota-authentication-azure-go v1.0.2/go.mod h1:aTcti0bUJEcq7kBfQG4Sr4ElvRNuaalXcFEu4iEyQ6M= -github.com/microsoft/kiota-http-go v1.3.2 h1:HSrbw6MDMmgLaqU1qT3SgdmD/18KPW2usfyUtE017Lw= -github.com/microsoft/kiota-http-go v1.3.2/go.mod h1:pEs3eLpCJBhsesjtvM/3T4ODKCofPgDlDsGHHetau2g= +github.com/microsoft/kiota-http-go v1.4.1 h1:zR54JahUOcu8h9C5z00fcQChzX8d01+BwhkTS8H16Ro= +github.com/microsoft/kiota-http-go v1.4.1/go.mod h1:Kup5nMDD3a9sjdgRKHCqZWqtrv3FbprjcPaGjLR6FzM= github.com/microsoft/kiota-serialization-form-go v1.0.0 h1:UNdrkMnLFqUCccQZerKjblsyVgifS11b3WCx+eFEsAI= github.com/microsoft/kiota-serialization-form-go v1.0.0/go.mod h1:h4mQOO6KVTNciMF6azi1J9QB19ujSw3ULKcSNyXXOMA= github.com/microsoft/kiota-serialization-json-go v1.0.7 h1:yMbckSTPrjZdM4EMXgzLZSA3CtDaUBI350u0VoYRz7Y= @@ -67,8 +67,8 @@ github.com/microsoft/kiota-serialization-text-go v1.0.0 h1:XOaRhAXy+g8ZVpcq7x7a0 github.com/microsoft/kiota-serialization-text-go v1.0.0/go.mod h1:sM1/C6ecnQ7IquQOGUrUldaO5wj+9+v7G2W3sQ3fy6M= github.com/microsoftgraph/msgraph-sdk-go v1.46.0 h1:5AT0FZkPFTiLZ9nZym1TLUFlcHFVhAAvqNb9B52Xvn0= github.com/microsoftgraph/msgraph-sdk-go v1.46.0/go.mod h1:Rf2aiCdmTvY325XfhxnUiRIuIyj66SYdszaovW091S0= -github.com/microsoftgraph/msgraph-sdk-go-core v1.1.0 h1:NB7c/n4Knj+TLaLfjsahhSqoUqoN/CtyNB0XIe/nJnM= -github.com/microsoftgraph/msgraph-sdk-go-core v1.1.0/go.mod h1:M3w/5IFJ1u/DpwOyjsjNSVEA43y1rLOeX58suyfBhGk= +github.com/microsoftgraph/msgraph-sdk-go-core v1.2.0 h1:vHvUKVnk0FkLSJv+aUvxIsM5kLNqWlu/WzUM8S4XOy4= +github.com/microsoftgraph/msgraph-sdk-go-core v1.2.0/go.mod h1:armKxoJybX70GBpd748K2e+1AySPYRbhvYW7EBplujw= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= From fe43cccb81f54713fdb3c9995e0c8e19cec9da9a Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Wed, 24 Jul 2024 12:55:06 +0200 Subject: [PATCH 0106/1670] Check MSEntraID group access permissions Current user group membership always returns, with User.Read.All the list of groups, but without their details. To access their details, we need to list all groups that the user has potentially access too. Add a check for it to return a better error for those use cases. --- internal/providers/msentraid/msentraid.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 449cbea3b6..e9ef6ab532 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -14,6 +14,7 @@ import ( "github.com/k0kubun/pp" msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go" msauth "github.com/microsoftgraph/msgraph-sdk-go-core/authentication" + msgraphgroups "github.com/microsoftgraph/msgraph-sdk-go/groups" "github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/ubuntu/authd-oidc-brokers/internal/providers/group" "golang.org/x/oauth2" @@ -53,6 +54,17 @@ func (p Provider) GetGroups(token *oauth2.Token) ([]group.Info, error) { client := msgraphsdk.NewGraphServiceClient(adapter) + // Check GroupMember.Read.All access + var topOne int32 = 1 + requestOptions := &msgraphgroups.GroupsRequestBuilderGetRequestConfiguration{ + QueryParameters: &msgraphgroups.GroupsRequestBuilderGetQueryParameters{ + Top: &topOne, // Limit to only one group + }, + } + if _, err = client.Groups().Get(context.Background(), requestOptions); err != nil { + return nil, fmt.Errorf("could not access user's groups: %v", err) + } + m, err := client.Me().MemberOf().Get(context.Background(), nil) if err != nil { return nil, err From b08569a385fd20a663473dee3fc0113f97235dc8 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Wed, 24 Jul 2024 09:33:49 -0400 Subject: [PATCH 0107/1670] Simplify providers CurrentAuthModeOffered signature --- internal/providers/msentraid/msentraid.go | 4 ++-- internal/providers/noprovider/noprovider.go | 4 ++-- internal/providers/providers.go | 2 +- internal/testutils/provider.go | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index e9ef6ab532..cbae601aaa 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -135,7 +135,7 @@ func (p Provider) CurrentAuthenticationModesOffered( supportedAuthModes map[string]string, tokenExists bool, providerReachable bool, - endpoints map[string]string, + endpoints map[string]struct{}, currentAuthStep int, ) ([]string, error) { var offeredModes []string @@ -150,7 +150,7 @@ func (p Provider) CurrentAuthenticationModesOffered( } default: // auth mode - if providerReachable && endpoints["device_auth"] != "" { + if _, ok := endpoints["device_auth"]; ok && providerReachable { offeredModes = []string{"device_auth"} } if tokenExists { diff --git a/internal/providers/noprovider/noprovider.go b/internal/providers/noprovider/noprovider.go index 6f6f898727..2e02ca0d4e 100644 --- a/internal/providers/noprovider/noprovider.go +++ b/internal/providers/noprovider/noprovider.go @@ -34,7 +34,7 @@ func (p NoProvider) CurrentAuthenticationModesOffered( supportedAuthModes map[string]string, tokenExists bool, providerReachable bool, - endpoints map[string]string, + endpoints map[string]struct{}, currentAuthStep int, ) ([]string, error) { var offeredModes []string @@ -49,7 +49,7 @@ func (p NoProvider) CurrentAuthenticationModesOffered( } default: // auth mode - if providerReachable && endpoints["device_auth"] != "" { + if _, ok := endpoints["device_auth"]; ok && providerReachable { offeredModes = []string{"device_auth"} } if tokenExists { diff --git a/internal/providers/providers.go b/internal/providers/providers.go index c37716a10f..ab54f8ad4b 100644 --- a/internal/providers/providers.go +++ b/internal/providers/providers.go @@ -15,7 +15,7 @@ type ProviderInfoer interface { supportedAuthModes map[string]string, tokenExists bool, providerReachable bool, - endpoints map[string]string, + endpoints map[string]struct{}, currentAuthStep int, ) ([]string, error) GetGroups(*oauth2.Token) ([]group.Info, error) diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index 77ad842fab..985dfc920c 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -238,7 +238,7 @@ func (p MockProviderInfoer) CurrentAuthenticationModesOffered( supportedAuthModes map[string]string, tokenExists bool, providerReachable bool, - endpoints map[string]string, + endpoints map[string]struct{}, currentAuthStep int, ) ([]string, error) { var offeredModes []string @@ -253,7 +253,7 @@ func (p MockProviderInfoer) CurrentAuthenticationModesOffered( } default: // auth mode - if providerReachable && endpoints["device_auth"] != "" { + if _, ok := endpoints["device_auth"]; ok && providerReachable { offeredModes = []string{"device_auth"} } if tokenExists { From dfa0656c0414de0b0a9ad46f7437685e33cb9902 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Thu, 25 Jul 2024 09:08:12 +0200 Subject: [PATCH 0108/1670] Include transitive groups membership MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When Azure AD group membership are organized in a tree, it’s expected that transitive inclusion to be taken into account. Change the API, which requires the same permission to follow this. The list of groups is then flatten when going over the API, resulting in no other code change needed. --- internal/providers/msentraid/msentraid.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index e9ef6ab532..53589b2121 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -65,7 +65,7 @@ func (p Provider) GetGroups(token *oauth2.Token) ([]group.Info, error) { return nil, fmt.Errorf("could not access user's groups: %v", err) } - m, err := client.Me().MemberOf().Get(context.Background(), nil) + m, err := client.Me().TransitiveMemberOf().Get(context.Background(), nil) if err != nil { return nil, err } From a71bcdc25087f5c4dfbd5b8e52957f99abfa2bed Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Wed, 24 Jul 2024 09:51:39 -0400 Subject: [PATCH 0109/1670] Allow to start broker and sessions in offline mode Even though we "allowed" offline authentication, the broker would not start without a network connection. Now, the broker does not rely on a connection to run and now sessions have control over the auth config needed to authenticate the user online. This means we can start sessions in offline mode when failing to connect to the provider. --- internal/broker/broker.go | 168 ++++++++++-------- internal/broker/broker_test.go | 99 +++++++++-- internal/broker/export_test.go | 14 +- .../first_call | 2 +- .../provider_url/test-user@email.com.cache | 1 + .../first_call | 3 + .../cache/.empty | 0 .../first_call | 3 + .../cache/.empty | 0 .../first_call | 3 + .../first_call | 2 +- internal/testutils/provider.go | 27 ++- 12 files changed, 221 insertions(+), 101 deletions(-) create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/cache/provider_url/test-user@email.com.cache create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/cache/.empty create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_link_expires/cache/.empty create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_link_expires/first_call diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 37b0d8adb0..36c1a80452 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -10,12 +10,12 @@ import ( "encoding/json" "errors" "fmt" - "net/http" "os" "path/filepath" "slices" "strings" "sync" + "time" "github.com/coreos/go-oidc/v3/oidc" "github.com/google/uuid" @@ -26,7 +26,10 @@ import ( "golang.org/x/oauth2" ) -const maxAuthAttempts = 3 +const ( + maxAuthAttempts = 3 + maxRequestDuration = 5 * time.Second +) // Config is the configuration for the broker. type Config struct { @@ -39,8 +42,11 @@ type Config struct { // Broker is the real implementation of the broker to track sessions and process oidc calls. type Broker struct { - providerInfo providers.ProviderInfoer - auth authConfig + providerInfo providers.ProviderInfoer + issuerURL string + oidcCfg oidc.Config + + cachePath string homeDirPath string allowedSSHSuffixes []string @@ -50,16 +56,6 @@ type Broker struct { privateKey *rsa.PrivateKey } -type authConfig struct { - cachePath string - - provider *oidc.Provider - providerURL string - - oidcCfg oidc.Config - oauthCfg oauth2.Config -} - type sessionInfo struct { username string lang string @@ -70,7 +66,9 @@ type sessionInfo struct { authModes []string attemptsPerMode map[string]int + authCfg authConfig authInfo map[string]any + isOffline bool cachePath string currentAuthStep int @@ -78,6 +76,12 @@ type sessionInfo struct { isAuthenticating *isAuthenticatedCtx } +// authConfig holds the required values for authenticating a user with OIDC. +type authConfig struct { + provider *oidc.Provider + oauth oauth2.Config +} + type isAuthenticatedCtx struct { ctx context.Context cancelFunc context.CancelFunc @@ -109,9 +113,7 @@ func New(cfg Config, args ...Option) (b *Broker, err error) { return &Broker{}, errors.New("cache path must be provided") } - clientID := cfg.ClientID - issuerURL := cfg.IssuerURL - if issuerURL == "" || clientID == "" { + if cfg.IssuerURL == "" || cfg.ClientID == "" { return &Broker{}, errors.New("issuer and client ID must be provided") } @@ -126,38 +128,19 @@ func New(cfg Config, args ...Option) (b *Broker, err error) { panic(fmt.Sprintf("could not create an valid rsa key: %v", err)) } - // Create provider - provider, err := oidc.NewProvider(context.TODO(), issuerURL) - if err != nil { - return &Broker{}, err - } - oidcCfg := oidc.Config{ - ClientID: clientID, - InsecureSkipSignatureCheck: opts.skipJWTSignatureCheck, - } - oauthCfg := oauth2.Config{ - ClientID: clientID, - Endpoint: provider.Endpoint(), - Scopes: append([]string{oidc.ScopeOpenID, "profile", "email"}, opts.providerInfo.AdditionalScopes()...), - } - authCfg := authConfig{ - provider: provider, - providerURL: issuerURL, - cachePath: cfg.CachePath, - oidcCfg: oidcCfg, - oauthCfg: oauthCfg, - } - - return &Broker{ + b = &Broker{ providerInfo: opts.providerInfo, - auth: authCfg, + issuerURL: cfg.IssuerURL, + oidcCfg: oidc.Config{ClientID: cfg.ClientID, InsecureSkipSignatureCheck: opts.skipJWTSignatureCheck}, + cachePath: cfg.CachePath, homeDirPath: homeDirPath, allowedSSHSuffixes: cfg.AllowedSSHSuffixes, privateKey: privateKey, currentSessions: make(map[string]sessionInfo), currentSessionsMu: sync.RWMutex{}, - }, nil + } + return b, nil } // NewSession creates a new session for the user. @@ -179,10 +162,17 @@ func (b *Broker) NewSession(username, lang, mode string) (sessionID, encryptionK return "", "", err } - _, url, _ := strings.Cut(b.auth.providerURL, "://") + _, url, _ := strings.Cut(b.issuerURL, "://") url = strings.ReplaceAll(url, "/", "_") url = strings.ReplaceAll(url, ":", "_") - session.cachePath = filepath.Join(b.auth.cachePath, url, username+".cache") + session.cachePath = filepath.Join(b.cachePath, url, username+".cache") + + // Check whether to start the session in offline mode. + session.authCfg, err = b.connectToProvider(context.Background()) + if err != nil { + slog.Debug(fmt.Sprintf("Could not connect to the provider: %v. Starting session in offline mode.", err)) + session.isOffline = true + } b.currentSessionsMu.Lock() b.currentSessions[sessionID] = session @@ -191,6 +181,24 @@ func (b *Broker) NewSession(username, lang, mode string) (sessionID, encryptionK return sessionID, base64.StdEncoding.EncodeToString(pubASN1), nil } +func (b *Broker) connectToProvider(ctx context.Context) (authCfg authConfig, err error) { + ctx, cancel := context.WithTimeout(ctx, maxRequestDuration) + defer cancel() + + provider, err := oidc.NewProvider(ctx, b.issuerURL) + if err != nil { + return authConfig{}, err + } + + oauthCfg := oauth2.Config{ + ClientID: b.oidcCfg.ClientID, + Endpoint: provider.Endpoint(), + Scopes: append([]string{oidc.ScopeOpenID, "profile", "email"}, b.providerInfo.AdditionalScopes()...), + } + + return authConfig{provider: provider, oauth: oauthCfg}, nil +} + // GetAuthenticationModes returns the authentication modes available for the user. func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []map[string]string) (authModes []map[string]string, err error) { session, err := b.getSession(sessionID) @@ -204,24 +212,17 @@ func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []m _, err = os.Stat(session.cachePath) tokenExists := err == nil - // Checks if the provider is accessible and if device authentication is supported - providerReachable := true - r, err := http.Get(strings.TrimSuffix(b.auth.providerURL, "/") + "/.well-known/openid-configuration") - // This means the provider is not available or something bad happened, so we assume no connection. - if err != nil || r.StatusCode != http.StatusOK { - providerReachable = false + endpoints := make(map[string]struct{}) + if session.authCfg.provider != nil && session.authCfg.provider.Endpoint().DeviceAuthURL != "" { + endpoints["device_auth"] = struct{}{} } availableModes, err := b.providerInfo.CurrentAuthenticationModesOffered( session.mode, supportedAuthModes, tokenExists, - providerReachable, - map[string]string{ - "auth": b.auth.provider.Endpoint().AuthURL, - "device_auth": b.auth.provider.Endpoint().DeviceAuthURL, - "token": b.auth.provider.Endpoint().TokenURL, - }, + !session.isOffline, + endpoints, session.currentAuthStep) if err != nil { return nil, err @@ -307,7 +308,9 @@ func (b *Broker) generateUILayout(session *sessionInfo, authModeID string) (map[ var uiLayout map[string]string switch authModeID { case "device_auth": - response, err := b.auth.oauthCfg.DeviceAuth(context.TODO()) + ctx, cancel := context.WithTimeout(context.Background(), maxRequestDuration) + defer cancel() + response, err := session.authCfg.oauth.DeviceAuth(ctx) if err != nil { return nil, fmt.Errorf("could not generate QR code layout: %v", err) } @@ -422,7 +425,6 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo } var authInfo authCachedInfo - offline := false switch session.selectedMode { case "device_auth": response, ok := session.authInfo["response"].(*oauth2.DeviceAuthResponse) @@ -430,7 +432,12 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo return AuthDenied, errorMessage{Message: "could not get required response"} } - t, err := b.auth.oauthCfg.DeviceAccessToken(ctx, response, b.providerInfo.AuthOptions()...) + if response.Expiry.IsZero() { + response.Expiry = time.Now().Add(time.Hour) + } + expiryCtx, cancel := context.WithDeadline(ctx, response.Expiry) + defer cancel() + t, err := session.authCfg.oauth.DeviceAccessToken(expiryCtx, response, b.providerInfo.AuthOptions()...) if err != nil { return AuthRetry, errorMessage{Message: fmt.Sprintf("could not authenticate user: %v", err)} } @@ -450,7 +457,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo return AuthNext, nil case "password": - authInfo, offline, err = b.loadAuthInfo(session, challenge) + authInfo, err = b.loadAuthInfo(ctx, session, challenge) if err != nil { return AuthRetry, errorMessage{Message: fmt.Sprintf("could not authenticate user: %v", err)} } @@ -480,7 +487,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo } } - if offline { + if session.isOffline { return AuthGranted, userInfoMessage{UserInfo: authInfo.UserInfo} } @@ -627,34 +634,36 @@ func (b *Broker) cacheAuthInfo(session *sessionInfo, authInfo authCachedInfo, pa } // loadAuthInfo deserializes the token from the cache and refreshes it if needed. -func (b *Broker) loadAuthInfo(session *sessionInfo, password string) (loadedInfo authCachedInfo, offline bool, err error) { +func (b *Broker) loadAuthInfo(ctx context.Context, session *sessionInfo, password string) (loadedInfo authCachedInfo, err error) { defer decorate.OnError(&err, "could not load cached info") s, err := os.ReadFile(session.cachePath) if err != nil { - return authCachedInfo{}, false, fmt.Errorf("could not read token: %v", err) + return authCachedInfo{}, fmt.Errorf("could not read token: %v", err) } deserialized, err := decrypt(s, []byte(password)) if err != nil { - return authCachedInfo{}, false, fmt.Errorf("could not deserialize token: %v", err) + return authCachedInfo{}, fmt.Errorf("could not deserialize token: %v", err) } var cachedInfo authCachedInfo if err := json.Unmarshal(deserialized, &cachedInfo); err != nil { - return authCachedInfo{}, false, fmt.Errorf("could not unmarshal token: %v", err) + return authCachedInfo{}, fmt.Errorf("could not unmarshal token: %v", err) + } + + // If the token is still valid, we return it. Ideally, we would refresh it online, but the TokenSource API also uses + // this logic to decide whether the token needs refreshing, so we should run it early to control the returned values. + if cachedInfo.Token.Valid() || session.isOffline { + return cachedInfo, nil } // Tries to refresh the access token. If the service is unavailable, we allow authentication. - tok, err := b.auth.oauthCfg.TokenSource(context.Background(), cachedInfo.Token).Token() + timeoutCtx, cancel := context.WithTimeout(ctx, maxRequestDuration) + defer cancel() + tok, err := session.authCfg.oauth.TokenSource(timeoutCtx, cachedInfo.Token).Token() if err != nil { - castErr := &oauth2.RetrieveError{} - if !errors.As(err, &castErr) || castErr.Response.StatusCode != http.StatusServiceUnavailable { - return authCachedInfo{}, false, fmt.Errorf("could not refresh token: %v", err) - } - - // The provider is unavailable, so we allow offline authentication. - return cachedInfo, true, nil + return authCachedInfo{}, fmt.Errorf("could not refresh token: %v", err) } // If the ID token was refreshed, we overwrite the cached one. @@ -663,13 +672,17 @@ func (b *Broker) loadAuthInfo(session *sessionInfo, password string) (loadedInfo refreshedIDToken = cachedInfo.RawIDToken } - return authCachedInfo{Token: tok, RawIDToken: refreshedIDToken}, false, nil + return authCachedInfo{Token: tok, RawIDToken: refreshedIDToken}, nil } func (b *Broker) fetchUserInfo(ctx context.Context, session *sessionInfo, t *authCachedInfo) (userInfo userInfo, err error) { defer decorate.OnError(&err, "could not fetch user info") - userClaims, err := b.userClaims(ctx, t.RawIDToken, session.username) + if session.isOffline { + return userInfo, errors.New("session is in offline mode") + } + + userClaims, err := b.userClaims(ctx, session, t.RawIDToken, session.username) if err != nil { return userInfo, err } @@ -690,9 +703,10 @@ type claims struct { Sub string `json:"sub"` } -func (b *Broker) userClaims(ctx context.Context, rawIDToken, username string) (userClaims claims, err error) { +func (b *Broker) userClaims(ctx context.Context, session *sessionInfo, rawIDToken, username string) (userClaims claims, err error) { // We need to verify the token to get the user claims. - idToken, err := b.auth.provider.Verifier(&b.auth.oidcCfg).Verify(ctx, rawIDToken) + // Due to an internal implementation, the verification process can't be cancelled, so we can't add a timeout for it. + idToken, err := session.authCfg.provider.Verifier(&b.oidcCfg).Verify(ctx, rawIDToken) if err != nil { return claims{}, fmt.Errorf("could not verify token: %v", err) } diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index e658cc505c..6815e71ab0 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -1,7 +1,6 @@ package broker_test import ( - "context" "encoding/json" "flag" "fmt" @@ -32,12 +31,12 @@ func TestNew(t *testing.T) { wantErr bool }{ - "Successfully create new broker": {}, + "Successfully create new broker": {}, + "Successfully create new even if can not connect to provider": {issuer: "https://notavailable"}, - "Error if issuer is not provided": {issuer: "-", wantErr: true}, - "Error if provided issuer is not reachable": {issuer: "https://notavailable", wantErr: true}, - "Error if clientID is not provided": {clientID: "-", wantErr: true}, - "Error if cacheDir is not provided": {cachePath: "-", wantErr: true}, + "Error if issuer is not provided": {issuer: "-", wantErr: true}, + "Error if clientID is not provided": {clientID: "-", wantErr: true}, + "Error if cacheDir is not provided": {cachePath: "-", wantErr: true}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { @@ -78,6 +77,52 @@ func TestNew(t *testing.T) { } } +func TestNewSession(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + customHandlers map[string]testutils.ProviderHandler + + wantOffline bool + }{ + "Successfully create new session": {}, + "Creates new session in offline mode if provider is not available": { + customHandlers: map[string]testutils.ProviderHandler{ + "/.well-known/openid-configuration": testutils.UnavailableHandler(), + }, + wantOffline: true, + }, + "Creates new session in offline mode if provider connection times out": { + customHandlers: map[string]testutils.ProviderHandler{ + "/.well-known/openid-configuration": testutils.HangingHandler(broker.MaxRequestDuration + 1), + }, + wantOffline: true, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + var opts []testutils.OptionProvider + for endpoint, handler := range tc.customHandlers { + opts = append(opts, testutils.WithHandler(endpoint, handler)) + } + + provider, stopServer := testutils.StartMockProvider("", opts...) + t.Cleanup(stopServer) + b := newBrokerForTests(t, broker.Config{IssuerURL: provider.URL}) + + id, _, err := b.NewSession("test-user", "lang", "auth") + require.NoError(t, err, "NewSession should not have returned an error") + + gotOffline, err := b.IsOffline(id) + require.NoError(t, err, "Session should have been created") + + require.Equal(t, tc.wantOffline, gotOffline, "Session should have been created in the expected mode") + }) + } +} + var supportedUILayouts = map[string]map[string]string{ "form": { "type": "form", @@ -162,6 +207,12 @@ func TestGetAuthenticationModes(t *testing.T) { testutils.OpenIDHandlerWithNoDeviceEndpoint("http://"+address), )) } + if tc.unavailableProvider { + opts = append(opts, testutils.WithHandler( + "/.well-known/openid-configuration", + testutils.UnavailableHandler(), + )) + } provider, stopServer = testutils.StartMockProvider(address, opts...) t.Cleanup(stopServer) } @@ -188,10 +239,6 @@ func TestGetAuthenticationModes(t *testing.T) { layouts = append(layouts, supportedUILayouts[layout]) } - if tc.unavailableProvider { - stopServer() - } - got, err := b.GetAuthenticationModes(sessionID, layouts) if tc.wantErr { require.Error(t, err, "GetAuthenticationModes should have returned an error") @@ -236,6 +283,11 @@ func TestSelectAuthenticationMode(t *testing.T) { "/device_auth": testutils.UnavailableHandler(), }, }, + "Error when selecting device_auth but request times out": {modeName: "device_auth", wantErr: true, + customHandlers: map[string]testutils.ProviderHandler{ + "/device_auth": testutils.HangingHandler(broker.MaxRequestDuration + 1), + }, + }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { @@ -327,7 +379,7 @@ func TestIsAuthenticated(t *testing.T) { firstMode: "password", preexistentToken: "expired", customHandlers: map[string]testutils.ProviderHandler{ - "/token": testutils.UnavailableHandler(), + "/.well-known/openid-configuration": testutils.UnavailableHandler(), }, }, @@ -347,14 +399,29 @@ func TestIsAuthenticated(t *testing.T) { }, "Error when mode is password and token is invalid": {firstMode: "password", preexistentToken: "invalid"}, "Error when mode is password and cached token can't be refreshed": {firstMode: "password", preexistentToken: "no-refresh"}, - "Error when mode is password and can not fetch user info": {firstMode: "password", preexistentToken: "invalid-id"}, + "Error when mode is password and token refresh times out": {firstMode: "password", preexistentToken: "expired", + customHandlers: map[string]testutils.ProviderHandler{ + "/token": testutils.HangingHandler(broker.MaxRequestDuration + 1), + }, + }, + "Error when mode is password and can not fetch user info": {firstMode: "password", preexistentToken: "invalid-id"}, "Error when mode is qrcode and response is invalid": {firstAuthInfo: map[string]any{"response": "not a valid response"}}, + "Error when mode is qrcode and link expires": { + customHandlers: map[string]testutils.ProviderHandler{ + "/device_auth": testutils.ExpiryDeviceAuthHandler(), + }, + }, "Error when mode is qrcode and can not get token": { customHandlers: map[string]testutils.ProviderHandler{ "/token": testutils.UnavailableHandler(), }, }, + "Error when mode is qrcode and can not get token due to timeout": { + customHandlers: map[string]testutils.ProviderHandler{ + "/token": testutils.HangingHandler(broker.MaxRequestDuration + 1), + }, + }, "Error when empty challenge is provided for local password": {firstChallenge: "-", wantSecondCall: true, secondChallenge: "-"}, "Error when mode is newpassword and token is not set": { firstMode: "newpassword", @@ -394,7 +461,7 @@ func TestIsAuthenticated(t *testing.T) { opts = append(opts, testutils.WithHandler(path, handler)) } p, cleanup := testutils.StartMockProvider("", opts...) - defer cleanup() + t.Cleanup(cleanup) provider = p } @@ -463,6 +530,7 @@ func TestIsAuthenticated(t *testing.T) { // Redact variant values from the response data = strings.ReplaceAll(data, sessionID, "SESSION_ID") data = strings.ReplaceAll(data, filepath.Dir(b.TokenPathForSession(sessionID)), "provider_cache_path") + data = strings.ReplaceAll(data, provider.URL, "provider_url") errStr := strings.ReplaceAll(fmt.Sprintf("%v", err), sessionID, "SESSION_ID") got := isAuthenticatedResponse{Access: access, Data: data, Err: errStr} @@ -504,6 +572,7 @@ func TestIsAuthenticated(t *testing.T) { // Redact variant values from the response data = strings.ReplaceAll(data, sessionID, "SESSION_ID") data = strings.ReplaceAll(data, filepath.Dir(b.TokenPathForSession(sessionID)), "provider_cache_path") + data = strings.ReplaceAll(data, provider.URL, "provider_url") errStr := strings.ReplaceAll(fmt.Sprintf("%v", err), sessionID, "SESSION_ID") got := isAuthenticatedResponse{Access: access, Data: data, Err: errStr} @@ -630,8 +699,7 @@ func TestFetchUserInfo(t *testing.T) { func TestCancelIsAuthenticated(t *testing.T) { t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) - provider, cleanup := testutils.StartMockProvider("", testutils.WithHandler("/token", testutils.HangingHandler(ctx))) + provider, cleanup := testutils.StartMockProvider("", testutils.WithHandler("/token", testutils.HangingHandler(3*time.Second))) t.Cleanup(cleanup) b := newBrokerForTests(t, broker.Config{IssuerURL: provider.URL}) @@ -650,7 +718,6 @@ func TestCancelIsAuthenticated(t *testing.T) { time.Sleep(50 * time.Millisecond) b.CancelIsAuthenticated(sessionID) - cancel() <-stopped } diff --git a/internal/broker/export_test.go b/internal/broker/export_test.go index 75c18db314..8524229b43 100644 --- a/internal/broker/export_test.go +++ b/internal/broker/export_test.go @@ -22,7 +22,7 @@ func (b *Broker) TokenPathForSession(sessionID string) string { // CachePath returns the path to the cache directory for tests. func (b *Broker) CachePath() string { - return b.auth.cachePath + return b.cachePath } // UpdateSessionAuthStep updates the current auth step for the given session. @@ -110,5 +110,17 @@ func (b *Broker) FetchUserInfo(sessionID string, cachedInfo *authCachedInfo) (us return uInfo, err } +// IsOffline returns whether the given session is offline or an error if the session does not exist. +func (b *Broker) IsOffline(sessionID string) (bool, error) { + session, err := b.getSession(sessionID) + if err != nil { + return false, err + } + return session.isOffline, nil +} + // UserInfo exposes the private userInfo for tests. type UserInfo = userInfo + +// MaxRequestDuration exposes the broker's maxRequestDuration for tests. +const MaxRequestDuration = maxRequestDuration diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/first_call index 72fec9bc67..1d0929cfbd 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/first_call @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"saved-user","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"saved-user","groups":[{"name":"saved-remote-group","ugid":"12345"},{"name":"saved-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/cache/provider_url/test-user@email.com.cache new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/cache/provider_url/test-user@email.com.cache @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/first_call new file mode 100644 index 0000000000..f54662431b --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/first_call @@ -0,0 +1,3 @@ +access: retry +data: '{"message":"could not authenticate user: could not load cached info: could not refresh token: Post \"provider_url/token\": context deadline exceeded"}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/cache/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/first_call new file mode 100644 index 0000000000..681dda0fdc --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/first_call @@ -0,0 +1,3 @@ +access: retry +data: '{"message":"could not authenticate user: oauth2: cannot fetch token: 408 Request Timeout\nResponse: "}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_link_expires/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_link_expires/cache/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_link_expires/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_link_expires/first_call new file mode 100644 index 0000000000..24ae10defb --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_link_expires/first_call @@ -0,0 +1,3 @@ +access: retry +data: '{"message":"could not authenticate user: context deadline exceeded"}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call index 72fec9bc67..1d0929cfbd 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"saved-user","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"saved-user","groups":[{"name":"saved-remote-group","ugid":"12345"},{"name":"saved-local-group","ugid":""}]}}' err: diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index 985dfc920c..2356ed21fc 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -1,7 +1,6 @@ package testutils import ( - "context" "encoding/base64" "errors" "fmt" @@ -129,7 +128,7 @@ func DefaultDeviceAuthHandler() ProviderHandler { func DefaultTokenHandler(serverURL string) ProviderHandler { return func(w http.ResponseWriter, r *http.Request) { // Mimics user going through auth process - time.Sleep(3 * time.Second) + time.Sleep(2 * time.Second) idToken := fmt.Sprintf(`{ "iss": "%s", @@ -187,16 +186,34 @@ func CustomResponseHandler(response string) ProviderHandler { } } -// HangingHandler returns a handler that hangs the request until the context is done. -func HangingHandler(ctx context.Context) ProviderHandler { +// HangingHandler returns a handler that hangs the request until the duration has elapsed. +func HangingHandler(d time.Duration) ProviderHandler { return func(w http.ResponseWriter, r *http.Request) { - <-ctx.Done() + time.Sleep(d) w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusRequestTimeout) } } +// ExpiryDeviceAuthHandler returns a handler that returns a device auth response with a short expiry time. +func ExpiryDeviceAuthHandler() ProviderHandler { + return func(w http.ResponseWriter, _ *http.Request) { + response := `{ + "device_code": "device_code", + "user_code": "user_code", + "verification_uri": "https://verification_uri.com", + "expires_in": 4 + }` + + w.Header().Add("Content-Type", "application/json") + _, err := w.Write([]byte(response)) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + } + } +} + // MockProviderInfoer is a mock that implements the ProviderInfoer interface. type MockProviderInfoer struct { Scopes []string From f1dfbd9b86e55aca21892b26b340f52ce532d35b Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 30 Jul 2024 07:43:03 -0400 Subject: [PATCH 0110/1670] Move fetchUserInfo logic to the providers pkg We used to control how the ID token was parsed into claims in the broker itself. This would become progressively unmaintainable as each provider could understand the claims differently. Now, instead of having to enforce the providers to use the email as username for local authentication, they control what will be parsed from the ID Token and return a UserInfo to broker, which then only fills the empty values with defaults (if any). --- internal/broker/authresponses.go | 4 +- internal/broker/broker.go | 75 ++++------------- internal/broker/broker_test.go | 17 ++-- internal/broker/export_test.go | 13 ++- internal/broker/helper_test.go | 36 ++++---- ...ser_info_ignoring_different_casing_in_name | 10 +++ ...r_info_with_default_home_when_not_provided | 2 +- .../successfully_fetch_user_info_with_groups | 2 +- ...uccessfully_fetch_user_info_without_groups | 2 +- .../first_call | 2 +- .../first_call | 2 +- .../first_call | 2 +- .../second_call | 2 +- .../first_call | 2 +- .../second_call | 2 +- internal/providers/group/info.go | 8 -- internal/providers/info/info.go | 44 ++++++++++ internal/providers/info/info_test.go | 82 +++++++++++++++++++ internal/providers/msentraid/msentraid.go | 59 ++++++++++--- internal/providers/noprovider/noprovider.go | 52 ++++++++++-- internal/providers/providers.go | 7 +- internal/testutils/provider.go | 50 +++++++++-- 22 files changed, 342 insertions(+), 133 deletions(-) create mode 100644 internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_ignoring_different_casing_in_name delete mode 100644 internal/providers/group/info.go create mode 100644 internal/providers/info/info.go create mode 100644 internal/providers/info/info_test.go diff --git a/internal/broker/authresponses.go b/internal/broker/authresponses.go index ddb8663e7b..b8f0b3441e 100644 --- a/internal/broker/authresponses.go +++ b/internal/broker/authresponses.go @@ -1,12 +1,14 @@ package broker +import "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" + type isAuthenticatedDataResponse interface { isAuthenticatedDataResponse() } // userInfoMessage represents the user information message that is returned to authd. type userInfoMessage struct { - UserInfo userInfo `json:"userinfo"` + UserInfo info.User `json:"userinfo"` } func (userInfoMessage) isAuthenticatedDataResponse() {} diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 36c1a80452..eca6862275 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -20,7 +20,7 @@ import ( "github.com/coreos/go-oidc/v3/oidc" "github.com/google/uuid" "github.com/ubuntu/authd-oidc-brokers/internal/providers" - "github.com/ubuntu/authd-oidc-brokers/internal/providers/group" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "github.com/ubuntu/decorate" "golang.org/x/exp/slog" "golang.org/x/oauth2" @@ -570,7 +570,8 @@ func (b *Broker) UserPreCheck(username string) (string, error) { return "", errors.New("username does not match the allowed suffixes") } - encoded, err := json.Marshal(b.userInfoFromClaims(claims{Email: username, Name: username}, nil)) + u := info.NewUser(username, filepath.Join(b.homeDirPath, username), "", "", "", nil) + encoded, err := json.Marshal(u) if err != nil { return "", fmt.Errorf("could not marshal user info: %v", err) } @@ -604,7 +605,7 @@ func (b *Broker) updateSession(sessionID string, session sessionInfo) error { type authCachedInfo struct { Token *oauth2.Token RawIDToken string - UserInfo userInfo + UserInfo info.User } // cacheAuthInfo serializes the access token and cache it. @@ -675,74 +676,32 @@ func (b *Broker) loadAuthInfo(ctx context.Context, session *sessionInfo, passwor return authCachedInfo{Token: tok, RawIDToken: refreshedIDToken}, nil } -func (b *Broker) fetchUserInfo(ctx context.Context, session *sessionInfo, t *authCachedInfo) (userInfo userInfo, err error) { +func (b *Broker) fetchUserInfo(ctx context.Context, session *sessionInfo, t *authCachedInfo) (userInfo info.User, err error) { defer decorate.OnError(&err, "could not fetch user info") if session.isOffline { - return userInfo, errors.New("session is in offline mode") + return info.User{}, errors.New("session is in offline mode") } - userClaims, err := b.userClaims(ctx, session, t.RawIDToken, session.username) + idToken, err := session.authCfg.provider.Verifier(&b.oidcCfg).Verify(ctx, t.RawIDToken) if err != nil { - return userInfo, err + return info.User{}, fmt.Errorf("could not verify token: %v", err) } - userGroups, err := b.providerInfo.GetGroups(t.Token) + userInfo, err = b.providerInfo.GetUserInfo(ctx, t.Token, idToken) if err != nil { - return userInfo, fmt.Errorf("could not get user groups: %v", err) + return info.User{}, fmt.Errorf("could not get user info: %v", err) } - return b.userInfoFromClaims(userClaims, userGroups), nil -} - -type claims struct { - Name string `json:"name"` - PreferredUserName string `json:"preferred_username"` - Email string `json:"email"` - EmailVerified bool `json:"email_verified"` - Sub string `json:"sub"` -} - -func (b *Broker) userClaims(ctx context.Context, session *sessionInfo, rawIDToken, username string) (userClaims claims, err error) { - // We need to verify the token to get the user claims. - // Due to an internal implementation, the verification process can't be cancelled, so we can't add a timeout for it. - idToken, err := session.authCfg.provider.Verifier(&b.oidcCfg).Verify(ctx, rawIDToken) - if err != nil { - return claims{}, fmt.Errorf("could not verify token: %v", err) - } - - if err := idToken.Claims(&userClaims); err != nil { - return claims{}, fmt.Errorf("could not get user info: %v", err) + // Some providers are case-insensitive, but we are not. So we need to lowercase the returned username. + if !strings.EqualFold(userInfo.Name, session.username) { + return info.User{}, fmt.Errorf("returned user %q does not match the selected one %q", userInfo.Name, session.username) } - if userClaims.Email == "" { - return claims{}, errors.New("user email is required, but was not provided") + // This means that home was not provided by the claims, so we need to set it to the broker default. + if !filepath.IsAbs(userInfo.Home) { + userInfo.Home = filepath.Join(b.homeDirPath, userInfo.Home) } - if userClaims.Email != username { - return claims{}, fmt.Errorf("returned user %q does not match the selected one %q", userClaims.Email, username) - } - - return userClaims, nil -} - -// userInfo represents the user information obtained from the provider. -type userInfo struct { - Name string `json:"name"` - UUID string `json:"uuid"` - Home string `json:"dir"` - Shell string `json:"shell"` - Gecos string `json:"gecos"` - Groups []group.Info `json:"groups"` -} - -func (b *Broker) userInfoFromClaims(userClaims claims, groups []group.Info) userInfo { - return userInfo{ - Name: userClaims.Email, - UUID: userClaims.Sub, - Home: filepath.Join(b.homeDirPath, userClaims.Email), - Shell: "/usr/bin/bash", - Gecos: userClaims.Name, - Groups: groups, - } + return userInfo, err } diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 6815e71ab0..c8d2357836 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -13,7 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ubuntu/authd-oidc-brokers/internal/broker" - "github.com/ubuntu/authd-oidc-brokers/internal/providers/group" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "github.com/ubuntu/authd-oidc-brokers/internal/testutils" "golang.org/x/oauth2" "gopkg.in/yaml.v3" @@ -631,12 +631,13 @@ func TestFetchUserInfo(t *testing.T) { "Successfully fetch user info with groups": {}, "Successfully fetch user info without groups": {emptyGroups: true}, "Successfully fetch user info with default home when not provided": {emptyHomeDir: true}, + "Successfully fetch user info ignoring different casing in name": {userToken: "uppercased-name"}, - "Error when token can not be validated": {userToken: "invalid", wantErr: true}, - "Error when ID token claims are invalid": {userToken: "invalid-id", wantErr: true}, - "Error when user email is not configured": {userToken: "no-email", wantErr: true}, - "Error when user email is different than the requested one": {userToken: "other-email", wantErr: true}, - "Error when getting user groups": {wantGroupErr: true, wantErr: true}, + "Error when token can not be validated": {userToken: "invalid", wantErr: true}, + "Error when ID token claims are invalid": {userToken: "invalid-id", wantErr: true}, + "Error when username is not configured": {userToken: "no-name", wantErr: true}, + "Error when username is different than the requested one": {userToken: "other-name", wantErr: true}, + "Error when getting user groups": {wantGroupErr: true, wantErr: true}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { @@ -656,13 +657,13 @@ func TestFetchUserInfo(t *testing.T) { mockInfoer := &testutils.MockProviderInfoer{ GroupsErr: tc.wantGroupErr, - Groups: []group.Info{ + Groups: []info.Group{ {Name: "remote-group", UGID: "12345"}, {Name: "linux-local-group", UGID: ""}, }, } if tc.emptyGroups { - mockInfoer.Groups = []group.Info{} + mockInfoer.Groups = []info.Group{} } b, err := broker.New(brokerCfg, broker.WithSkipSignatureCheck(), broker.WithCustomProviderInfo(mockInfoer)) diff --git a/internal/broker/export_test.go b/internal/broker/export_test.go index 8524229b43..b69d20c50a 100644 --- a/internal/broker/export_test.go +++ b/internal/broker/export_test.go @@ -5,6 +5,8 @@ import ( "fmt" "os" "path/filepath" + + "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" ) // TokenPathForSession returns the path to the token file for the given session. @@ -94,17 +96,15 @@ func writeTrashToken(path, challenge string) error { } // FetchUserInfo exposes the broker's fetchUserInfo method for tests. -// -//nolint:revive // This is a test helper and the returned userInfo will be marshaled for golden files. -func (b *Broker) FetchUserInfo(sessionID string, cachedInfo *authCachedInfo) (userInfo, error) { +func (b *Broker) FetchUserInfo(sessionID string, cachedInfo *authCachedInfo) (info.User, error) { s, err := b.getSession(sessionID) if err != nil { - return userInfo{}, err + return info.User{}, err } uInfo, err := b.fetchUserInfo(context.TODO(), &s, cachedInfo) if err != nil { - return userInfo{}, err + return info.User{}, err } return uInfo, err @@ -119,8 +119,5 @@ func (b *Broker) IsOffline(sessionID string) (bool, error) { return session.isOffline, nil } -// UserInfo exposes the private userInfo for tests. -type UserInfo = userInfo - // MaxRequestDuration exposes the broker's maxRequestDuration for tests. const MaxRequestDuration = maxRequestDuration diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index 5e9f4737c9..169d12a644 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ubuntu/authd-oidc-brokers/internal/broker" - "github.com/ubuntu/authd-oidc-brokers/internal/providers/group" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "github.com/ubuntu/authd-oidc-brokers/internal/testutils" "golang.org/x/oauth2" ) @@ -36,7 +36,7 @@ func newBrokerForTests(t *testing.T, cfg broker.Config) (b *broker.Broker) { cfg, broker.WithSkipSignatureCheck(), broker.WithCustomProviderInfo(&testutils.MockProviderInfoer{ - Groups: []group.Info{ + Groups: []info.Group{ {Name: "remote-group", UGID: "12345"}, {Name: "linux-local-group", UGID: ""}, }, @@ -126,14 +126,16 @@ func generateCachedInfo(t *testing.T, preexistentToken, issuer string) *broker.A return nil } - var email string + var username string switch preexistentToken { - case "no-email": - email = "" - case "other-email": - email = "other-user@email.com" + case "no-name": + username = "" + case "other-name": + username = "other-user@email.com" + case "uppercased-name": + username = "TEST-USER@EMAIL.COM" default: - email = "test-user@email.com" + username = "test-user@email.com" } idToken := fmt.Sprintf(`{ @@ -142,10 +144,8 @@ func generateCachedInfo(t *testing.T, preexistentToken, issuer string) *broker.A "aud": "test-client-id", "name": "saved-user", "exp": 9999999999, - "preferred_username": "User Saved", - "email": "%s", - "email_verified": true - }`, issuer, email) + "preferred_username": "%s" + }`, issuer, username) encodedToken := fmt.Sprintf(".%s.", base64.RawURLEncoding.EncodeToString([]byte(idToken))) tok, ok := testTokens[preexistentToken] @@ -153,13 +153,13 @@ func generateCachedInfo(t *testing.T, preexistentToken, issuer string) *broker.A tok = testTokens["valid"] } - tok.UserInfo = broker.UserInfo{ - Name: email, + tok.UserInfo = info.User{ + Name: username, UUID: "saved-user-id", - Home: "/home/" + email, - Gecos: "saved-user", + Home: "/home/" + username, + Gecos: username, Shell: "/usr/bin/bash", - Groups: []group.Info{ + Groups: []info.Group{ {Name: "saved-remote-group", UGID: "12345"}, {Name: "saved-local-group", UGID: ""}, }, @@ -167,7 +167,7 @@ func generateCachedInfo(t *testing.T, preexistentToken, issuer string) *broker.A if preexistentToken == "invalid-id" { encodedToken = ".invalid." - tok.UserInfo = broker.UserInfo{} + tok.UserInfo = info.User{} } tok.Token = tok.Token.WithExtra(map[string]string{"id_token": encodedToken}) diff --git a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_ignoring_different_casing_in_name b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_ignoring_different_casing_in_name new file mode 100644 index 0000000000..6b8a8aafc3 --- /dev/null +++ b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_ignoring_different_casing_in_name @@ -0,0 +1,10 @@ +name: TEST-USER@EMAIL.COM +uuid: saved-user-id +home: /home/userInfoTests/TEST-USER@EMAIL.COM +shell: /usr/bin/bash +gecos: TEST-USER@EMAIL.COM +groups: + - name: remote-group + ugid: "12345" + - name: linux-local-group + ugid: "" diff --git a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_default_home_when_not_provided b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_default_home_when_not_provided index 5a6359313b..010bd89ef1 100644 --- a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_default_home_when_not_provided +++ b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_default_home_when_not_provided @@ -2,7 +2,7 @@ name: test-user@email.com uuid: saved-user-id home: /home/test-user@email.com shell: /usr/bin/bash -gecos: saved-user +gecos: test-user@email.com groups: - name: remote-group ugid: "12345" diff --git a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_groups b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_groups index 96d01f3fa6..4c8fefb44a 100644 --- a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_groups +++ b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_groups @@ -2,7 +2,7 @@ name: test-user@email.com uuid: saved-user-id home: /home/userInfoTests/test-user@email.com shell: /usr/bin/bash -gecos: saved-user +gecos: test-user@email.com groups: - name: remote-group ugid: "12345" diff --git a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_without_groups b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_without_groups index 0a0ae71794..2cd470c337 100644 --- a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_without_groups +++ b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_without_groups @@ -2,5 +2,5 @@ name: test-user@email.com uuid: saved-user-id home: /home/userInfoTests/test-user@email.com shell: /usr/bin/bash -gecos: saved-user +gecos: test-user@email.com groups: [] diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/first_call index ba20e9e1f2..3c4c96bc15 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/first_call @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/first_call index 1d0929cfbd..2928c3a331 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/first_call @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"saved-user","groups":[{"name":"saved-remote-group","ugid":"12345"},{"name":"saved-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"saved-remote-group","ugid":"12345"},{"name":"saved-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/first_call index 1d0929cfbd..2928c3a331 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/first_call @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"saved-user","groups":[{"name":"saved-remote-group","ugid":"12345"},{"name":"saved-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"saved-remote-group","ugid":"12345"},{"name":"saved-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/second_call index ba20e9e1f2..3c4c96bc15 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/second_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/second_call @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call index 1d0929cfbd..2928c3a331 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"saved-user","groups":[{"name":"saved-remote-group","ugid":"12345"},{"name":"saved-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"saved-remote-group","ugid":"12345"},{"name":"saved-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/second_call index ba20e9e1f2..3c4c96bc15 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/second_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/second_call @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' err: diff --git a/internal/providers/group/info.go b/internal/providers/group/info.go deleted file mode 100644 index 73afa3b1d6..0000000000 --- a/internal/providers/group/info.go +++ /dev/null @@ -1,8 +0,0 @@ -// Package group defines group-related types used by the broker. -package group - -// Info represents the group information that is fetched by the broker. -type Info struct { - Name string `json:"name"` - UGID string `json:"ugid"` -} diff --git a/internal/providers/info/info.go b/internal/providers/info/info.go new file mode 100644 index 0000000000..b42ca8dbb8 --- /dev/null +++ b/internal/providers/info/info.go @@ -0,0 +1,44 @@ +// Package info defines types used by the broker. +package info + +// Group represents the group information that is fetched by the broker. +type Group struct { + Name string `json:"name"` + UGID string `json:"ugid"` +} + +// User represents the user information obtained from the provider. +type User struct { + Name string `json:"name"` + UUID string `json:"uuid"` + Home string `json:"dir"` + Shell string `json:"shell"` + Gecos string `json:"gecos"` + Groups []Group `json:"groups"` +} + +// NewUser creates a new user with the specified values. +// +// It fills the defaults for Shell and Gecos if they are empty. +func NewUser(name, home, uuid, shell, gecos string, groups []Group) User { + u := User{ + Name: name, + Home: home, + UUID: uuid, + Shell: shell, + Gecos: gecos, + Groups: groups, + } + + if u.Home == "" { + u.Home = u.Name + } + if u.Shell == "" { + u.Shell = "/usr/bin/bash" + } + if u.Gecos == "" { + u.Gecos = u.Name + } + + return u +} diff --git a/internal/providers/info/info_test.go b/internal/providers/info/info_test.go new file mode 100644 index 0000000000..7a65bfe29e --- /dev/null +++ b/internal/providers/info/info_test.go @@ -0,0 +1,82 @@ +package info_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" +) + +func TestNewUser(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + name string + home string + uuid string + shell string + gecos string + groups []info.Group + }{ + "Create a new user": { + name: "test-user", + home: "/home/test-user", + uuid: "some-uuid", + shell: "/usr/bin/zsh", + gecos: "Test User", + groups: []info.Group{{Name: "test-group", UGID: "12345"}}, + }, + + // Default values + "Create a new user with default home": { + name: "test-user", + home: "", + uuid: "some-uuid", + shell: "/usr/bin/zsh", + gecos: "Test User", + groups: []info.Group{{Name: "test-group", UGID: "12345"}}, + }, + "Create a new user with default shell": { + name: "test-user", + home: "/home/test-user", + uuid: "some-uuid", + shell: "", + gecos: "Test User", + groups: []info.Group{{Name: "test-group", UGID: "12345"}}, + }, + "Create a new user with default gecos": {name: "test-user", + home: "/home/test-user", + uuid: "some-uuid", + shell: "/usr/bin/zsh", + gecos: "", + groups: []info.Group{{Name: "test-group", UGID: "12345"}}}, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + wantHome := tc.home + if tc.home == "" { + wantHome = tc.name + } + + wantShell := tc.shell + if tc.shell == "" { + wantShell = "/usr/bin/bash" + } + + wantGecos := tc.gecos + if tc.gecos == "" { + wantGecos = tc.name + } + + got := info.NewUser(tc.name, tc.home, tc.uuid, tc.shell, tc.gecos, tc.groups) + require.Equal(t, tc.name, got.Name, "Name does not match the expected value") + require.Equal(t, wantHome, got.Home, "Home does not match the expected value") + require.Equal(t, tc.uuid, got.UUID, "UUID does not match the expected value") + require.Equal(t, wantShell, got.Shell, "Shell does not match the expected value") + require.Equal(t, wantGecos, got.Gecos, "Gecos does not match the expected value") + require.Equal(t, tc.groups, got.Groups, "Groups do not match the expected value") + }) + } +} diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index be4192c1f5..f81fe47a6c 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -13,10 +13,10 @@ import ( "github.com/coreos/go-oidc/v3/oidc" "github.com/k0kubun/pp" msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go" - msauth "github.com/microsoftgraph/msgraph-sdk-go-core/authentication" + msgraphauth "github.com/microsoftgraph/msgraph-sdk-go-core/authentication" msgraphgroups "github.com/microsoftgraph/msgraph-sdk-go/groups" - "github.com/microsoftgraph/msgraph-sdk-go/models" - "github.com/ubuntu/authd-oidc-brokers/internal/providers/group" + msgraphmodels "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "golang.org/x/oauth2" ) @@ -39,10 +39,49 @@ func (p Provider) AuthOptions() []oauth2.AuthCodeOption { return []oauth2.AuthCodeOption{} } -// GetGroups access the Microsoft Graph API to get the groups the user is a member of. -func (p Provider) GetGroups(token *oauth2.Token) ([]group.Info, error) { +// GetUserInfo is a no-op when no specific provider is in use. +func (p Provider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken) (info.User, error) { + userClaims, err := p.userClaims(idToken) + if err != nil { + return info.User{}, err + } + + userGroups, err := p.getGroups(accessToken) + if err != nil { + return info.User{}, err + } + + return info.NewUser( + userClaims.PreferredUserName, + userClaims.Home, + userClaims.Sub, + userClaims.Shell, + userClaims.Gecos, + userGroups, + ), nil +} + +type claims struct { + PreferredUserName string `json:"preferred_username"` + Sub string `json:"sub"` + Home string `json:"home"` + Shell string `json:"shell"` + Gecos string `json:"gecos"` +} + +// userClaims returns the user claims parsed from the ID token. +func (p Provider) userClaims(idToken *oidc.IDToken) (claims, error) { + var userClaims claims + if err := idToken.Claims(&userClaims); err != nil { + return claims{}, fmt.Errorf("could not get user info: %v", err) + } + return userClaims, nil +} + +// getGroups access the Microsoft Graph API to get the groups the user is a member of. +func (p Provider) getGroups(token *oauth2.Token) ([]info.Group, error) { cred := azureTokenCredential{token: token} - auth, err := msauth.NewAzureIdentityAuthenticationProvider(cred) + auth, err := msgraphauth.NewAzureIdentityAuthenticationProvider(cred) if err != nil { return nil, err } @@ -70,10 +109,10 @@ func (p Provider) GetGroups(token *oauth2.Token) ([]group.Info, error) { return nil, err } - var groups []group.Info + var groups []info.Group for _, obj := range m.GetValue() { unknown := "Unknown" - msGroup, ok := obj.(*models.Group) + msGroup, ok := obj.(*msgraphmodels.Group) if !ok { id, oType := obj.GetId(), obj.GetOdataType() if id == nil { @@ -107,7 +146,7 @@ func (p Provider) GetGroups(token *oauth2.Token) ([]group.Info, error) { // Local group if strings.HasPrefix(groupName, localGroupPrefix) { groupName = strings.TrimPrefix(groupName, localGroupPrefix) - groups = append(groups, group.Info{Name: groupName}) + groups = append(groups, info.Group{Name: groupName}) continue } @@ -121,7 +160,7 @@ func (p Provider) GetGroups(token *oauth2.Token) ([]group.Info, error) { return nil, errors.New("could not parse group id") } - groups = append(groups, group.Info{Name: groupName, UGID: *id}) + groups = append(groups, info.Group{Name: groupName, UGID: *id}) } return groups, nil diff --git a/internal/providers/noprovider/noprovider.go b/internal/providers/noprovider/noprovider.go index 2e02ca0d4e..fc85bfe2f4 100644 --- a/internal/providers/noprovider/noprovider.go +++ b/internal/providers/noprovider/noprovider.go @@ -2,11 +2,12 @@ package noprovider import ( + "context" "errors" "fmt" "github.com/coreos/go-oidc/v3/oidc" - "github.com/ubuntu/authd-oidc-brokers/internal/providers/group" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "golang.org/x/oauth2" ) @@ -23,11 +24,6 @@ func (p NoProvider) AuthOptions() []oauth2.AuthCodeOption { return []oauth2.AuthCodeOption{} } -// GetGroups is a no-op when no specific provider is in use. -func (p NoProvider) GetGroups(_ *oauth2.Token) ([]group.Info, error) { - return nil, nil -} - // CurrentAuthenticationModesOffered returns the generic authentication modes supported by the provider. func (p NoProvider) CurrentAuthenticationModesOffered( sessionMode string, @@ -68,3 +64,47 @@ func (p NoProvider) CurrentAuthenticationModesOffered( return offeredModes, nil } + +// GetUserInfo is a no-op when no specific provider is in use. +func (p NoProvider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken) (info.User, error) { + userClaims, err := p.userClaims(idToken) + if err != nil { + return info.User{}, err + } + + userGroups, err := p.getGroups(accessToken) + if err != nil { + return info.User{}, err + } + + return info.NewUser( + userClaims.PreferredUserName, + userClaims.Home, + userClaims.Sub, + userClaims.Shell, + userClaims.Gecos, + userGroups, + ), nil +} + +type claims struct { + PreferredUserName string `json:"preferred_username"` + Sub string `json:"sub"` + Home string `json:"home"` + Shell string `json:"shell"` + Gecos string `json:"gecos"` +} + +// userClaims returns the user claims parsed from the ID token. +func (p NoProvider) userClaims(idToken *oidc.IDToken) (claims, error) { + var userClaims claims + if err := idToken.Claims(&userClaims); err != nil { + return claims{}, fmt.Errorf("could not get user info: %v", err) + } + return userClaims, nil +} + +// getGroups is a no-op when no specific provider is in use. +func (p NoProvider) getGroups(_ *oauth2.Token) ([]info.Group, error) { + return nil, nil +} diff --git a/internal/providers/providers.go b/internal/providers/providers.go index ab54f8ad4b..94dc58c538 100644 --- a/internal/providers/providers.go +++ b/internal/providers/providers.go @@ -2,7 +2,10 @@ package providers import ( - "github.com/ubuntu/authd-oidc-brokers/internal/providers/group" + "context" + + "github.com/coreos/go-oidc/v3/oidc" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "golang.org/x/oauth2" ) @@ -18,5 +21,5 @@ type ProviderInfoer interface { endpoints map[string]struct{}, currentAuthStep int, ) ([]string, error) - GetGroups(*oauth2.Token) ([]group.Info, error) + GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken) (info.User, error) } diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index 2356ed21fc..29f273a7b7 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -1,6 +1,7 @@ package testutils import ( + "context" "encoding/base64" "errors" "fmt" @@ -10,7 +11,7 @@ import ( "time" "github.com/coreos/go-oidc/v3/oidc" - "github.com/ubuntu/authd-oidc-brokers/internal/providers/group" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "golang.org/x/oauth2" ) @@ -136,8 +137,8 @@ func DefaultTokenHandler(serverURL string) ProviderHandler { "aud": "test-client-id", "exp": 9999999999, "name": "test-user", - "preferred_username": "User Test", - "email": "test-user@email.com", + "preferred_username": "test-user@email.com", + "email": "test-user@anotheremail.com", "email_verified": true }`, serverURL) @@ -218,7 +219,7 @@ func ExpiryDeviceAuthHandler() ProviderHandler { type MockProviderInfoer struct { Scopes []string Options []oauth2.AuthCodeOption - Groups []group.Info + Groups []info.Group GroupsErr bool } @@ -238,8 +239,47 @@ func (p *MockProviderInfoer) AuthOptions() []oauth2.AuthCodeOption { return []oauth2.AuthCodeOption{} } +// GetUserInfo is a no-op when no specific provider is in use. +func (p *MockProviderInfoer) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken) (info.User, error) { + userClaims, err := p.userClaims(idToken) + if err != nil { + return info.User{}, err + } + + userGroups, err := p.getGroups(accessToken) + if err != nil { + return info.User{}, err + } + + return info.NewUser( + userClaims.PreferredUserName, + userClaims.Home, + userClaims.Sub, + userClaims.Shell, + userClaims.Gecos, + userGroups, + ), nil +} + +type claims struct { + PreferredUserName string `json:"preferred_username"` + Sub string `json:"sub"` + Home string `json:"home"` + Shell string `json:"shell"` + Gecos string `json:"gecos"` +} + +// userClaims returns the user claims parsed from the ID token. +func (p *MockProviderInfoer) userClaims(idToken *oidc.IDToken) (claims, error) { + var userClaims claims + if err := idToken.Claims(&userClaims); err != nil { + return claims{}, fmt.Errorf("could not get user info: %v", err) + } + return userClaims, nil +} + // GetGroups returns the groups the user is a member of. -func (p *MockProviderInfoer) GetGroups(*oauth2.Token) ([]group.Info, error) { +func (p *MockProviderInfoer) getGroups(*oauth2.Token) ([]info.Group, error) { if p.GroupsErr { return nil, errors.New("error requested in the mock") } From b512992f872d6bb856e100a52cd435ec79c670fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 09:00:27 +0000 Subject: [PATCH 0111/1670] deps(go): bump golang.org/x/oauth2 from 0.21.0 to 0.22.0 Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.21.0 to 0.22.0. - [Commits](https://github.com/golang/oauth2/compare/v0.21.0...v0.22.0) --- updated-dependencies: - dependency-name: golang.org/x/oauth2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c6518ec589..6f6679f5bf 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 golang.org/x/crypto v0.25.0 golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 - golang.org/x/oauth2 v0.21.0 + golang.org/x/oauth2 v0.22.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 534f9df54a..fa423179a4 100644 --- a/go.sum +++ b/go.sum @@ -140,8 +140,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= From 86a6b87d946dd54a2c059cf49fd2c20d29e9f38e Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Mon, 5 Aug 2024 07:23:56 -0400 Subject: [PATCH 0112/1670] Add tests for concurrent IsAuthenticated calls This is to ensure one session does not interfere with another when happening at the same time on the broker. In order to understand better the tests, run them with the verbosity flag (-v) so that you can see the ordering of the calls. --- internal/broker/broker_test.go | 137 +++++++++++++++++- internal/broker/helper_test.go | 11 ++ .../cache/provider_url/user-timeout-0.cache | 1 + .../cache/provider_url/user-timeout-1.cache | 1 + .../first_auth | 3 + .../second_auth | 3 + .../cache/provider_url/user-timeout-1.cache | 1 + .../cache/provider_url/user-timeout-3.cache | 1 + .../first_auth | 3 + .../second_auth | 3 + .../cache/provider_url/user-timeout-2.cache | 1 + .../cache/provider_url/user-timeout-3.cache | 1 + .../first_auth | 3 + .../second_auth | 3 + internal/testutils/provider.go | 12 ++ 15 files changed, 178 insertions(+), 6 deletions(-) create mode 100644 internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/cache/provider_url/user-timeout-0.cache create mode 100644 internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/cache/provider_url/user-timeout-1.cache create mode 100644 internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/first_auth create mode 100644 internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/second_auth create mode 100644 internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/cache/provider_url/user-timeout-1.cache create mode 100644 internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/cache/provider_url/user-timeout-3.cache create mode 100644 internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/first_auth create mode 100644 internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/second_auth create mode 100644 internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/cache/provider_url/user-timeout-2.cache create mode 100644 internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/cache/provider_url/user-timeout-3.cache create mode 100644 internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/first_auth create mode 100644 internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/second_auth diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index c8d2357836..1caa985f69 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -339,6 +339,12 @@ func TestSelectAuthenticationMode(t *testing.T) { } } +type isAuthenticatedResponse struct { + Access string + Data string + Err string +} + func TestIsAuthenticated(t *testing.T) { t.Parallel() @@ -503,12 +509,6 @@ func TestIsAuthenticated(t *testing.T) { authData = "invalid json" } - type isAuthenticatedResponse struct { - Access string - Data string - Err string - } - firstCallDone := make(chan struct{}) go func() { defer close(firstCallDone) @@ -616,6 +616,131 @@ func TestIsAuthenticated(t *testing.T) { } } +// Due to ordering restrictions, this test can not be run in parallel, otherwise the routines would not be ordered as expected. +func TestConcurrentIsAuthenticated(t *testing.T) { + tests := map[string]struct { + firstUser string + secondUser string + + timeBetween time.Duration + }{ + "First auth starts and finishes before second": {timeBetween: 2 * time.Second}, + "First auth starts first but second finishes first": {firstUser: "user-timeout-3", timeBetween: time.Second}, + "First auth starts first then second starts and first finishes": {firstUser: "user-timeout-2", secondUser: "user-timeout-3", timeBetween: time.Second}, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + outDir := t.TempDir() + cacheDir := filepath.Join(outDir, "cache") + err := os.Mkdir(cacheDir, 0700) + require.NoError(t, err, "Setup: Mkdir should not have returned an error") + b := newBrokerForTests(t, broker.Config{CachePath: cacheDir, IssuerURL: defaultProvider.URL}) + + if tc.firstUser == "" { + tc.firstUser = "user-timeout-0" + } + if tc.secondUser == "" { + tc.secondUser = "user-timeout-1" + } + + firstSession, firstKey := newSessionForTests(t, b, tc.firstUser, "") + tok := generateCachedInfo(t, tc.firstUser, defaultProvider.URL) + err = b.CacheAuthInfo(firstSession, tok, "password") + require.NoError(t, err, "Setup: SaveToken should not have returned an error") + + secondSession, secondKey := newSessionForTests(t, b, tc.secondUser, "") + tok = generateCachedInfo(t, tc.secondUser, defaultProvider.URL) + err = b.CacheAuthInfo(secondSession, tok, "password") + require.NoError(t, err, "Setup: SaveToken should not have returned an error") + + firstCallDone := make(chan struct{}) + //nolint:dupl // This is not a duplicate, just a very similar set of calls. + go func() { + t.Logf("%s: First auth starting", t.Name()) + defer close(firstCallDone) + + updateAuthModes(t, b, firstSession, "password") + + authData := `{"challenge":"` + encryptChallenge(t, "password", firstKey) + `"}` + + access, data, err := b.IsAuthenticated(firstSession, authData) + require.True(t, json.Valid([]byte(data)), "IsAuthenticated returned data must be a valid JSON") + + // Redact variant values from the response + data = strings.ReplaceAll(data, firstSession, "SESSION_ID") + data = strings.ReplaceAll(data, filepath.Dir(b.TokenPathForSession(firstSession)), "provider_cache_path") + data = strings.ReplaceAll(data, defaultProvider.URL, "provider_url") + errStr := strings.ReplaceAll(fmt.Sprintf("%v", err), firstSession, "SESSION_ID") + + got := isAuthenticatedResponse{Access: access, Data: data, Err: errStr} + + out, err := yaml.Marshal(got) + require.NoError(t, err, "Failed to marshal first response") + + err = os.WriteFile(filepath.Join(outDir, "first_auth"), out, 0600) + require.NoError(t, err, "Failed to write first response") + + t.Logf("%s: First auth done", t.Name()) + }() + + time.Sleep(tc.timeBetween) + + secondCallDone := make(chan struct{}) + //nolint:dupl // This is not a duplicate, just a very similar set of calls. + go func() { + t.Logf("%s: Second auth starting", t.Name()) + defer close(secondCallDone) + + updateAuthModes(t, b, secondSession, "password") + + authData := `{"challenge":"` + encryptChallenge(t, "password", secondKey) + `"}` + + access, data, err := b.IsAuthenticated(secondSession, authData) + require.True(t, json.Valid([]byte(data)), "IsAuthenticated returned data must be a valid JSON") + + // Redact variant values from the response + data = strings.ReplaceAll(data, secondSession, "SESSION_ID") + data = strings.ReplaceAll(data, filepath.Dir(b.TokenPathForSession(secondSession)), "provider_cache_path") + data = strings.ReplaceAll(data, defaultProvider.URL, "provider_url") + errStr := strings.ReplaceAll(fmt.Sprintf("%v", err), secondSession, "SESSION_ID") + + got := isAuthenticatedResponse{Access: access, Data: data, Err: errStr} + + out, err := yaml.Marshal(got) + require.NoError(t, err, "Failed to marshal second response") + + err = os.WriteFile(filepath.Join(outDir, "second_auth"), out, 0600) + require.NoError(t, err, "Failed to write second response") + + t.Logf("%s: Second auth done", t.Name()) + }() + + <-firstCallDone + <-secondCallDone + + for _, sessionID := range []string{firstSession, secondSession} { + // Ensure that the token content is generic to avoid golden file conflicts + if _, err := os.Stat(b.TokenPathForSession(sessionID)); err == nil { + err := os.WriteFile(b.TokenPathForSession(sessionID), []byte("Definitely an encrypted token"), 0600) + require.NoError(t, err, "Teardown: Failed to write generic token file") + } + } + + // Ensure that the directory structure is generic to avoid golden file conflicts + if _, err := os.Stat(filepath.Dir(b.TokenPathForSession(firstSession))); err == nil { + toReplace := strings.ReplaceAll(strings.TrimPrefix(defaultProvider.URL, "http://"), ":", "_") + providerCache := filepath.Dir(b.TokenPathForSession(firstSession)) + newProviderCache := strings.ReplaceAll(providerCache, toReplace, "provider_url") + err := os.Rename(providerCache, newProviderCache) + if err != nil { + require.ErrorIs(t, err, os.ErrNotExist, "Teardown: Failed to rename cache directory") + } + } + testutils.CompareTreesWithFiltering(t, outDir, testutils.GoldenPath(t), testutils.Update()) + }) + } +} + func TestFetchUserInfo(t *testing.T) { t.Parallel() diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index 169d12a644..d6c0abde4d 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -7,6 +7,7 @@ import ( "crypto/x509" "encoding/base64" "fmt" + "strings" "testing" "time" @@ -138,6 +139,11 @@ func generateCachedInfo(t *testing.T, preexistentToken, issuer string) *broker.A username = "test-user@email.com" } + // This is to handle delay cases where we need to control the authentication ordering. + if strings.HasPrefix(preexistentToken, "user-timeout-") { + username = preexistentToken + } + idToken := fmt.Sprintf(`{ "iss": "%s", "sub": "saved-user-id", @@ -165,6 +171,11 @@ func generateCachedInfo(t *testing.T, preexistentToken, issuer string) *broker.A }, } + // This is to force the broker to query the provider for the user info. + if strings.HasPrefix(preexistentToken, "user-timeout-") { + tok.UserInfo = info.User{} + } + if preexistentToken == "invalid-id" { encodedToken = ".invalid." tok.UserInfo = info.User{} diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/cache/provider_url/user-timeout-0.cache b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/cache/provider_url/user-timeout-0.cache new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/cache/provider_url/user-timeout-0.cache @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/cache/provider_url/user-timeout-1.cache b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/cache/provider_url/user-timeout-1.cache new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/cache/provider_url/user-timeout-1.cache @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/first_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/first_auth new file mode 100644 index 0000000000..bd6241b176 --- /dev/null +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/first_auth @@ -0,0 +1,3 @@ +access: granted +data: '{"userinfo":{"name":"user-timeout-0","uuid":"saved-user-id","dir":"/home/user-timeout-0","shell":"/usr/bin/bash","gecos":"user-timeout-0","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/second_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/second_auth new file mode 100644 index 0000000000..1fee6db9d3 --- /dev/null +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/second_auth @@ -0,0 +1,3 @@ +access: granted +data: '{"userinfo":{"name":"user-timeout-1","uuid":"saved-user-id","dir":"/home/user-timeout-1","shell":"/usr/bin/bash","gecos":"user-timeout-1","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/cache/provider_url/user-timeout-1.cache b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/cache/provider_url/user-timeout-1.cache new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/cache/provider_url/user-timeout-1.cache @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/cache/provider_url/user-timeout-3.cache b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/cache/provider_url/user-timeout-3.cache new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/cache/provider_url/user-timeout-3.cache @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/first_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/first_auth new file mode 100644 index 0000000000..d4235f545a --- /dev/null +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/first_auth @@ -0,0 +1,3 @@ +access: granted +data: '{"userinfo":{"name":"user-timeout-3","uuid":"saved-user-id","dir":"/home/user-timeout-3","shell":"/usr/bin/bash","gecos":"user-timeout-3","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/second_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/second_auth new file mode 100644 index 0000000000..1fee6db9d3 --- /dev/null +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/second_auth @@ -0,0 +1,3 @@ +access: granted +data: '{"userinfo":{"name":"user-timeout-1","uuid":"saved-user-id","dir":"/home/user-timeout-1","shell":"/usr/bin/bash","gecos":"user-timeout-1","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/cache/provider_url/user-timeout-2.cache b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/cache/provider_url/user-timeout-2.cache new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/cache/provider_url/user-timeout-2.cache @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/cache/provider_url/user-timeout-3.cache b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/cache/provider_url/user-timeout-3.cache new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/cache/provider_url/user-timeout-3.cache @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/first_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/first_auth new file mode 100644 index 0000000000..e70dc9da68 --- /dev/null +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/first_auth @@ -0,0 +1,3 @@ +access: granted +data: '{"userinfo":{"name":"user-timeout-2","uuid":"saved-user-id","dir":"/home/user-timeout-2","shell":"/usr/bin/bash","gecos":"user-timeout-2","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/second_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/second_auth new file mode 100644 index 0000000000..d4235f545a --- /dev/null +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/second_auth @@ -0,0 +1,3 @@ +access: granted +data: '{"userinfo":{"name":"user-timeout-3","uuid":"saved-user-id","dir":"/home/user-timeout-3","shell":"/usr/bin/bash","gecos":"user-timeout-3","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +err: diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index 29f273a7b7..6e005c8abf 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -8,6 +8,8 @@ import ( "net" "net/http" "net/http/httptest" + "strconv" + "strings" "time" "github.com/coreos/go-oidc/v3/oidc" @@ -251,6 +253,16 @@ func (p *MockProviderInfoer) GetUserInfo(ctx context.Context, accessToken *oauth return info.User{}, err } + // This is a special case for testing purposes. If the username starts with "user-timeout-", we will delay the + // return for a while to control the authentication order for multiple users. + if strings.HasPrefix(userClaims.PreferredUserName, "user-timeout") { + d, err := strconv.Atoi(strings.TrimPrefix(userClaims.PreferredUserName, "user-timeout-")) + if err != nil { + return info.User{}, err + } + time.Sleep(time.Duration(d) * time.Second) + } + return info.NewUser( userClaims.PreferredUserName, userClaims.Home, From cb46abbfe6d09d42b17d8fd62cd7e8b860aa397a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 08:33:58 +0000 Subject: [PATCH 0113/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.46.0 to 1.47.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.46.0...v1.47.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6f6679f5bf..c3c00abed8 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/godbus/dbus/v5 v5.1.0 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.46.0 + github.com/microsoftgraph/msgraph-sdk-go v1.47.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.2.0 github.com/otiai10/copy v1.14.0 github.com/spf13/cobra v1.8.1 diff --git a/go.sum b/go.sum index fa423179a4..f5a36f2cf4 100644 --- a/go.sum +++ b/go.sum @@ -65,8 +65,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.0.0 h1:3O5sb5Zj+moLBiJy github.com/microsoft/kiota-serialization-multipart-go v1.0.0/go.mod h1:yauLeBTpANk4L03XD985akNysG24SnRJGaveZf+p4so= github.com/microsoft/kiota-serialization-text-go v1.0.0 h1:XOaRhAXy+g8ZVpcq7x7a0jlETWnWrEum0RhmbYrTFnA= github.com/microsoft/kiota-serialization-text-go v1.0.0/go.mod h1:sM1/C6ecnQ7IquQOGUrUldaO5wj+9+v7G2W3sQ3fy6M= -github.com/microsoftgraph/msgraph-sdk-go v1.46.0 h1:5AT0FZkPFTiLZ9nZym1TLUFlcHFVhAAvqNb9B52Xvn0= -github.com/microsoftgraph/msgraph-sdk-go v1.46.0/go.mod h1:Rf2aiCdmTvY325XfhxnUiRIuIyj66SYdszaovW091S0= +github.com/microsoftgraph/msgraph-sdk-go v1.47.0 h1:qXfmDij9md6mPsSAJjiDNmS4hxqKo0R489GiVMZVmmY= +github.com/microsoftgraph/msgraph-sdk-go v1.47.0/go.mod h1:Gnws5D7d/930uS9J4qlCm4BAR/zenqECMk9tgMDXeZQ= github.com/microsoftgraph/msgraph-sdk-go-core v1.2.0 h1:vHvUKVnk0FkLSJv+aUvxIsM5kLNqWlu/WzUM8S4XOy4= github.com/microsoftgraph/msgraph-sdk-go-core v1.2.0/go.mod h1:armKxoJybX70GBpd748K2e+1AySPYRbhvYW7EBplujw= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= From 3b3ad8595c032aa0abcc00f002018c212304544b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 08:34:04 +0000 Subject: [PATCH 0114/1670] deps(go): bump golang.org/x/crypto from 0.25.0 to 0.26.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.25.0 to 0.26.0. - [Commits](https://github.com/golang/crypto/compare/v0.25.0...v0.26.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 6f6679f5bf..648a2fd98e 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/stretchr/testify v1.9.0 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 - golang.org/x/crypto v0.25.0 + golang.org/x/crypto v0.26.0 golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 golang.org/x/oauth2 v0.22.0 gopkg.in/ini.v1 v1.67.0 @@ -65,7 +65,7 @@ require ( go.opentelemetry.io/otel/trace v1.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect ) diff --git a/go.sum b/go.sum index fa423179a4..a28ed228e7 100644 --- a/go.sum +++ b/go.sum @@ -130,8 +130,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -144,8 +144,8 @@ golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -153,16 +153,16 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc 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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From 3ad61954ff5d336f4ba982c32f7df2c35590c0ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 08:34:12 +0000 Subject: [PATCH 0115/1670] deps(go): bump github.com/Azure/azure-sdk-for-go/sdk/azcore Bumps [github.com/Azure/azure-sdk-for-go/sdk/azcore](https://github.com/Azure/azure-sdk-for-go) from 1.13.0 to 1.14.0. - [Release notes](https://github.com/Azure/azure-sdk-for-go/releases) - [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/release.md) - [Commits](https://github.com/Azure/azure-sdk-for-go/compare/sdk/azcore/v1.13.0...sdk/azcore/v1.14.0) --- updated-dependencies: - dependency-name: github.com/Azure/azure-sdk-for-go/sdk/azcore dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6f6679f5bf..67134f7751 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.0 toolchain go1.22.5 require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 github.com/coreos/go-oidc/v3 v3.11.0 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/godbus/dbus/v5 v5.1.0 diff --git a/go.sum b/go.sum index fa423179a4..c740e5ff03 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 h1:GJHeeA2N7xrG3q30L2UXDyuWRzDM900/65j70wcM4Ww= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= github.com/cjlapao/common-go v0.0.39 h1:bAAUrj2B9v0kMzbAOhzjSmiyDy+rd56r2sy7oEiQLlA= From 64841d12cca8b77ddf1126e75952069e1c10c40b Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 15 Aug 2024 12:09:10 +0200 Subject: [PATCH 0116/1670] Fix nil pointer dereference Handle the case that `TransitiveMemberOf().Get()` returns `nil, nil`. UDENG-3908 --- internal/providers/msentraid/msentraid.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index f81fe47a6c..cc18894432 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -108,6 +108,10 @@ func (p Provider) getGroups(token *oauth2.Token) ([]info.Group, error) { if err != nil { return nil, err } + if m == nil { + slog.Debug("Got nil response from Microsoft Graph API for user's groups, assuming that user is not a member of any group.") + return []info.Group{}, nil + } var groups []info.Group for _, obj := range m.GetValue() { From 3731a54a53af3fa9f860f6ac3ddf91aec0e16763 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Aug 2024 09:05:01 +0000 Subject: [PATCH 0117/1670] deps(go-tools): bump github.com/golangci/golangci-lint in /tools Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.59.1 to 1.60.1. - [Release notes](https://github.com/golangci/golangci-lint/releases) - [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md) - [Commits](https://github.com/golangci/golangci-lint/compare/v1.59.1...v1.60.1) --- updated-dependencies: - dependency-name: github.com/golangci/golangci-lint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- tools/go.mod | 48 +++++++++++------------- tools/go.sum | 104 ++++++++++++++++++++++++--------------------------- 2 files changed, 71 insertions(+), 81 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index 3e03a64d3f..d8d0c2b289 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -1,10 +1,9 @@ module github.com/ubuntu/authd-oidc-brokers/tools go 1.22.0 - toolchain go1.22.5 -require github.com/golangci/golangci-lint v1.59.1 +require github.com/golangci/golangci-lint v1.60.1 require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect @@ -13,11 +12,11 @@ require ( github.com/Abirdcfly/dupword v0.0.14 // indirect github.com/Antonboom/errname v0.1.13 // indirect github.com/Antonboom/nilnil v0.1.9 // indirect - github.com/Antonboom/testifylint v1.3.1 // indirect - github.com/BurntSushi/toml v1.4.0 // indirect - github.com/Crocmagnon/fatcontext v0.2.2 // indirect + github.com/Antonboom/testifylint v1.4.3 // indirect + github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect + github.com/Crocmagnon/fatcontext v0.4.0 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect - github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 // indirect + github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect github.com/alecthomas/go-check-sumtype v0.1.4 // indirect @@ -29,7 +28,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bkielbasa/cyclop v1.2.1 // indirect github.com/blizzy78/varnamelen v0.8.0 // indirect - github.com/bombsimon/wsl/v4 v4.2.1 // indirect + github.com/bombsimon/wsl/v4 v4.4.1 // indirect github.com/breml/bidichk v0.2.7 // indirect github.com/breml/errchkjson v0.3.6 // indirect github.com/butuzov/ireturn v0.3.0 // indirect @@ -62,7 +61,7 @@ require ( github.com/go-viper/mapstructure/v2 v2.0.0 // indirect github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect github.com/gobwas/glob v0.2.3 // indirect - github.com/gofrs/flock v0.8.1 // indirect + github.com/gofrs/flock v0.12.1 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e // indirect @@ -84,7 +83,7 @@ require ( github.com/jgautheron/goconst v1.7.1 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect - github.com/jjti/go-spancheck v0.6.1 // indirect + github.com/jjti/go-spancheck v0.6.2 // indirect github.com/julz/importas v0.1.0 // indirect github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect github.com/kisielk/errcheck v1.7.0 // indirect @@ -106,12 +105,11 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mgechev/revive v1.3.7 // indirect + github.com/mgechev/revive v1.3.9 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/moricho/tparallel v0.3.1 // indirect + github.com/moricho/tparallel v0.3.2 // indirect github.com/nakabonne/nestif v0.3.1 // indirect - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect github.com/nunnatsa/ginkgolinter v0.16.2 // indirect @@ -119,7 +117,7 @@ require ( github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/polyfloyd/go-errorlint v1.5.2 // indirect + github.com/polyfloyd/go-errorlint v1.6.0 // indirect github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect @@ -129,22 +127,22 @@ require ( github.com/quasilyte/gogrep v0.5.0 // indirect github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect - github.com/ryancurrah/gomodguard v1.3.2 // indirect + github.com/ryancurrah/gomodguard v1.3.3 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect - github.com/sashamelentyev/usestdlibvars v1.26.0 // indirect + github.com/sashamelentyev/usestdlibvars v1.27.0 // indirect github.com/securego/gosec/v2 v2.20.1-0.20240525090044-5f0084eb01a9 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect - github.com/sivchari/tenv v1.7.1 // indirect + github.com/sivchari/tenv v1.10.0 // indirect github.com/sonatard/noctx v0.0.2 // indirect github.com/sourcegraph/go-diff v0.7.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/cobra v1.7.0 // indirect + github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.12.0 // indirect @@ -153,7 +151,6 @@ require ( github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/testify v1.9.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect - github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c // indirect github.com/tdakkota/asciicheck v0.2.0 // indirect github.com/tetafro/godot v1.4.16 // indirect github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect @@ -162,31 +159,30 @@ require ( github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect github.com/ultraware/funlen v0.1.0 // indirect github.com/ultraware/whitespace v0.1.1 // indirect - github.com/uudashr/gocognit v1.1.2 // indirect + github.com/uudashr/gocognit v1.1.3 // indirect github.com/xen0n/gosmopolitan v1.2.2 // indirect github.com/yagipy/maintidx v1.0.0 // indirect github.com/yeya24/promlinter v0.3.0 // indirect github.com/ykadowak/zerologlint v0.1.5 // indirect gitlab.com/bosi/decorder v0.4.2 // indirect go-simpler.org/musttag v0.12.2 // indirect - go-simpler.org/sloglint v0.7.1 // indirect + go-simpler.org/sloglint v0.7.2 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/automaxprocs v1.5.3 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect - golang.org/x/mod v0.18.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.21.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.23.0 // indirect golang.org/x/text v0.15.0 // indirect - golang.org/x/tools v0.22.0 // indirect + golang.org/x/tools v0.24.0 // indirect google.golang.org/protobuf v1.33.0 // indirect - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - honnef.co/go/tools v0.4.7 // indirect + honnef.co/go/tools v0.5.0 // indirect mvdan.cc/gofumpt v0.6.0 // indirect mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect ) diff --git a/tools/go.sum b/tools/go.sum index d5558afe58..81ac837888 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -43,18 +43,18 @@ github.com/Antonboom/errname v0.1.13 h1:JHICqsewj/fNckzrfVSe+T33svwQxmjC+1ntDsHO github.com/Antonboom/errname v0.1.13/go.mod h1:uWyefRYRN54lBg6HseYCFhs6Qjcy41Y3Jl/dVhA87Ns= github.com/Antonboom/nilnil v0.1.9 h1:eKFMejSxPSA9eLSensFmjW2XTgTwJMjZ8hUHtV4s/SQ= github.com/Antonboom/nilnil v0.1.9/go.mod h1:iGe2rYwCq5/Me1khrysB4nwI7swQvjclR8/YRPl5ihQ= -github.com/Antonboom/testifylint v1.3.1 h1:Uam4q1Q+2b6H7gvk9RQFw6jyVDdpzIirFOOrbs14eG4= -github.com/Antonboom/testifylint v1.3.1/go.mod h1:NV0hTlteCkViPW9mSR4wEMfwp+Hs1T3dY60bkvSfhpM= +github.com/Antonboom/testifylint v1.4.3 h1:ohMt6AHuHgttaQ1xb6SSnxCeK4/rnK7KKzbvs7DmEck= +github.com/Antonboom/testifylint v1.4.3/go.mod h1:+8Q9+AOLsz5ZiQiiYujJKs9mNz398+M6UgslP4qgJLA= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= -github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Crocmagnon/fatcontext v0.2.2 h1:OrFlsDdOj9hW/oBEJBNSuH7QWf+E9WPVHw+x52bXVbk= -github.com/Crocmagnon/fatcontext v0.2.2/go.mod h1:WSn/c/+MMNiD8Pri0ahRj0o9jVpeowzavOQplBJw6u0= +github.com/Crocmagnon/fatcontext v0.4.0 h1:4ykozu23YHA0JB6+thiuEv7iT6xq995qS1vcuWZq0tg= +github.com/Crocmagnon/fatcontext v0.4.0/go.mod h1:ZtWrXkgyfsYPzS6K3O88va6t2GEglG93vnII/F94WC0= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 h1:sATXp1x6/axKxz2Gjxv8MALP0bXaNRfQinEwyfMcx8c= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0/go.mod h1:Nl76DrGNJTA1KJ0LePKBw/vznBX1EHbAZX8mwjR82nI= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 h1:/fTUt5vmbkAcMBt4YQiuC23cV0kEsN1MVMNqeOW43cU= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0/go.mod h1:ONJg5sxcbsdQQ4pOW8TGdTidT2TMAUy/2Xhr8mrYaao= github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= @@ -90,8 +90,8 @@ github.com/bkielbasa/cyclop v1.2.1 h1:AeF71HZDob1P2/pRm1so9cd1alZnrpyc4q2uP2l0gJ github.com/bkielbasa/cyclop v1.2.1/go.mod h1:K/dT/M0FPAiYjBgQGau7tz+3TMh4FWAEqlMhzFWCrgM= github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= -github.com/bombsimon/wsl/v4 v4.2.1 h1:Cxg6u+XDWff75SIFFmNsqnIOgob+Q9hG6y/ioKbRFiM= -github.com/bombsimon/wsl/v4 v4.2.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo= +github.com/bombsimon/wsl/v4 v4.4.1 h1:jfUaCkN+aUpobrMO24zwyAMwMAV5eSziCkOKEauOLdw= +github.com/bombsimon/wsl/v4 v4.4.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo= github.com/breml/bidichk v0.2.7 h1:dAkKQPLl/Qrk7hnP6P+E0xOodrq8Us7+U0o4UBOAlQY= github.com/breml/bidichk v0.2.7/go.mod h1:YodjipAGI9fGcYM7II6wFvGhdMYsC5pHDlGzqvEW3tQ= github.com/breml/errchkjson v0.3.6 h1:VLhVkqSBH96AvXEyclMR37rZslRrY2kcyq+31HCsVrA= @@ -119,7 +119,7 @@ github.com/ckaznocha/intrange v0.1.2 h1:3Y4JAxcMntgb/wABQ6e8Q8leMd26JbX2790lIss9 github.com/ckaznocha/intrange v0.1.2/go.mod h1:RWffCw/vKBwHeOEwWdCikAtY0q4gGt8VhJZEEA5n+RE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= github.com/daixiang0/gci v0.13.4 h1:61UGkmpoAcxHM2hhNkZEf5SzwQtWJXTSws7jaPyqwlw= @@ -190,8 +190,8 @@ github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80 github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= -github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= +github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -226,8 +226,8 @@ github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9 github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e h1:ULcKCDV1LOZPFxGZaA6TlQbiM3J2GCPnkx/bGF6sX/g= github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e/go.mod h1:Pm5KhLPA8gSnQwrQ6ukebRcapGb/BG9iUkdaiCcGHJM= -github.com/golangci/golangci-lint v1.59.1 h1:CRRLu1JbhK5avLABFJ/OHVSQ0Ie5c4ulsOId1h3TTks= -github.com/golangci/golangci-lint v1.59.1/go.mod h1:jX5Oif4C7P0j9++YB2MMJmoNrb01NJ8ITqKWNLewThg= +github.com/golangci/golangci-lint v1.60.1 h1:DRKNqNTQRLBJZ1il5u4fvgLQCjQc7QFs0DbhksJtVJE= +github.com/golangci/golangci-lint v1.60.1/go.mod h1:jDIPN1rYaIA+ijp9OZcUmUCoQOtZ76pOlFbi15FlLJY= github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= github.com/golangci/modinfo v0.3.4 h1:oU5huX3fbxqQXdfspamej74DFX0kyGLkw1ppvXoJ8GA= @@ -301,8 +301,8 @@ github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjz github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af h1:KA9BjwUk7KlCh6S9EAGWBt1oExIUv9WyNCiRz5amv48= github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= -github.com/jjti/go-spancheck v0.6.1 h1:ZK/wE5Kyi1VX3PJpUO2oEgeoI4FWOUm7Shb2Gbv5obI= -github.com/jjti/go-spancheck v0.6.1/go.mod h1:vF1QkOO159prdo6mHRxak2CpzDpHAfKiPUDP/NeRnX8= +github.com/jjti/go-spancheck v0.6.2 h1:iYtoxqPMzHUPp7St+5yA8+cONdyXD3ug6KK15n7Pklk= +github.com/jjti/go-spancheck v0.6.2/go.mod h1:+X7lvIrR5ZdUTkxFYqzJ0abr8Sb5LOo80uOhWNqIrYA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -368,8 +368,8 @@ github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/Qd github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mgechev/revive v1.3.7 h1:502QY0vQGe9KtYJ9FpxMz9rL+Fc/P13CI5POL4uHCcE= -github.com/mgechev/revive v1.3.7/go.mod h1:RJ16jUbF0OWC3co/+XTxmFNgEpUPwnnA0BRllX2aDNA= +github.com/mgechev/revive v1.3.9 h1:18Y3R4a2USSBF+QZKFQwVkBROUda7uoBlkEuBD+YD1A= +github.com/mgechev/revive v1.3.9/go.mod h1:+uxEIr5UH0TjXWHTno3xh4u7eg6jDpXKzQccA9UGhHU= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -379,14 +379,12 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/moricho/tparallel v0.3.1 h1:fQKD4U1wRMAYNngDonW5XupoB/ZGJHdpzrWqgyg9krA= -github.com/moricho/tparallel v0.3.1/go.mod h1:leENX2cUv7Sv2qDgdi0D0fCftN8fRC67Bcn8pqzeYNI= +github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= +github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= 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/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= @@ -416,8 +414,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polyfloyd/go-errorlint v1.5.2 h1:SJhVik3Umsjh7mte1vE0fVZ5T1gznasQG3PV7U5xFdA= -github.com/polyfloyd/go-errorlint v1.5.2/go.mod h1:sH1QC1pxxi0fFecsVIzBmxtrgd9IF/SkJpA6wqyKAJs= +github.com/polyfloyd/go-errorlint v1.6.0 h1:tftWV9DE7txiFzPpztTAwyoRLKNj9gpVm2cg8/OwcYY= +github.com/polyfloyd/go-errorlint v1.6.0/go.mod h1:HR7u8wuP1kb1NeN1zqTd1ZMlqUKPPHF+Id4vIPvDqVw= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -456,8 +454,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryancurrah/gomodguard v1.3.2 h1:CuG27ulzEB1Gu5Dk5gP8PFxSOZ3ptSdP5iI/3IXxM18= -github.com/ryancurrah/gomodguard v1.3.2/go.mod h1:LqdemiFomEjcxOqirbQCb3JFvSxH2JUYMerTFd3sF2o= +github.com/ryancurrah/gomodguard v1.3.3 h1:eiSQdJVNr9KTNxY2Niij8UReSwR8Xrte3exBrAZfqpg= +github.com/ryancurrah/gomodguard v1.3.3/go.mod h1:rsKQjj4l3LXe8N344Ow7agAy5p9yjsWOtRzUMYmA0QY= github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/NsJFwFK7Uxc= @@ -466,8 +464,8 @@ github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6Ng github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= -github.com/sashamelentyev/usestdlibvars v1.26.0 h1:LONR2hNVKxRmzIrZR0PhSF3mhCAzvnr+DcUiHgREfXE= -github.com/sashamelentyev/usestdlibvars v1.26.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= +github.com/sashamelentyev/usestdlibvars v1.27.0 h1:t/3jZpSXtRPRf2xr0m63i32ZrusyurIGT9E5wAvXQnI= +github.com/sashamelentyev/usestdlibvars v1.27.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= github.com/securego/gosec/v2 v2.20.1-0.20240525090044-5f0084eb01a9 h1:rnO6Zp1YMQwv8AyxzuwsVohljJgp4L0ZqiCgtACsPsc= github.com/securego/gosec/v2 v2.20.1-0.20240525090044-5f0084eb01a9/go.mod h1:dg7lPlu/xK/Ut9SedURCoZbVCR4yC7fM65DtH9/CDHs= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= @@ -481,8 +479,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= -github.com/sivchari/tenv v1.7.1 h1:PSpuD4bu6fSmtWMxSGWcvqUUgIn7k3yOJhOIzVWn8Ak= -github.com/sivchari/tenv v1.7.1/go.mod h1:64yStXKSOxDfX47NlhVwND4dHwfZDdbp2Lyl018Icvg= +github.com/sivchari/tenv v1.10.0 h1:g/hzMA+dBCKqGXgW8AV/1xIWhAvDrx0zFKNR48NFMg0= +github.com/sivchari/tenv v1.10.0/go.mod h1:tdY24masnVoZFxYrHv/nD6Tc8FbkEtAQEEziXpyMgqY= github.com/sonatard/noctx v0.0.2 h1:L7Dz4De2zDQhW8S0t+KUjY0MAQJd6SgVwhzNIc4ok00= github.com/sonatard/noctx v0.0.2/go.mod h1:kzFz+CzWSjQ2OzIm46uJZoXuBpa2+0y3T36U18dWqIo= github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= @@ -491,8 +489,8 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -521,8 +519,6 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c h1:+aPplBwWcHBo6q9xrfWdMrT9o4kltkmmvpemgIjep/8= -github.com/t-yuki/gocover-cobertura v0.0.0-20180217150009-aaee18c8195c/go.mod h1:SbErYREK7xXdsRiigaQiQkI9McGRzYMvlKYaP3Nimdk= github.com/tdakkota/asciicheck v0.2.0 h1:o8jvnUANo0qXtnslk2d3nMKTFNlOnJjRrNcj0j9qkHM= github.com/tdakkota/asciicheck v0.2.0/go.mod h1:Qb7Y9EgjCLJGup51gDHFzbI08/gbGhL/UVhYIPWG2rg= github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= @@ -543,8 +539,8 @@ github.com/ultraware/funlen v0.1.0 h1:BuqclbkY6pO+cvxoq7OsktIXZpgBSkYTQtmwhAK81v github.com/ultraware/funlen v0.1.0/go.mod h1:XJqmOQja6DpxarLj6Jj1U7JuoS8PvL4nEqDaQhy22p4= github.com/ultraware/whitespace v0.1.1 h1:bTPOGejYFulW3PkcrqkeQwOd6NKOOXvmGD9bo/Gk8VQ= github.com/ultraware/whitespace v0.1.1/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= -github.com/uudashr/gocognit v1.1.2 h1:l6BAEKJqQH2UpKAPKdMfZf5kE4W/2xk8pfU1OVLvniI= -github.com/uudashr/gocognit v1.1.2/go.mod h1:aAVdLURqcanke8h3vg35BC++eseDm66Z7KmchI5et4k= +github.com/uudashr/gocognit v1.1.3 h1:l+a111VcDbKfynh+airAy/DJQKaXh2m9vkoysMPSZyM= +github.com/uudashr/gocognit v1.1.3/go.mod h1:aKH8/e8xbTRBwjbCkwZ8qt4l2EpKXl31KMHgSS+lZ2U= github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= @@ -566,8 +562,8 @@ go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= go-simpler.org/musttag v0.12.2 h1:J7lRc2ysXOq7eM8rwaTYnNrHd5JwjppzB6mScysB2Cs= go-simpler.org/musttag v0.12.2/go.mod h1:uN1DVIasMTQKk6XSik7yrJoEysGtR2GRqvWnI9S7TYM= -go-simpler.org/sloglint v0.7.1 h1:qlGLiqHbN5islOxjeLXoPtUdZXb669RW+BDQ+xOSNoU= -go-simpler.org/sloglint v0.7.1/go.mod h1:OlaVDRh/FKKd4X4sIMbsz8st97vomydceL146Fthh/c= +go-simpler.org/sloglint v0.7.2 h1:Wc9Em/Zeuu7JYpl+oKoYOsQSy2X560aVueCW/m6IijY= +go-simpler.org/sloglint v0.7.2/go.mod h1:US+9C80ppl7VsThQclkM7BkCHQAzuz8kHLsW3ppuluo= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -635,8 +631,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -675,8 +671,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= 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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -696,8 +692,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -743,7 +739,6 @@ golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/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-20220702020025-31831981b65f/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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -752,8 +747,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -830,14 +825,13 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -923,8 +917,8 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -945,8 +939,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.4.7 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs= -honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= +honnef.co/go/tools v0.5.0 h1:29uoiIormS3Z6R+t56STz/oI4v+mB51TSmEOdJPgRnE= +honnef.co/go/tools v0.5.0/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo= mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA= mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f h1:lMpcwN6GxNbWtbpI1+xzFLSW8XzX0u72NttUGVFjO3U= From cad771406f31b487e5fb9db1ff4029c80ed38a62 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 19 Aug 2024 12:52:40 +0200 Subject: [PATCH 0118/1670] Fix TestMain should call os.Exit to set exit code --- internal/broker/broker_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 1caa985f69..9ed5781809 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -928,5 +928,5 @@ func TestMain(m *testing.M) { defaultProvider = server - m.Run() + os.Exit(m.Run()) } From 1edc7ba0738976d9714a9fd6d050d71084421a00 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 19 Aug 2024 13:04:37 +0200 Subject: [PATCH 0119/1670] Run `go mod tidy` in tools/ To fix the "Go: Code sanity" "Generated files up to date" CI job. --- tools/go.mod | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/go.mod b/tools/go.mod index d8d0c2b289..3122de0556 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -1,6 +1,7 @@ module github.com/ubuntu/authd-oidc-brokers/tools -go 1.22.0 +go 1.22.1 + toolchain go1.22.5 require github.com/golangci/golangci-lint v1.60.1 From d7a87ae4ebc32ed90db24a5858b76ab03c110bc3 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Wed, 21 Aug 2024 06:49:03 -0400 Subject: [PATCH 0120/1670] Bump Go to version 1.23 --- go.mod | 4 +--- tools/go.mod | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 1e0642a285..cb38cafc2a 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/ubuntu/authd-oidc-brokers -go 1.22.0 - -toolchain go1.22.5 +go 1.23.0 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 diff --git a/tools/go.mod b/tools/go.mod index 3122de0556..b372daf95f 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -1,8 +1,6 @@ module github.com/ubuntu/authd-oidc-brokers/tools -go 1.22.1 - -toolchain go1.22.5 +go 1.23.0 require github.com/golangci/golangci-lint v1.60.1 From 9ed3fd5d42c302e911b4dcf4e94f7f4748ca15ff Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Wed, 21 Aug 2024 08:08:58 -0400 Subject: [PATCH 0121/1670] Improve error messages returned by the broker Exposing too much on the error messages can allow attackers to grab sensitive information just by forcing authentication failures. In order to prevent leaks, we now print the full error message on the system logs and return a simpler one through dbus. --- internal/broker/broker.go | 49 +++++++++++++------ internal/broker/broker_test.go | 37 ++------------ .../second_call | 4 +- .../first_call | 2 +- .../second_call | 2 +- .../second_call | 2 +- .../first_call | 2 +- .../first_call | 2 +- .../first_call | 2 +- .../first_call | 2 +- .../first_call | 2 +- .../first_call | 2 +- .../first_call | 2 +- .../first_call | 2 +- .../first_call | 2 +- .../first_call | 2 +- .../first_call | 2 +- .../first_call | 2 +- .../first_call | 2 +- .../first_call | 2 +- .../first_call | 2 +- 21 files changed, 58 insertions(+), 68 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index eca6862275..f90de137fa 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -418,10 +418,13 @@ func (b *Broker) IsAuthenticated(sessionID, authenticationData string) (string, } func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo, authData map[string]string) (access string, data isAuthenticatedDataResponse) { + defer decorateErrorMessage(&data, "authentication failure") + // Decrypt challenge if present. challenge, err := decodeRawChallenge(b.privateKey, authData["challenge"]) if err != nil { - return AuthRetry, errorMessage{Message: fmt.Sprintf("could not decode challenge: %v", err)} + slog.Error(err.Error()) + return AuthRetry, errorMessage{Message: "could not decode challenge"} } var authInfo authCachedInfo @@ -429,6 +432,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo case "device_auth": response, ok := session.authInfo["response"].(*oauth2.DeviceAuthResponse) if !ok { + slog.Error("could not get device auth response") return AuthDenied, errorMessage{Message: "could not get required response"} } @@ -439,18 +443,21 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo defer cancel() t, err := session.authCfg.oauth.DeviceAccessToken(expiryCtx, response, b.providerInfo.AuthOptions()...) if err != nil { - return AuthRetry, errorMessage{Message: fmt.Sprintf("could not authenticate user: %v", err)} + slog.Error(err.Error()) + return AuthRetry, errorMessage{Message: "could not authenticate user remotely"} } rawIDToken, ok := t.Extra("id_token").(string) if !ok { - return AuthDenied, errorMessage{Message: "could not get id_token"} + slog.Error("could not get ID token") + return AuthDenied, errorMessage{Message: "could not get ID token"} } authInfo = authCachedInfo{Token: t, RawIDToken: rawIDToken} authInfo.UserInfo, err = b.fetchUserInfo(ctx, session, &authInfo) if err != nil { - return AuthDenied, errorMessage{Message: fmt.Sprintf("could not get user info: %v", err)} + slog.Error(err.Error()) + return AuthDenied, errorMessage{Message: "could not fetch user info"} } session.authInfo["auth_info"] = authInfo @@ -459,13 +466,15 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo case "password": authInfo, err = b.loadAuthInfo(ctx, session, challenge) if err != nil { - return AuthRetry, errorMessage{Message: fmt.Sprintf("could not authenticate user: %v", err)} + slog.Error(err.Error()) + return AuthRetry, errorMessage{Message: "could not load cached info"} } if authInfo.UserInfo.Name == "" { authInfo.UserInfo, err = b.fetchUserInfo(ctx, session, &authInfo) if err != nil { - return AuthDenied, errorMessage{Message: fmt.Sprintf("could not get user info: %v", err)} + slog.Error(err.Error()) + return AuthDenied, errorMessage{Message: "could not fetch user info"} } } @@ -476,13 +485,14 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo case "newpassword": if challenge == "" { - return AuthRetry, errorMessage{Message: "challenge must not be empty"} + return AuthRetry, errorMessage{Message: "empty challenge"} } var ok bool // This mode must always come after a authentication mode, so it has to have an auth_info. authInfo, ok = session.authInfo["auth_info"].(authCachedInfo) if !ok { + slog.Error("could not get required information") return AuthDenied, errorMessage{Message: "could not get required information"} } } @@ -492,7 +502,8 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo } if err := b.cacheAuthInfo(session, authInfo, challenge); err != nil { - return AuthRetry, errorMessage{Message: fmt.Sprintf("could not update cached info: %v", err)} + slog.Error(err.Error()) + return AuthDenied, errorMessage{Message: "could not cache user info"} } return AuthGranted, userInfoMessage{UserInfo: authInfo.UserInfo} @@ -505,7 +516,8 @@ func (b *Broker) startAuthenticate(sessionID string) (context.Context, error) { } if session.isAuthenticating != nil { - return nil, fmt.Errorf("IsAuthenticated already running for session %q", sessionID) + slog.Error(fmt.Sprintf("Authentication already running for session %q", sessionID)) + return nil, errors.New("authentication already running for this user session") } ctx, cancel := context.WithCancel(context.Background()) @@ -610,8 +622,6 @@ type authCachedInfo struct { // cacheAuthInfo serializes the access token and cache it. func (b *Broker) cacheAuthInfo(session *sessionInfo, authInfo authCachedInfo, password string) (err error) { - defer decorate.OnError(&err, "could not cache info") - content, err := json.Marshal(authInfo) if err != nil { return fmt.Errorf("could not marshal token: %v", err) @@ -636,8 +646,6 @@ func (b *Broker) cacheAuthInfo(session *sessionInfo, authInfo authCachedInfo, pa // loadAuthInfo deserializes the token from the cache and refreshes it if needed. func (b *Broker) loadAuthInfo(ctx context.Context, session *sessionInfo, password string) (loadedInfo authCachedInfo, err error) { - defer decorate.OnError(&err, "could not load cached info") - s, err := os.ReadFile(session.cachePath) if err != nil { return authCachedInfo{}, fmt.Errorf("could not read token: %v", err) @@ -677,8 +685,6 @@ func (b *Broker) loadAuthInfo(ctx context.Context, session *sessionInfo, passwor } func (b *Broker) fetchUserInfo(ctx context.Context, session *sessionInfo, t *authCachedInfo) (userInfo info.User, err error) { - defer decorate.OnError(&err, "could not fetch user info") - if session.isOffline { return info.User{}, errors.New("session is in offline mode") } @@ -705,3 +711,16 @@ func (b *Broker) fetchUserInfo(ctx context.Context, session *sessionInfo, t *aut return userInfo, err } + +// decorateErrorMessage decorates the isAuthenticatedDataResponse with the provided message, if it's an errorMessage. +func decorateErrorMessage(data *isAuthenticatedDataResponse, msg string) { + if *data == nil { + return + } + errMsg, ok := (*data).(errorMessage) + if !ok { + return + } + errMsg.Message = fmt.Sprintf("%s: %s", msg, errMsg.Message) + *data = errMsg +} diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 9ed5781809..012c3da16b 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -527,14 +527,7 @@ func TestIsAuthenticated(t *testing.T) { access, data, err := b.IsAuthenticated(sessionID, authData) require.True(t, json.Valid([]byte(data)), "IsAuthenticated returned data must be a valid JSON") - // Redact variant values from the response - data = strings.ReplaceAll(data, sessionID, "SESSION_ID") - data = strings.ReplaceAll(data, filepath.Dir(b.TokenPathForSession(sessionID)), "provider_cache_path") - data = strings.ReplaceAll(data, provider.URL, "provider_url") - errStr := strings.ReplaceAll(fmt.Sprintf("%v", err), sessionID, "SESSION_ID") - - got := isAuthenticatedResponse{Access: access, Data: data, Err: errStr} - + got := isAuthenticatedResponse{Access: access, Data: data, Err: fmt.Sprint(err)} out, err := yaml.Marshal(got) require.NoError(t, err, "Failed to marshal first response") @@ -569,13 +562,7 @@ func TestIsAuthenticated(t *testing.T) { access, data, err := b.IsAuthenticated(sessionID, secondAuthData) require.True(t, json.Valid([]byte(data)), "IsAuthenticated returned data must be a valid JSON") - // Redact variant values from the response - data = strings.ReplaceAll(data, sessionID, "SESSION_ID") - data = strings.ReplaceAll(data, filepath.Dir(b.TokenPathForSession(sessionID)), "provider_cache_path") - data = strings.ReplaceAll(data, provider.URL, "provider_url") - errStr := strings.ReplaceAll(fmt.Sprintf("%v", err), sessionID, "SESSION_ID") - - got := isAuthenticatedResponse{Access: access, Data: data, Err: errStr} + got := isAuthenticatedResponse{Access: access, Data: data, Err: fmt.Sprint(err)} out, err := yaml.Marshal(got) require.NoError(t, err, "Failed to marshal second response") @@ -654,7 +641,6 @@ func TestConcurrentIsAuthenticated(t *testing.T) { require.NoError(t, err, "Setup: SaveToken should not have returned an error") firstCallDone := make(chan struct{}) - //nolint:dupl // This is not a duplicate, just a very similar set of calls. go func() { t.Logf("%s: First auth starting", t.Name()) defer close(firstCallDone) @@ -666,14 +652,7 @@ func TestConcurrentIsAuthenticated(t *testing.T) { access, data, err := b.IsAuthenticated(firstSession, authData) require.True(t, json.Valid([]byte(data)), "IsAuthenticated returned data must be a valid JSON") - // Redact variant values from the response - data = strings.ReplaceAll(data, firstSession, "SESSION_ID") - data = strings.ReplaceAll(data, filepath.Dir(b.TokenPathForSession(firstSession)), "provider_cache_path") - data = strings.ReplaceAll(data, defaultProvider.URL, "provider_url") - errStr := strings.ReplaceAll(fmt.Sprintf("%v", err), firstSession, "SESSION_ID") - - got := isAuthenticatedResponse{Access: access, Data: data, Err: errStr} - + got := isAuthenticatedResponse{Access: access, Data: data, Err: fmt.Sprint(err)} out, err := yaml.Marshal(got) require.NoError(t, err, "Failed to marshal first response") @@ -686,7 +665,6 @@ func TestConcurrentIsAuthenticated(t *testing.T) { time.Sleep(tc.timeBetween) secondCallDone := make(chan struct{}) - //nolint:dupl // This is not a duplicate, just a very similar set of calls. go func() { t.Logf("%s: Second auth starting", t.Name()) defer close(secondCallDone) @@ -698,14 +676,7 @@ func TestConcurrentIsAuthenticated(t *testing.T) { access, data, err := b.IsAuthenticated(secondSession, authData) require.True(t, json.Valid([]byte(data)), "IsAuthenticated returned data must be a valid JSON") - // Redact variant values from the response - data = strings.ReplaceAll(data, secondSession, "SESSION_ID") - data = strings.ReplaceAll(data, filepath.Dir(b.TokenPathForSession(secondSession)), "provider_cache_path") - data = strings.ReplaceAll(data, defaultProvider.URL, "provider_url") - errStr := strings.ReplaceAll(fmt.Sprintf("%v", err), secondSession, "SESSION_ID") - - got := isAuthenticatedResponse{Access: access, Data: data, Err: errStr} - + got := isAuthenticatedResponse{Access: access, Data: data, Err: fmt.Sprint(err)} out, err := yaml.Marshal(got) require.NoError(t, err, "Failed to marshal second response") diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/second_call index caa6fbf909..00cb9a752e 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/second_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/second_call @@ -1,3 +1,3 @@ -access: retry -data: '{"message":"could not update cached info: could not cache info: could not create token directory: mkdir provider_cache_path: permission denied"}' +access: denied +data: '{"message":"authentication failure: could not cache user info"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_challenge_can_not_be_decrypted/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_challenge_can_not_be_decrypted/first_call index 3e188452d6..919fc1d7cd 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_challenge_can_not_be_decrypted/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_challenge_can_not_be_decrypted/first_call @@ -1,3 +1,3 @@ access: retry -data: '{"message":"could not decode challenge: could not decode challenge"}' +data: '{"message":"authentication failure: could not decode challenge"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/second_call index db4bb9d028..8bb152f9ba 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/second_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/second_call @@ -1,3 +1,3 @@ access: retry -data: '{"message":"challenge must not be empty"}' +data: '{"message":"authentication failure: empty challenge"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/second_call index d3ee131870..1cd265a88e 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/second_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/second_call @@ -1,3 +1,3 @@ access: denied data: '{}' -err: IsAuthenticated already running for session "SESSION_ID" +err: authentication already running for this user session diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/first_call index 7260101931..c47bb5dfc9 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/first_call @@ -1,3 +1,3 @@ access: denied -data: '{"message":"could not get required information"}' +data: '{"message":"authentication failure: could not get required information"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/first_call index 7260101931..c47bb5dfc9 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/first_call @@ -1,3 +1,3 @@ access: denied -data: '{"message":"could not get required information"}' +data: '{"message":"authentication failure: could not get required information"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_token_is_not_set/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_token_is_not_set/first_call index 7260101931..c47bb5dfc9 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_token_is_not_set/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_token_is_not_set/first_call @@ -1,3 +1,3 @@ access: denied -data: '{"message":"could not get required information"}' +data: '{"message":"authentication failure: could not get required information"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/first_call index 828bae2b62..69d73127bc 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/first_call @@ -1,3 +1,3 @@ access: retry -data: '{"message":"could not authenticate user: could not load cached info: could not refresh token: oauth2: token expired and refresh token is not set"}' +data: '{"message":"authentication failure: could not load cached info"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/first_call index c46e0130e7..411c897806 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/first_call @@ -1,3 +1,3 @@ access: denied -data: '{"message":"could not get user info: could not fetch user info: could not verify token: oidc: failed to unmarshal claims: invalid character ''\\u008a'' looking for beginning of value"}' +data: '{"message":"authentication failure: could not fetch user info"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call index 8545112d4b..69d73127bc 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call @@ -1,3 +1,3 @@ access: retry -data: '{"message":"could not authenticate user: could not load cached info: could not read token: open provider_cache_path/test-user@email.com.cache: no such file or directory"}' +data: '{"message":"authentication failure: could not load cached info"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/first_call index 0d8f437161..69d73127bc 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/first_call @@ -1,3 +1,3 @@ access: retry -data: '{"message":"could not authenticate user: could not load cached info: could not unmarshal token: invalid character ''T'' looking for beginning of value"}' +data: '{"message":"authentication failure: could not load cached info"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/first_call index f54662431b..69d73127bc 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/first_call @@ -1,3 +1,3 @@ access: retry -data: '{"message":"could not authenticate user: could not load cached info: could not refresh token: Post \"provider_url/token\": context deadline exceeded"}' +data: '{"message":"authentication failure: could not load cached info"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/first_call index 5c32b2e8ad..69d73127bc 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/first_call @@ -1,3 +1,3 @@ access: retry -data: '{"message":"could not authenticate user: could not load cached info: could not refresh token: oauth2: cannot fetch token: 400 Bad Request\nResponse: "}' +data: '{"message":"authentication failure: could not load cached info"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token/first_call index 609e9da6a1..78e15876e9 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token/first_call @@ -1,3 +1,3 @@ access: retry -data: '{"message":"could not authenticate user: oauth2: cannot fetch token: 503 Service Unavailable\nResponse: "}' +data: '{"message":"authentication failure: could not authenticate user remotely"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/first_call index 681dda0fdc..78e15876e9 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/first_call @@ -1,3 +1,3 @@ access: retry -data: '{"message":"could not authenticate user: oauth2: cannot fetch token: 408 Request Timeout\nResponse: "}' +data: '{"message":"authentication failure: could not authenticate user remotely"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_link_expires/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_link_expires/first_call index 24ae10defb..78e15876e9 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_link_expires/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_link_expires/first_call @@ -1,3 +1,3 @@ access: retry -data: '{"message":"could not authenticate user: context deadline exceeded"}' +data: '{"message":"authentication failure: could not authenticate user remotely"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_response_is_invalid/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_response_is_invalid/first_call index daeaf044ae..51c8345079 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_response_is_invalid/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_response_is_invalid/first_call @@ -1,3 +1,3 @@ access: denied -data: '{"message":"could not get required response"}' +data: '{"message":"authentication failure: could not get required response"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/first_call index 010bc15efb..69d73127bc 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/first_call @@ -1,3 +1,3 @@ access: retry -data: '{"message":"could not authenticate user: could not load cached info: could not deserialize token: cipher: message authentication failed"}' +data: '{"message":"authentication failure: could not load cached info"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/first_call index c1ae6a98b7..411c897806 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/first_call @@ -1,3 +1,3 @@ access: denied -data: '{"message":"could not get user info: could not fetch user info: returned user \"test-user@email.com\" does not match the selected one \"not-matching\""}' +data: '{"message":"authentication failure: could not fetch user info"}' err: From 13d7fd641d3055c9d0fe577a832d084b5b6a54ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 08:54:07 +0000 Subject: [PATCH 0122/1670] deps(go-tools): bump github.com/golangci/golangci-lint in /tools Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.60.1 to 1.60.3. - [Release notes](https://github.com/golangci/golangci-lint/releases) - [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md) - [Commits](https://github.com/golangci/golangci-lint/compare/v1.60.1...v1.60.3) --- updated-dependencies: - dependency-name: github.com/golangci/golangci-lint dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- tools/go.mod | 18 ++++++++-------- tools/go.sum | 58 +++++++++++++++++++++++++++------------------------- 2 files changed, 39 insertions(+), 37 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index b372daf95f..5d9cfaf7f2 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -2,7 +2,7 @@ module github.com/ubuntu/authd-oidc-brokers/tools go 1.23.0 -require github.com/golangci/golangci-lint v1.60.1 +require github.com/golangci/golangci-lint v1.60.3 require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect @@ -63,7 +63,7 @@ require ( github.com/gofrs/flock v0.12.1 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect - github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e // indirect + github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 // indirect github.com/golangci/misspell v0.6.0 // indirect github.com/golangci/modinfo v0.3.4 // indirect github.com/golangci/plugin-module-register v0.1.1 // indirect @@ -132,7 +132,7 @@ require ( github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.27.0 // indirect - github.com/securego/gosec/v2 v2.20.1-0.20240525090044-5f0084eb01a9 // indirect + github.com/securego/gosec/v2 v2.20.1-0.20240822074752-ab3f6c1c83a0 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect @@ -154,7 +154,7 @@ require ( github.com/tetafro/godot v1.4.16 // indirect github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect github.com/timonwong/loggercheck v0.9.4 // indirect - github.com/tomarrell/wrapcheck/v2 v2.8.3 // indirect + github.com/tomarrell/wrapcheck/v2 v2.9.0 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect github.com/ultraware/funlen v0.1.0 // indirect github.com/ultraware/whitespace v0.1.1 // indirect @@ -170,18 +170,18 @@ require ( go.uber.org/automaxprocs v1.5.3 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.23.0 // indirect - golang.org/x/text v0.15.0 // indirect + golang.org/x/text v0.17.0 // indirect golang.org/x/tools v0.24.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - honnef.co/go/tools v0.5.0 // indirect - mvdan.cc/gofumpt v0.6.0 // indirect + honnef.co/go/tools v0.5.1 // indirect + mvdan.cc/gofumpt v0.7.0 // indirect mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect ) diff --git a/tools/go.sum b/tools/go.sum index 81ac837888..750b93716f 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -141,8 +141,8 @@ github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4 github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA= github.com/firefart/nonamedreturns v1.0.5/go.mod h1:gHJjDqhGM4WyPt639SOZs+G89Ko7QKH5R5BhnO6xJhw= -github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= @@ -160,8 +160,10 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= +github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= @@ -224,10 +226,10 @@ 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/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= -github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e h1:ULcKCDV1LOZPFxGZaA6TlQbiM3J2GCPnkx/bGF6sX/g= -github.com/golangci/gofmt v0.0.0-20231018234816-f50ced29576e/go.mod h1:Pm5KhLPA8gSnQwrQ6ukebRcapGb/BG9iUkdaiCcGHJM= -github.com/golangci/golangci-lint v1.60.1 h1:DRKNqNTQRLBJZ1il5u4fvgLQCjQc7QFs0DbhksJtVJE= -github.com/golangci/golangci-lint v1.60.1/go.mod h1:jDIPN1rYaIA+ijp9OZcUmUCoQOtZ76pOlFbi15FlLJY= +github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 h1:/1322Qns6BtQxUZDTAT4SdcoxknUki7IAoK4SAXr8ME= +github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9/go.mod h1:Oesb/0uFAyWoaw1U1qS5zyjCg5NP9C9iwjnI4tIsXEE= +github.com/golangci/golangci-lint v1.60.3 h1:l38A5de24ZeDlcFF+EB7m3W5joPD99/hS5SIHJPyZa0= +github.com/golangci/golangci-lint v1.60.3/go.mod h1:J4vOpcjzRI+lDL2DKNGBZVB3EQSBfCBCMpaydWLtJNo= github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= github.com/golangci/modinfo v0.3.4 h1:oU5huX3fbxqQXdfspamej74DFX0kyGLkw1ppvXoJ8GA= @@ -264,8 +266,8 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= -github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -393,10 +395,10 @@ github.com/nunnatsa/ginkgolinter v0.16.2 h1:8iLqHIZvN4fTLDC0Ke9tbSZVcyVHoBs0HIbn github.com/nunnatsa/ginkgolinter v0.16.2/go.mod h1:4tWRinDN1FeJgU+iJANW/kz7xKN5nYRAOfJDQUS9dOQ= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.17.3 h1:oJcvKpIb7/8uLpDDtnQuf18xVnwKp8DTD7DQ6gTd/MU= -github.com/onsi/ginkgo/v2 v2.17.3/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= -github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= -github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/onsi/ginkgo/v2 v2.20.0 h1:PE84V2mHqoT1sglvHc8ZdQtPcwmvvt29WLEEO3xmdZw= +github.com/onsi/ginkgo/v2 v2.20.0/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= @@ -466,8 +468,8 @@ github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tM github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.27.0 h1:t/3jZpSXtRPRf2xr0m63i32ZrusyurIGT9E5wAvXQnI= github.com/sashamelentyev/usestdlibvars v1.27.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= -github.com/securego/gosec/v2 v2.20.1-0.20240525090044-5f0084eb01a9 h1:rnO6Zp1YMQwv8AyxzuwsVohljJgp4L0ZqiCgtACsPsc= -github.com/securego/gosec/v2 v2.20.1-0.20240525090044-5f0084eb01a9/go.mod h1:dg7lPlu/xK/Ut9SedURCoZbVCR4yC7fM65DtH9/CDHs= +github.com/securego/gosec/v2 v2.20.1-0.20240822074752-ab3f6c1c83a0 h1:VqD4JMoqwuuCz8GZlBDsIDyE6K4YUsWJpbNtuOWHoFk= +github.com/securego/gosec/v2 v2.20.1-0.20240822074752-ab3f6c1c83a0/go.mod h1:iyeMMRw8QEmueUSZ2VqmkQMiDyDcobfPnG00CV/NWdE= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= @@ -531,8 +533,8 @@ github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 h1:quvGphlmUVU+n github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966/go.mod h1:27bSVNWSBOHm+qRp1T9qzaIpsWEP6TbUnei/43HK+PQ= github.com/timonwong/loggercheck v0.9.4 h1:HKKhqrjcVj8sxL7K77beXh0adEm6DLjV/QOGeMXEVi4= github.com/timonwong/loggercheck v0.9.4/go.mod h1:caz4zlPcgvpEkXgVnAJGowHAMW2NwHaNlpS8xDbVhTg= -github.com/tomarrell/wrapcheck/v2 v2.8.3 h1:5ov+Cbhlgi7s/a42BprYoxsr73CbdMUTzE3bRDFASUs= -github.com/tomarrell/wrapcheck/v2 v2.8.3/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= +github.com/tomarrell/wrapcheck/v2 v2.9.0 h1:801U2YCAjLhdN8zhZ/7tdjB3EnAoRlJHt/s+9hijLQ4= +github.com/tomarrell/wrapcheck/v2 v2.9.0/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= github.com/ultraware/funlen v0.1.0 h1:BuqclbkY6pO+cvxoq7OsktIXZpgBSkYTQtmwhAK81vI= @@ -597,8 +599,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= -golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8= @@ -765,8 +767,8 @@ 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.6.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.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -911,8 +913,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj 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.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -939,10 +941,10 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.5.0 h1:29uoiIormS3Z6R+t56STz/oI4v+mB51TSmEOdJPgRnE= -honnef.co/go/tools v0.5.0/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= -mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo= -mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA= +honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I= +honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= +mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU= +mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo= mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f h1:lMpcwN6GxNbWtbpI1+xzFLSW8XzX0u72NttUGVFjO3U= mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f/go.mod h1:RSLa7mKKCNeTTMHBw5Hsy2rfJmd6O2ivt9Dw9ZqCQpQ= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= From 4abe1e3311aaebdb9f2e0ddcba522082c62076a8 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 21 Aug 2024 10:22:21 +0200 Subject: [PATCH 0123/1670] Use more descriptive error messages We used "could not get user info" in too many places, making it hard to tell where this error message originated from. Also, add a brief description to errors which originate in third-party packages before passing them up the call stack, to make it easier to tell where in our code we first encountered the error. --- internal/providers/msentraid/msentraid.go | 14 +++++++------- internal/providers/noprovider/noprovider.go | 2 +- internal/testutils/provider.go | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index cc18894432..9ac5a5688f 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -73,7 +73,7 @@ type claims struct { func (p Provider) userClaims(idToken *oidc.IDToken) (claims, error) { var userClaims claims if err := idToken.Claims(&userClaims); err != nil { - return claims{}, fmt.Errorf("could not get user info: %v", err) + return claims{}, fmt.Errorf("failed to get ID token claims: %v", err) } return userClaims, nil } @@ -83,12 +83,12 @@ func (p Provider) getGroups(token *oauth2.Token) ([]info.Group, error) { cred := azureTokenCredential{token: token} auth, err := msgraphauth.NewAzureIdentityAuthenticationProvider(cred) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to create AzureIdentityAuthenticationProvider: %v", err) } adapter, err := msgraphsdk.NewGraphRequestAdapter(auth) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to create GraphRequestAdapter: %v", err) } client := msgraphsdk.NewGraphServiceClient(adapter) @@ -101,12 +101,12 @@ func (p Provider) getGroups(token *oauth2.Token) ([]info.Group, error) { }, } if _, err = client.Groups().Get(context.Background(), requestOptions); err != nil { - return nil, fmt.Errorf("could not access user's groups: %v", err) + return nil, fmt.Errorf("failed to list groups: %v", err) } m, err := client.Me().TransitiveMemberOf().Get(context.Background(), nil) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get user's groups: %v", err) } if m == nil { slog.Debug("Got nil response from Microsoft Graph API for user's groups, assuming that user is not a member of any group.") @@ -134,7 +134,7 @@ func (p Provider) getGroups(token *oauth2.Token) ([]info.Group, error) { v, err := msGroup.GetBackingStore().Get("displayName") if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get displayName from group object: %v", err) } name, ok := v.(*string) if !ok || name == nil { @@ -156,7 +156,7 @@ func (p Provider) getGroups(token *oauth2.Token) ([]info.Group, error) { v, err = msGroup.GetBackingStore().Get("id") if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get id from group object: %v", err) } id, ok := v.(*string) if !ok || id == nil { diff --git a/internal/providers/noprovider/noprovider.go b/internal/providers/noprovider/noprovider.go index fc85bfe2f4..6f2ac9c508 100644 --- a/internal/providers/noprovider/noprovider.go +++ b/internal/providers/noprovider/noprovider.go @@ -99,7 +99,7 @@ type claims struct { func (p NoProvider) userClaims(idToken *oidc.IDToken) (claims, error) { var userClaims claims if err := idToken.Claims(&userClaims); err != nil { - return claims{}, fmt.Errorf("could not get user info: %v", err) + return claims{}, fmt.Errorf("failed to get ID token claims: %v", err) } return userClaims, nil } diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index 6e005c8abf..221a49aa85 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -285,7 +285,7 @@ type claims struct { func (p *MockProviderInfoer) userClaims(idToken *oidc.IDToken) (claims, error) { var userClaims claims if err := idToken.Claims(&userClaims); err != nil { - return claims{}, fmt.Errorf("could not get user info: %v", err) + return claims{}, fmt.Errorf("failed to get ID token claims: %v", err) } return userClaims, nil } From 7f3c357083e063ee7feb1d3a42b3497283f7088f Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 27 Aug 2024 08:27:28 -0400 Subject: [PATCH 0124/1670] Better errors when failing to create broker object We used to have a quite big prefix for the error and use the same message for when either the client ID or the issuer URL were not configured. Now, we can show a more targeted error that also won't require the user to scroll right to fully see the message. --- internal/broker/broker.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index eca6862275..a98741baf3 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -98,7 +98,7 @@ type Option func(*option) // New returns a new oidc Broker with the providers listed in the configuration file. func New(cfg Config, args ...Option) (b *Broker, err error) { - defer decorate.OnError(&err, "could not create broker with provided issuer and client ID") + defer decorate.OnError(&err, "could not create broker") opts := option{ // This is to avoid too much complexity in the tests. @@ -110,11 +110,16 @@ func New(cfg Config, args ...Option) (b *Broker, err error) { } if cfg.CachePath == "" { - return &Broker{}, errors.New("cache path must be provided") + err = errors.Join(err, errors.New("cache path is required and was not provided")) } - - if cfg.IssuerURL == "" || cfg.ClientID == "" { - return &Broker{}, errors.New("issuer and client ID must be provided") + if cfg.IssuerURL == "" { + err = errors.Join(err, errors.New("issuer URL is required and was not provided")) + } + if cfg.ClientID == "" { + err = errors.Join(err, errors.New("client ID is required and was not provided")) + } + if err != nil { + return nil, err } homeDirPath := "/home" @@ -125,7 +130,8 @@ func New(cfg Config, args ...Option) (b *Broker, err error) { // Generate a new private key for the broker. privateKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { - panic(fmt.Sprintf("could not create an valid rsa key: %v", err)) + slog.Error(err.Error()) + return nil, errors.New("failed to generate broker private key") } b = &Broker{ From 2b6529d25ca941b4d20fcb19be61376ebb77acf0 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 27 Aug 2024 08:29:45 -0400 Subject: [PATCH 0125/1670] Return error if config file was not edited Since we don't connect to the provider at broker creation anymore, we can't know for certain if the config file was updated until the user tries to authenticate. While we still can't exactly know if the provider is valid, we can at least check whether it was edited or not, so we can fail sooner. --- internal/dbusservice/dbusservice.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/internal/dbusservice/dbusservice.go b/internal/dbusservice/dbusservice.go index 306ec121a7..273501b4e2 100644 --- a/internal/dbusservice/dbusservice.go +++ b/internal/dbusservice/dbusservice.go @@ -3,6 +3,7 @@ package dbusservice import ( "context" + "errors" "fmt" "strings" @@ -128,9 +129,18 @@ func parseConfig(cfgPath string) (map[string]map[string]string, error) { for _, section := range iniCfg.Sections() { cfg[section.Name()] = make(map[string]string) for _, key := range section.Keys() { + if strings.Contains(key.String(), "<") && strings.Contains(key.String(), ">") { + err = errors.Join(err, fmt.Errorf("found invalid character in section %q, key %q", section.Name(), key.Name())) + continue + } cfg[section.Name()][key.Name()] = key.String() } } + + // This means we found at least one section that was potentially not edited. + if err != nil { + return nil, fmt.Errorf("config file has invalid values, did you edit the file %q?\n%w", cfgPath, err) + } return cfg, nil } From edb4ad784107ad5e1c5be0b46be3a916aea08ca8 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 27 Aug 2024 08:34:20 -0400 Subject: [PATCH 0126/1670] Add tests for the config file parsing --- internal/dbusservice/internal_test.go | 99 +++++++++++++++++++ ...values_contain_a_single_template_delimiter | 4 + .../golden/successfully_parse_config_file | 4 + ...lly_parse_config_file_with_optional_values | 7 ++ 4 files changed, 114 insertions(+) create mode 100644 internal/dbusservice/internal_test.go create mode 100644 internal/dbusservice/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter create mode 100644 internal/dbusservice/testdata/TestParseConfig/golden/successfully_parse_config_file create mode 100644 internal/dbusservice/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values diff --git a/internal/dbusservice/internal_test.go b/internal/dbusservice/internal_test.go new file mode 100644 index 0000000000..e57b36e083 --- /dev/null +++ b/internal/dbusservice/internal_test.go @@ -0,0 +1,99 @@ +package dbusservice + +import ( + "flag" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + "github.com/ubuntu/authd-oidc-brokers/internal/testutils" +) + +var configTypes = map[string]string{ + "valid": ` +[oidc] +issuer = https://issuer.url.com +client_id = client_id +`, + + "valid+optional": ` +[oidc] +issuer = https://issuer.url.com +client_id = client_id + +[users] +home_base_dir = /home +allowed_ssh_suffixes = @issuer.url.com +`, + + "singles": ` +[oidc] +issuer = https://ISSUER_URL> +client_id = +client_id = +`, +} + +func TestParseConfig(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + configType string + + wantErr bool + }{ + "Successfully parse config file": {}, + "Successfully parse config file with optional values": {configType: "valid+optional"}, + + "Do not fail if values contain a single template delimiter": {configType: "singles"}, + + "Error if file does not exist": {configType: "inexistent", wantErr: true}, + "Error if file is unreadable": {configType: "unreadable", wantErr: true}, + "Error if file is not updated": {configType: "template", wantErr: true}, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + confPath := filepath.Join(t.TempDir(), "broker.conf") + + if tc.configType == "" { + tc.configType = "valid" + } + err := os.WriteFile(confPath, []byte(configTypes[tc.configType]), 0600) + require.NoError(t, err, "Setup: Failed to write config file") + + switch tc.configType { + case "inexistent": + err = os.Remove(confPath) + require.NoError(t, err, "Setup: Failed to remove config file") + case "unreadable": + err = os.Chmod(confPath, 0000) + require.NoError(t, err, "Setup: Failed to make config file unreadable") + } + + got, err := parseConfig(confPath) + if tc.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + want := testutils.LoadWithUpdateFromGoldenYAML(t, got) + require.EqualValues(t, want, got) + }) + } +} + +func TestMain(m *testing.M) { + testutils.InstallUpdateFlag() + flag.Parse() + + m.Run() +} diff --git a/internal/dbusservice/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter b/internal/dbusservice/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter new file mode 100644 index 0000000000..17a42fa4e9 --- /dev/null +++ b/internal/dbusservice/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter @@ -0,0 +1,4 @@ +DEFAULT: {} +oidc: + client_id: diff --git a/internal/dbusservice/testdata/TestParseConfig/golden/successfully_parse_config_file b/internal/dbusservice/testdata/TestParseConfig/golden/successfully_parse_config_file new file mode 100644 index 0000000000..622dbad7f1 --- /dev/null +++ b/internal/dbusservice/testdata/TestParseConfig/golden/successfully_parse_config_file @@ -0,0 +1,4 @@ +DEFAULT: {} +oidc: + client_id: client_id + issuer: https://issuer.url.com diff --git a/internal/dbusservice/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values b/internal/dbusservice/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values new file mode 100644 index 0000000000..53fe96579b --- /dev/null +++ b/internal/dbusservice/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values @@ -0,0 +1,7 @@ +DEFAULT: {} +oidc: + client_id: client_id + issuer: https://issuer.url.com +users: + allowed_ssh_suffixes: '@issuer.url.com' + home_base_dir: /home From 509ad8b44e3dd54a1af9a3ac42e78832e413e209 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 30 Aug 2024 13:00:32 +0200 Subject: [PATCH 0127/1670] Fix debug logs not printed in some packages We imported the wrong slog package in some places, causing debug logs to not be printed (because the log level is not set for that package, so it defaults to INFO). --- go.mod | 2 +- internal/broker/broker.go | 2 +- internal/broker/encrypt.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index cb38cafc2a..2e6d702a20 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,6 @@ require ( github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 golang.org/x/crypto v0.26.0 - golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 golang.org/x/oauth2 v0.22.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 @@ -62,6 +61,7 @@ require ( go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect + golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/net v0.27.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.23.0 // indirect diff --git a/internal/broker/broker.go b/internal/broker/broker.go index f90de137fa..a9cc22efd7 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -10,6 +10,7 @@ import ( "encoding/json" "errors" "fmt" + "log/slog" "os" "path/filepath" "slices" @@ -22,7 +23,6 @@ import ( "github.com/ubuntu/authd-oidc-brokers/internal/providers" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "github.com/ubuntu/decorate" - "golang.org/x/exp/slog" "golang.org/x/oauth2" ) diff --git a/internal/broker/encrypt.go b/internal/broker/encrypt.go index 74cce26172..0e4755129b 100644 --- a/internal/broker/encrypt.go +++ b/internal/broker/encrypt.go @@ -9,9 +9,9 @@ import ( "encoding/base64" "errors" "fmt" + "log/slog" "golang.org/x/crypto/scrypt" - "golang.org/x/exp/slog" ) const saltLen = 32 From 511b9b7ebc4bb00061c8cefc2671f5146c592d05 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 30 Aug 2024 16:32:10 +0200 Subject: [PATCH 0128/1670] ci: Block merging PRs with fixup commits We want to avoid force pushing to PRs which have already been reviewed, so we're doing fixup commits instead. To keep a clean git history, those should be squashed before merging (with `git rebase --autosquash`). This new GitHub action should avoid that we accidentally merge PRs which have unsquashed fixup commits. --- .github/workflows/git.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .github/workflows/git.yml diff --git a/.github/workflows/git.yml b/.github/workflows/git.yml new file mode 100644 index 0000000000..b42aeb491e --- /dev/null +++ b/.github/workflows/git.yml @@ -0,0 +1,12 @@ +name: Git Checks + +on: [pull_request] + +jobs: + block-fixup: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2.0.0 + - name: Block Fixup Commit Merge + uses: 13rac1/block-fixup-merge-action@v2.0.0 From 4d36d052bdf5c780ac58ed6aafaf91af3defdffc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 08:11:14 +0000 Subject: [PATCH 0129/1670] deps(ci): bump actions/checkout from 2.0.0 to 4.1.7 Bumps [actions/checkout](https://github.com/actions/checkout) from 2.0.0 to 4.1.7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v4.1.7) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/auto-updates.yml | 2 +- .github/workflows/ci.yml | 4 ++-- .github/workflows/git.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/auto-updates.yml b/.github/workflows/auto-updates.yml index 5c34c0cfe1..878c72059a 100644 --- a/.github/workflows/auto-updates.yml +++ b/.github/workflows/auto-updates.yml @@ -26,7 +26,7 @@ jobs: set -eu sudo apt update sudo apt install -y git - - uses: actions/checkout@v4 + - uses: actions/checkout@v4.1.7 with: ref: main fetch-depth: 0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aab5fb236d..ef2fd750ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: name: "Go: Code sanity" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4.1.7 - name: Go code sanity check uses: canonical/desktop-engineering/gh-actions/go/code-sanity@main with: @@ -23,7 +23,7 @@ jobs: name: "Go: Tests" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4.1.7 - uses: actions/setup-go@v5 with: go-version-file: go.mod diff --git a/.github/workflows/git.yml b/.github/workflows/git.yml index b42aeb491e..566fb63328 100644 --- a/.github/workflows/git.yml +++ b/.github/workflows/git.yml @@ -7,6 +7,6 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.0.0 + - uses: actions/checkout@v4.1.7 - name: Block Fixup Commit Merge uses: 13rac1/block-fixup-merge-action@v2.0.0 From d8a9afc41c91b240f45d68dc0a57c7b0ea3e1a22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 08:30:55 +0000 Subject: [PATCH 0130/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go-core Bumps [github.com/microsoftgraph/msgraph-sdk-go-core](https://github.com/microsoftgraph/msgraph-sdk-go-core) from 1.2.0 to 1.2.1. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go-core/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go-core/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go-core/compare/v1.2.0...v1.2.1) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go-core dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 2e6d702a20..ab98088d11 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible github.com/microsoftgraph/msgraph-sdk-go v1.47.0 - github.com/microsoftgraph/msgraph-sdk-go-core v1.2.0 + github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 github.com/otiai10/copy v1.14.0 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 @@ -39,10 +39,10 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect github.com/microsoft/kiota-abstractions-go v1.6.1 // indirect - github.com/microsoft/kiota-authentication-azure-go v1.0.2 // indirect - github.com/microsoft/kiota-http-go v1.4.1 // indirect + github.com/microsoft/kiota-authentication-azure-go v1.1.0 // indirect + github.com/microsoft/kiota-http-go v1.4.4 // indirect github.com/microsoft/kiota-serialization-form-go v1.0.0 // indirect - github.com/microsoft/kiota-serialization-json-go v1.0.7 // indirect + github.com/microsoft/kiota-serialization-json-go v1.0.8 // indirect github.com/microsoft/kiota-serialization-multipart-go v1.0.0 // indirect github.com/microsoft/kiota-serialization-text-go v1.0.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect diff --git a/go.sum b/go.sum index c2ec225cb8..d90af1c1ba 100644 --- a/go.sum +++ b/go.sum @@ -53,22 +53,22 @@ github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPn github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/microsoft/kiota-abstractions-go v1.6.1 h1:NXK50S3BwJn9Wj6bO0YFuAig7y2WVgdQ/ie1ktMl2J4= github.com/microsoft/kiota-abstractions-go v1.6.1/go.mod h1:FI1I2OHg0E7bK5t8DPnw+9C/CHVyLP6XeqDBT+95pTE= -github.com/microsoft/kiota-authentication-azure-go v1.0.2 h1:tClGeyFZJ+4Bakf8u0euPM4wqy4ethycdOgx3jyH3pI= -github.com/microsoft/kiota-authentication-azure-go v1.0.2/go.mod h1:aTcti0bUJEcq7kBfQG4Sr4ElvRNuaalXcFEu4iEyQ6M= -github.com/microsoft/kiota-http-go v1.4.1 h1:zR54JahUOcu8h9C5z00fcQChzX8d01+BwhkTS8H16Ro= -github.com/microsoft/kiota-http-go v1.4.1/go.mod h1:Kup5nMDD3a9sjdgRKHCqZWqtrv3FbprjcPaGjLR6FzM= +github.com/microsoft/kiota-authentication-azure-go v1.1.0 h1:HudH57Enel9zFQ4TEaJw6lMiyZ5RbBdrRHwdU0NP2RY= +github.com/microsoft/kiota-authentication-azure-go v1.1.0/go.mod h1:zfPFOiLdEqM77Hua5B/2vpcXrVaGqSWjHSRzlvAWEgc= +github.com/microsoft/kiota-http-go v1.4.4 h1:HM0KT/Q7o+JsGatFkkbTIqJL24Jzo5eMI5NNe9N4TQ4= +github.com/microsoft/kiota-http-go v1.4.4/go.mod h1:Kup5nMDD3a9sjdgRKHCqZWqtrv3FbprjcPaGjLR6FzM= github.com/microsoft/kiota-serialization-form-go v1.0.0 h1:UNdrkMnLFqUCccQZerKjblsyVgifS11b3WCx+eFEsAI= github.com/microsoft/kiota-serialization-form-go v1.0.0/go.mod h1:h4mQOO6KVTNciMF6azi1J9QB19ujSw3ULKcSNyXXOMA= -github.com/microsoft/kiota-serialization-json-go v1.0.7 h1:yMbckSTPrjZdM4EMXgzLZSA3CtDaUBI350u0VoYRz7Y= -github.com/microsoft/kiota-serialization-json-go v1.0.7/go.mod h1:1krrY7DYl3ivPIzl4xTaBpew6akYNa8/Tal8g+kb0cc= +github.com/microsoft/kiota-serialization-json-go v1.0.8 h1:+aViv9k6wqaw1Fx6P49fl5GIB1hN3b6CG0McNTcUYBc= +github.com/microsoft/kiota-serialization-json-go v1.0.8/go.mod h1:O8+v11U0EUwHlCz7hrW38KxDmdhKAHfv4Q89uvsBalY= github.com/microsoft/kiota-serialization-multipart-go v1.0.0 h1:3O5sb5Zj+moLBiJympbXNaeV07K0d46IfuEd5v9+pBs= github.com/microsoft/kiota-serialization-multipart-go v1.0.0/go.mod h1:yauLeBTpANk4L03XD985akNysG24SnRJGaveZf+p4so= github.com/microsoft/kiota-serialization-text-go v1.0.0 h1:XOaRhAXy+g8ZVpcq7x7a0jlETWnWrEum0RhmbYrTFnA= github.com/microsoft/kiota-serialization-text-go v1.0.0/go.mod h1:sM1/C6ecnQ7IquQOGUrUldaO5wj+9+v7G2W3sQ3fy6M= github.com/microsoftgraph/msgraph-sdk-go v1.47.0 h1:qXfmDij9md6mPsSAJjiDNmS4hxqKo0R489GiVMZVmmY= github.com/microsoftgraph/msgraph-sdk-go v1.47.0/go.mod h1:Gnws5D7d/930uS9J4qlCm4BAR/zenqECMk9tgMDXeZQ= -github.com/microsoftgraph/msgraph-sdk-go-core v1.2.0 h1:vHvUKVnk0FkLSJv+aUvxIsM5kLNqWlu/WzUM8S4XOy4= -github.com/microsoftgraph/msgraph-sdk-go-core v1.2.0/go.mod h1:armKxoJybX70GBpd748K2e+1AySPYRbhvYW7EBplujw= +github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 h1:P1wpmn3xxfPMFJHg+PJPcusErfRkl63h6OdAnpDbkS8= +github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1/go.mod h1:vFmWQGWyLlhxCESNLv61vlE4qesBU+eWmEVH7DJSESA= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= From 5369c2f07a36f52d294a83895d98201b0a246c9c Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 2 Sep 2024 10:44:21 +0200 Subject: [PATCH 0131/1670] Use "actions/checkout@v4" instead of "actions/checkout@v4.1.7" Unless we expect minor version updates of the checkout action to break things, it should safe to use "v4" instead to always use the latest v4 version without triggering dependabot, thus reducing the noise caused by dependabot a bit. This reverts commit 4d36d052bdf5c780ac58ed6aafaf91af3defdffc. --- .github/workflows/auto-updates.yml | 2 +- .github/workflows/ci.yml | 4 ++-- .github/workflows/git.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/auto-updates.yml b/.github/workflows/auto-updates.yml index 878c72059a..5c34c0cfe1 100644 --- a/.github/workflows/auto-updates.yml +++ b/.github/workflows/auto-updates.yml @@ -26,7 +26,7 @@ jobs: set -eu sudo apt update sudo apt install -y git - - uses: actions/checkout@v4.1.7 + - uses: actions/checkout@v4 with: ref: main fetch-depth: 0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef2fd750ff..aab5fb236d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: name: "Go: Code sanity" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.7 + - uses: actions/checkout@v4 - name: Go code sanity check uses: canonical/desktop-engineering/gh-actions/go/code-sanity@main with: @@ -23,7 +23,7 @@ jobs: name: "Go: Tests" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.7 + - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version-file: go.mod diff --git a/.github/workflows/git.yml b/.github/workflows/git.yml index 566fb63328..e3f3a5cfc2 100644 --- a/.github/workflows/git.yml +++ b/.github/workflows/git.yml @@ -7,6 +7,6 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.7 + - uses: actions/checkout@v4 - name: Block Fixup Commit Merge uses: 13rac1/block-fixup-merge-action@v2.0.0 From a3dd755269153e11ddb7760ad8623c4ed7f13e8d Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 30 Aug 2024 13:19:34 +0200 Subject: [PATCH 0132/1670] Print warning if an expected scope is missing Should make it easier to debug scope-related issues even if the user didn't enable verbose logs. Co-authored-by: Didier Roche-Tolomelli --- internal/broker/broker.go | 7 ++++- internal/consts/oidc.go | 9 +++++++ internal/providers/default.go | 2 +- internal/providers/msentraid/msentraid.go | 30 ++++++++++++++++++++- internal/providers/noprovider/noprovider.go | 11 ++++++++ internal/providers/providers.go | 1 + internal/providers/withmsentraid.go | 2 +- internal/testutils/provider.go | 25 +++++++++++++++-- 8 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 internal/consts/oidc.go diff --git a/internal/broker/broker.go b/internal/broker/broker.go index faca648b5c..8aff04e6e8 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -20,6 +20,7 @@ import ( "github.com/coreos/go-oidc/v3/oidc" "github.com/google/uuid" + "github.com/ubuntu/authd-oidc-brokers/internal/consts" "github.com/ubuntu/authd-oidc-brokers/internal/providers" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "github.com/ubuntu/decorate" @@ -199,7 +200,7 @@ func (b *Broker) connectToProvider(ctx context.Context) (authCfg authConfig, err oauthCfg := oauth2.Config{ ClientID: b.oidcCfg.ClientID, Endpoint: provider.Endpoint(), - Scopes: append([]string{oidc.ScopeOpenID, "profile", "email"}, b.providerInfo.AdditionalScopes()...), + Scopes: append(consts.DefaultScopes, b.providerInfo.AdditionalScopes()...), } return authConfig{provider: provider, oauth: oauthCfg}, nil @@ -453,6 +454,10 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo return AuthRetry, errorMessage{Message: "could not authenticate user remotely"} } + if err = b.providerInfo.CheckTokenScopes(t); err != nil { + slog.Warn(err.Error()) + } + rawIDToken, ok := t.Extra("id_token").(string) if !ok { slog.Error("could not get ID token") diff --git a/internal/consts/oidc.go b/internal/consts/oidc.go new file mode 100644 index 0000000000..c9cf3d0f20 --- /dev/null +++ b/internal/consts/oidc.go @@ -0,0 +1,9 @@ +package consts + +import "github.com/coreos/go-oidc/v3/oidc" + +var ( + // DefaultScopes contains the OIDC scopes that we require for all providers. + // Provider implementations can append additional scopes. + DefaultScopes = []string{oidc.ScopeOpenID, "profile", "email"} +) diff --git a/internal/providers/default.go b/internal/providers/default.go index 21514ecfc9..f3b406dcc1 100644 --- a/internal/providers/default.go +++ b/internal/providers/default.go @@ -8,5 +8,5 @@ import ( // CurrentProviderInfo returns a generic oidc provider implementation. func CurrentProviderInfo() ProviderInfoer { - return noprovider.NoProvider{} + return noprovider.New() } diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 9ac5a5688f..3c6f885cc5 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "log/slog" + "slices" "strings" "github.com/Azure/azure-sdk-for-go/sdk/azcore" @@ -16,6 +17,7 @@ import ( msgraphauth "github.com/microsoftgraph/msgraph-sdk-go-core/authentication" msgraphgroups "github.com/microsoftgraph/msgraph-sdk-go/groups" msgraphmodels "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/ubuntu/authd-oidc-brokers/internal/consts" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "golang.org/x/oauth2" ) @@ -27,7 +29,16 @@ func init() { const localGroupPrefix = "linux-" // Provider is the Microsoft Entra ID provider implementation. -type Provider struct{} +type Provider struct { + expectedScopes []string +} + +// New returns a new MSEntraID provider. +func New() Provider { + return Provider{ + expectedScopes: append(consts.DefaultScopes, "GroupMember.Read.All", "User.Read"), + } +} // AdditionalScopes returns the generic scopes required by the EntraID provider. func (p Provider) AdditionalScopes() []string { @@ -39,6 +50,23 @@ func (p Provider) AuthOptions() []oauth2.AuthCodeOption { return []oauth2.AuthCodeOption{} } +// CheckTokenScopes checks if the token has the required scopes. +func (p Provider) CheckTokenScopes(token *oauth2.Token) error { + scopesStr, ok := token.Extra("scope").(string) + if !ok { + return fmt.Errorf("failed to cast token scopes to string: %v", token.Extra("scope")) + } + + scopes := strings.Split(scopesStr, " ") + var errs []error + for _, s := range p.expectedScopes { + if !slices.Contains(scopes, s) { + errs = append(errs, fmt.Errorf("token is missing scope %q", s)) + } + } + return errors.Join(errs...) +} + // GetUserInfo is a no-op when no specific provider is in use. func (p Provider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken) (info.User, error) { userClaims, err := p.userClaims(idToken) diff --git a/internal/providers/noprovider/noprovider.go b/internal/providers/noprovider/noprovider.go index 6f2ac9c508..1c64079bc4 100644 --- a/internal/providers/noprovider/noprovider.go +++ b/internal/providers/noprovider/noprovider.go @@ -14,6 +14,17 @@ import ( // NoProvider is a generic OIDC provider. type NoProvider struct{} +// New returns a new NoProvider. +func New() NoProvider { + return NoProvider{} +} + +// CheckTokenScopes should check the token scopes, but we're not sure +// if there is a generic way to do this, so for now it's a no-op. +func (p NoProvider) CheckTokenScopes(token *oauth2.Token) error { + return nil +} + // AdditionalScopes returns the generic scopes required by the provider. func (p NoProvider) AdditionalScopes() []string { return []string{oidc.ScopeOfflineAccess} diff --git a/internal/providers/providers.go b/internal/providers/providers.go index 94dc58c538..e2b29fd75d 100644 --- a/internal/providers/providers.go +++ b/internal/providers/providers.go @@ -13,6 +13,7 @@ import ( type ProviderInfoer interface { AdditionalScopes() []string AuthOptions() []oauth2.AuthCodeOption + CheckTokenScopes(token *oauth2.Token) error CurrentAuthenticationModesOffered( sessionMode string, supportedAuthModes map[string]string, diff --git a/internal/providers/withmsentraid.go b/internal/providers/withmsentraid.go index a45dcc8b16..3d29d7e98e 100644 --- a/internal/providers/withmsentraid.go +++ b/internal/providers/withmsentraid.go @@ -8,5 +8,5 @@ import ( // CurrentProviderInfo returns a Microsoft Entra ID provider implementation. func CurrentProviderInfo() ProviderInfoer { - return msentraid.Provider{} + return msentraid.New() } diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index 221a49aa85..95a36be935 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -8,15 +8,19 @@ import ( "net" "net/http" "net/http/httptest" + "slices" "strconv" "strings" "time" "github.com/coreos/go-oidc/v3/oidc" + "github.com/ubuntu/authd-oidc-brokers/internal/consts" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "golang.org/x/oauth2" ) +var scopes = []string{"offline_access", "openid", "profile"} + // ProviderHandler is a function that handles a request to the mock provider. type ProviderHandler func(http.ResponseWriter, *http.Request) @@ -151,10 +155,10 @@ func DefaultTokenHandler(serverURL string) ProviderHandler { "access_token": "accesstoken", "refresh_token": "refreshtoken", "token_type": "Bearer", - "scope": "offline_access openid profile", + "scope": "%s", "expires_in": 3600, "id_token": "%s" - }`, rawToken) + }`, strings.Join(scopes, " "), rawToken) w.Header().Add("Content-Type", "application/json") _, err := w.Write([]byte(response)) @@ -225,6 +229,23 @@ type MockProviderInfoer struct { GroupsErr bool } +// CheckTokenScopes checks if the token has the required scopes. +func (p *MockProviderInfoer) CheckTokenScopes(token *oauth2.Token) error { + scopesStr, ok := token.Extra("scope").(string) + if !ok { + return fmt.Errorf("failed to cast token scopes to string: %v", token.Extra("scope")) + } + + scopes := strings.Split(scopesStr, " ") + var errs []error + for _, s := range consts.DefaultScopes { + if !slices.Contains(scopes, s) { + errs = append(errs, fmt.Errorf("token is missing scope %q", s)) + } + } + return errors.Join(errs...) +} + // AdditionalScopes returns the additional scopes required by the provider. func (p *MockProviderInfoer) AdditionalScopes() []string { if p.Scopes != nil { From f59dd48449801efa663b7e8d300957656adebaaa Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Mon, 2 Sep 2024 17:40:23 +0200 Subject: [PATCH 0133/1670] TestCheckTokenScopes for token API Add package tests for msentraid. Co-authored-by: Adrian Dombeck --- internal/providers/msentraid/export_test.go | 8 +++ .../providers/msentraid/msentraid_test.go | 53 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 internal/providers/msentraid/export_test.go create mode 100644 internal/providers/msentraid/msentraid_test.go diff --git a/internal/providers/msentraid/export_test.go b/internal/providers/msentraid/export_test.go new file mode 100644 index 0000000000..8b28309c74 --- /dev/null +++ b/internal/providers/msentraid/export_test.go @@ -0,0 +1,8 @@ +package msentraid + +import "strings" + +// AllExpectedScopes returns all the default expected scopes for a new provider. +func AllExpectedScopes() string { + return strings.Join(New().expectedScopes, " ") +} diff --git a/internal/providers/msentraid/msentraid_test.go b/internal/providers/msentraid/msentraid_test.go new file mode 100644 index 0000000000..8d5e80d084 --- /dev/null +++ b/internal/providers/msentraid/msentraid_test.go @@ -0,0 +1,53 @@ +package msentraid_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/msentraid" + "golang.org/x/oauth2" +) + +func TestNew(t *testing.T) { + p := msentraid.New() + + require.NotEmpty(t, p, "New should return a non-empty provider") +} + +func TestCheckTokenScopes(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + scopes string + noExtraScopeField bool + + wantErr bool + }{ + "success when checking all scopes are present": {scopes: msentraid.AllExpectedScopes()}, + "success even if getting more scopes than requested": {scopes: msentraid.AllExpectedScopes() + " extra-scope"}, + + "error with missing scopes": {scopes: "profile email", wantErr: true}, + "error without extra scope field": {noExtraScopeField: true, wantErr: true}, + "error with empty scopes": {scopes: "", wantErr: true}, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + p := msentraid.New() + + token := &oauth2.Token{} + if !tc.noExtraScopeField { + token = token.WithExtra(map[string]interface{}{"scope": any(tc.scopes)}) + } + + err := p.CheckTokenScopes(token) + if tc.wantErr { + require.Error(t, err, "CheckTokenScopes should return an error") + return + } + + require.NoError(t, err, "CheckTokenScopes should not return an error") + }) + } +} From 66d20ef91b51fc1f3d5688739c195b88ac016d93 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 2 Sep 2024 22:10:02 +0200 Subject: [PATCH 0134/1670] Test CheckTokenScopes in TestIsAuthenticated --- internal/broker/broker_test.go | 11 ++++++++++- .../cache/provider_url/test-user@email.com.cache | 1 + .../first_call | 3 +++ .../second_call | 3 +++ internal/providers/msentraid/msentraid.go | 9 ++++++--- internal/testutils/provider.go | 15 ++++++++------- 6 files changed, 31 insertions(+), 11 deletions(-) create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/cache/provider_url/test-user@email.com.cache create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/second_call diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 012c3da16b..51c85be51d 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -360,6 +360,7 @@ func TestIsAuthenticated(t *testing.T) { badFirstKey bool customHandlers map[string]testutils.ProviderHandler + address string wantSecondCall bool secondChallenge string @@ -388,6 +389,14 @@ func TestIsAuthenticated(t *testing.T) { "/.well-known/openid-configuration": testutils.UnavailableHandler(), }, }, + "Authenticating still allowed if token is missing scopes": { + firstChallenge: "-", + wantSecondCall: true, + customHandlers: map[string]testutils.ProviderHandler{ + "/token": testutils.DefaultTokenHandler("http://127.0.0.1:31313", []string{}), + }, + address: "127.0.0.1:31313", + }, "Error when authentication data is invalid": {invalidAuthData: true}, "Error when challenge can not be decrypted": {firstMode: "password", badFirstKey: true}, @@ -466,7 +475,7 @@ func TestIsAuthenticated(t *testing.T) { for path, handler := range tc.customHandlers { opts = append(opts, testutils.WithHandler(path, handler)) } - p, cleanup := testutils.StartMockProvider("", opts...) + p, cleanup := testutils.StartMockProvider(tc.address, opts...) t.Cleanup(cleanup) provider = p } diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/cache/provider_url/test-user@email.com.cache new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/cache/provider_url/test-user@email.com.cache @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/first_call new file mode 100644 index 0000000000..d0887a134f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/first_call @@ -0,0 +1,3 @@ +access: next +data: '{}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/second_call new file mode 100644 index 0000000000..3c4c96bc15 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/second_call @@ -0,0 +1,3 @@ +access: granted +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +err: diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 3c6f885cc5..d8a7aa8f51 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -58,13 +58,16 @@ func (p Provider) CheckTokenScopes(token *oauth2.Token) error { } scopes := strings.Split(scopesStr, " ") - var errs []error + var missingScopes []string for _, s := range p.expectedScopes { if !slices.Contains(scopes, s) { - errs = append(errs, fmt.Errorf("token is missing scope %q", s)) + missingScopes = append(missingScopes, s) } } - return errors.Join(errs...) + if len(missingScopes) > 0 { + return fmt.Errorf("missing required scopes: %s", strings.Join(missingScopes, ", ")) + } + return nil } // GetUserInfo is a no-op when no specific provider is in use. diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index 95a36be935..be9d370b6a 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -19,8 +19,6 @@ import ( "golang.org/x/oauth2" ) -var scopes = []string{"offline_access", "openid", "profile"} - // ProviderHandler is a function that handles a request to the mock provider. type ProviderHandler func(http.ResponseWriter, *http.Request) @@ -56,7 +54,7 @@ func StartMockProvider(address string, args ...OptionProvider) (*httptest.Server handlers: map[string]ProviderHandler{ "/.well-known/openid-configuration": DefaultOpenIDHandler(server.URL), "/device_auth": DefaultDeviceAuthHandler(), - "/token": DefaultTokenHandler(server.URL), + "/token": DefaultTokenHandler(server.URL, consts.DefaultScopes), }, } for _, arg := range args { @@ -132,7 +130,7 @@ func DefaultDeviceAuthHandler() ProviderHandler { } // DefaultTokenHandler returns a handler that returns a default token response. -func DefaultTokenHandler(serverURL string) ProviderHandler { +func DefaultTokenHandler(serverURL string, scopes []string) ProviderHandler { return func(w http.ResponseWriter, r *http.Request) { // Mimics user going through auth process time.Sleep(2 * time.Second) @@ -237,13 +235,16 @@ func (p *MockProviderInfoer) CheckTokenScopes(token *oauth2.Token) error { } scopes := strings.Split(scopesStr, " ") - var errs []error + var missingScopes []string for _, s := range consts.DefaultScopes { if !slices.Contains(scopes, s) { - errs = append(errs, fmt.Errorf("token is missing scope %q", s)) + missingScopes = append(missingScopes, s) } } - return errors.Join(errs...) + if len(missingScopes) > 0 { + return fmt.Errorf("missing required scopes: %s", strings.Join(missingScopes, ", ")) + } + return nil } // AdditionalScopes returns the additional scopes required by the provider. From b98684adfec4ef4e1e627f2562d6f8f1fe3ed538 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 08:18:38 +0000 Subject: [PATCH 0135/1670] deps(ci): bump peter-evans/create-pull-request from 6 to 7 Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 6 to 7. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/v6...v7) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/auto-updates.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/auto-updates.yml b/.github/workflows/auto-updates.yml index 5c34c0cfe1..87715a6cdc 100644 --- a/.github/workflows/auto-updates.yml +++ b/.github/workflows/auto-updates.yml @@ -74,7 +74,7 @@ jobs: git commit -m "Regenerate consts for ${{ matrix.branch_name }}" - name: Create Pull Request if: ${{ steps.merge.outputs.has_conflicts == 'false' }} - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v7 with: commit-message: Auto update ${{ matrix.branch_name }} branch title: Auto update ${{ matrix.branch_name }} branch @@ -99,7 +99,7 @@ jobs: git reset --hard main - name: Create Pull Request if: ${{ steps.merge.outputs.has_conflicts == 'true' }} - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v7 with: commit-message: Auto update ${{ matrix.branch_name }} branch title: Auto update ${{ matrix.branch_name }} branch From 9a9defa95b33883776848045770bd4d925621e03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 08:31:18 +0000 Subject: [PATCH 0136/1670] deps(go): bump golang.org/x/crypto from 0.26.0 to 0.27.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.26.0 to 0.27.0. - [Commits](https://github.com/golang/crypto/compare/v0.26.0...v0.27.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index ab98088d11..16445caaf7 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/stretchr/testify v1.9.0 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 - golang.org/x/crypto v0.26.0 + golang.org/x/crypto v0.27.0 golang.org/x/oauth2 v0.22.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 @@ -64,6 +64,6 @@ require ( golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/net v0.27.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect ) diff --git a/go.sum b/go.sum index d90af1c1ba..7f82ebedc1 100644 --- a/go.sum +++ b/go.sum @@ -130,8 +130,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -153,16 +153,16 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc 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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From 60a81a5169762f68b82cf9e52002aca430883585 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 10:33:29 +0000 Subject: [PATCH 0137/1670] deps(go): bump golang.org/x/oauth2 from 0.22.0 to 0.23.0 Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.22.0 to 0.23.0. - [Commits](https://github.com/golang/oauth2/compare/v0.22.0...v0.23.0) --- updated-dependencies: - dependency-name: golang.org/x/oauth2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 16445caaf7..4741d4fff0 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 golang.org/x/crypto v0.27.0 - golang.org/x/oauth2 v0.22.0 + golang.org/x/oauth2 v0.23.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 7f82ebedc1..35071dfb95 100644 --- a/go.sum +++ b/go.sum @@ -140,8 +140,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= -golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= From c602d18a0a7f597430558f95f6308aefbb750855 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 5 Sep 2024 12:21:41 +0200 Subject: [PATCH 0138/1670] Only fetch user's groups, not directory roles and administrative units When the user account is a guest account on the tenant, the existing permissions are not enough to list all the memberships, so the `TransitiveMemberOf().Get()` call results in a permission denied error. The existing permissions are enough to list only the groups though, and that's the only thing we're interested in. This commit also removes the call to `client.Groups().Get()` which guest accounts are not permitted to use without additional privileges. That call was introduced in fe43cccb81f54713fdb3c9995e0c8e19cec9da9a to check for the `GroupMember.Read.All` permission and fail with a helpful error message when it's missing. Instead, we now check if the permission is present in the `scopes` field of the access token. It's safe to do so because the permissions in the `scopes` field are valid even if the permissions of the app were changed (so if an admin removes the `GroupMember.Read.All` permission from the app, existing non-expired access tokens still have that permission). --- internal/broker/broker.go | 17 ++++++-- internal/providers/errors/errors.go | 20 +++++++++ internal/providers/msentraid/msentraid.go | 49 ++++++++++++----------- 3 files changed, 60 insertions(+), 26 deletions(-) create mode 100644 internal/providers/errors/errors.go diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 8aff04e6e8..db4d04d45e 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -22,6 +22,7 @@ import ( "github.com/google/uuid" "github.com/ubuntu/authd-oidc-brokers/internal/consts" "github.com/ubuntu/authd-oidc-brokers/internal/providers" + providerErrors "github.com/ubuntu/authd-oidc-brokers/internal/providers/errors" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "github.com/ubuntu/decorate" "golang.org/x/oauth2" @@ -468,7 +469,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo authInfo.UserInfo, err = b.fetchUserInfo(ctx, session, &authInfo) if err != nil { slog.Error(err.Error()) - return AuthDenied, errorMessage{Message: "could not fetch user info"} + return AuthDenied, errorMessageForDisplay(err, "could not fetch user info") } session.authInfo["auth_info"] = authInfo @@ -485,7 +486,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo authInfo.UserInfo, err = b.fetchUserInfo(ctx, session, &authInfo) if err != nil { slog.Error(err.Error()) - return AuthDenied, errorMessage{Message: "could not fetch user info"} + return AuthDenied, errorMessageForDisplay(err, "could not fetch user info") } } @@ -707,7 +708,7 @@ func (b *Broker) fetchUserInfo(ctx context.Context, session *sessionInfo, t *aut userInfo, err = b.providerInfo.GetUserInfo(ctx, t.Token, idToken) if err != nil { - return info.User{}, fmt.Errorf("could not get user info: %v", err) + return info.User{}, fmt.Errorf("could not get user info: %w", err) } // Some providers are case-insensitive, but we are not. So we need to lowercase the returned username. @@ -735,3 +736,13 @@ func decorateErrorMessage(data *isAuthenticatedDataResponse, msg string) { errMsg.Message = fmt.Sprintf("%s: %s", msg, errMsg.Message) *data = errMsg } + +// Checks if the provided error is of type ForDisplayError. If it is, it returns the error message. Else, it returns +// the provided fallback message. +func errorMessageForDisplay(err error, fallback string) errorMessage { + var e *providerErrors.ForDisplayError + if errors.As(err, &e) { + return errorMessage{Message: e.Error()} + } + return errorMessage{Message: fallback} +} diff --git a/internal/providers/errors/errors.go b/internal/providers/errors/errors.go new file mode 100644 index 0000000000..7ca2f7f055 --- /dev/null +++ b/internal/providers/errors/errors.go @@ -0,0 +1,20 @@ +// Package errors provides custom error types which can be returned by the providers +package errors + +import ( + "fmt" +) + +// ForDisplayError is an error type for errors that are meant to be displayed to the user. +type ForDisplayError struct { + message string +} + +func (e ForDisplayError) Error() string { + return e.message +} + +// NewForDisplayError creates a new ForDisplayError with the given format and arguments. +func NewForDisplayError(format string, v ...interface{}) ForDisplayError { + return ForDisplayError{message: fmt.Sprintf(format, v...)} +} diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index d8a7aa8f51..1ff1dd0d6d 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -15,9 +15,9 @@ import ( "github.com/k0kubun/pp" msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go" msgraphauth "github.com/microsoftgraph/msgraph-sdk-go-core/authentication" - msgraphgroups "github.com/microsoftgraph/msgraph-sdk-go/groups" msgraphmodels "github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/ubuntu/authd-oidc-brokers/internal/consts" + providerErrors "github.com/ubuntu/authd-oidc-brokers/internal/providers/errors" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "golang.org/x/oauth2" ) @@ -52,12 +52,11 @@ func (p Provider) AuthOptions() []oauth2.AuthCodeOption { // CheckTokenScopes checks if the token has the required scopes. func (p Provider) CheckTokenScopes(token *oauth2.Token) error { - scopesStr, ok := token.Extra("scope").(string) - if !ok { - return fmt.Errorf("failed to cast token scopes to string: %v", token.Extra("scope")) + scopes, err := p.getTokenScopes(token) + if err != nil { + return err } - scopes := strings.Split(scopesStr, " ") var missingScopes []string for _, s := range p.expectedScopes { if !slices.Contains(scopes, s) { @@ -70,6 +69,14 @@ func (p Provider) CheckTokenScopes(token *oauth2.Token) error { return nil } +func (p Provider) getTokenScopes(token *oauth2.Token) ([]string, error) { + scopesStr, ok := token.Extra("scope").(string) + if !ok { + return nil, fmt.Errorf("failed to cast token scopes to string: %v", token.Extra("scope")) + } + return strings.Split(scopesStr, " "), nil +} + // GetUserInfo is a no-op when no specific provider is in use. func (p Provider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken) (info.User, error) { userClaims, err := p.userClaims(idToken) @@ -111,6 +118,15 @@ func (p Provider) userClaims(idToken *oidc.IDToken) (claims, error) { // getGroups access the Microsoft Graph API to get the groups the user is a member of. func (p Provider) getGroups(token *oauth2.Token) ([]info.Group, error) { + // Check if the token has the GroupMember.Read.All scope + scopes, err := p.getTokenScopes(token) + if err != nil { + return nil, err + } + if !slices.Contains(scopes, "GroupMember.Read.All") { + return nil, providerErrors.NewForDisplayError("the Microsoft Entra ID app is missing the GroupMember.Read.All permission") + } + cred := azureTokenCredential{token: token} auth, err := msgraphauth.NewAzureIdentityAuthenticationProvider(cred) if err != nil { @@ -124,28 +140,15 @@ func (p Provider) getGroups(token *oauth2.Token) ([]info.Group, error) { client := msgraphsdk.NewGraphServiceClient(adapter) - // Check GroupMember.Read.All access - var topOne int32 = 1 - requestOptions := &msgraphgroups.GroupsRequestBuilderGetRequestConfiguration{ - QueryParameters: &msgraphgroups.GroupsRequestBuilderGetQueryParameters{ - Top: &topOne, // Limit to only one group - }, - } - if _, err = client.Groups().Get(context.Background(), requestOptions); err != nil { - return nil, fmt.Errorf("failed to list groups: %v", err) - } - - m, err := client.Me().TransitiveMemberOf().Get(context.Background(), nil) + // Get the groups (only the groups, not directory roles or administrative units, because that would require + // additional permissions) which the user is a member of. + graphGroups, err := client.Me().TransitiveMemberOf().GraphGroup().Get(context.Background(), nil) if err != nil { - return nil, fmt.Errorf("failed to get user's groups: %v", err) - } - if m == nil { - slog.Debug("Got nil response from Microsoft Graph API for user's groups, assuming that user is not a member of any group.") - return []info.Group{}, nil + return nil, fmt.Errorf("failed to get user groups: %v", err) } var groups []info.Group - for _, obj := range m.GetValue() { + for _, obj := range graphGroups.GetValue() { unknown := "Unknown" msGroup, ok := obj.(*msgraphmodels.Group) if !ok { From 2b702bbfd875fc609a4e8a74b6fd0f3e0947d93d Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 10 Sep 2024 05:46:40 -0400 Subject: [PATCH 0139/1670] Fix test for unreachable provider on password login --- internal/broker/broker_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 51c85be51d..81759328fa 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -379,7 +379,7 @@ func TestIsAuthenticated(t *testing.T) { firstMode: "password", preexistentToken: "valid", customHandlers: map[string]testutils.ProviderHandler{ - "/token": testutils.UnavailableHandler(), + "/.well-known/openid-configuration": testutils.UnavailableHandler(), }, }, "Authenticating with password still allowed if token is expired and server is unreachable": { From 30d23aca2baedb29b3c0c60e87d8a300c0ff50b4 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 10 Sep 2024 05:48:01 -0400 Subject: [PATCH 0140/1670] Update user info even on local authentication We used to rely on the cached information if the access token was still valid. Now, we always try to fetch the user information (username, groups, etc) if the session is online, even if we did not refresh the token. --- internal/broker/broker.go | 2 +- .../successfully_authenticate_user_with_password/first_call | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index db4d04d45e..e474ed6803 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -482,7 +482,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo return AuthRetry, errorMessage{Message: "could not load cached info"} } - if authInfo.UserInfo.Name == "" { + if !session.isOffline { authInfo.UserInfo, err = b.fetchUserInfo(ctx, session, &authInfo) if err != nil { slog.Error(err.Error()) diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call index 2928c3a331..cc046e18ae 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"saved-remote-group","ugid":"12345"},{"name":"saved-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' err: From b2de44fee278659f9c965b1543ef122188440d8a Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 10 Sep 2024 16:39:27 +0200 Subject: [PATCH 0141/1670] Revert "Update user info even on local authentication" Commit 30d23aca2baedb29b3c0c60e87d8a300c0ff50b4 broke local authentication when the access token is _not_ expired. With that commit, we tried to get the groups with the locally stored token, which in case of the MSEntraID provider, returns an error when the scopes field doesn't contain the GroupMember.Read.All scope. The problem is that we don't store the extra scopes field of the token locally, so it always fails with: could not get user info: failed to cast token scopes to string: . This reverts commit 30d23aca2baedb29b3c0c60e87d8a300c0ff50b4. --- internal/broker/broker.go | 2 +- .../successfully_authenticate_user_with_password/first_call | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index e474ed6803..db4d04d45e 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -482,7 +482,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo return AuthRetry, errorMessage{Message: "could not load cached info"} } - if !session.isOffline { + if authInfo.UserInfo.Name == "" { authInfo.UserInfo, err = b.fetchUserInfo(ctx, session, &authInfo) if err != nil { slog.Error(err.Error()) diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call index cc046e18ae..2928c3a331 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"saved-remote-group","ugid":"12345"},{"name":"saved-local-group","ugid":""}]}}' err: From 3f11a956038099e699aa3e47cfe05ba28343cc73 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 3 Sep 2024 19:50:23 +0200 Subject: [PATCH 0142/1670] Move username comparison to provider The decision whether two usernames are considered equal is provider-specific. For example, some providers have case-insensitive usernames. UDENG-4414 --- internal/broker/broker.go | 5 ++- internal/broker/broker_test.go | 1 - internal/broker/helper_test.go | 2 -- internal/providers/msentraid/msentraid.go | 9 ++++++ .../providers/msentraid/msentraid_test.go | 31 +++++++++++++++++++ internal/providers/noprovider/noprovider.go | 8 +++++ internal/providers/providers.go | 1 + internal/testutils/provider.go | 8 +++++ 8 files changed, 59 insertions(+), 6 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index db4d04d45e..3e280842a5 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -711,9 +711,8 @@ func (b *Broker) fetchUserInfo(ctx context.Context, session *sessionInfo, t *aut return info.User{}, fmt.Errorf("could not get user info: %w", err) } - // Some providers are case-insensitive, but we are not. So we need to lowercase the returned username. - if !strings.EqualFold(userInfo.Name, session.username) { - return info.User{}, fmt.Errorf("returned user %q does not match the selected one %q", userInfo.Name, session.username) + if err = b.providerInfo.VerifyUsername(session.username, userInfo.Name); err != nil { + return info.User{}, fmt.Errorf("username verification failed: %w", err) } // This means that home was not provided by the claims, so we need to set it to the broker default. diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 81759328fa..07d22f9762 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -736,7 +736,6 @@ func TestFetchUserInfo(t *testing.T) { "Successfully fetch user info with groups": {}, "Successfully fetch user info without groups": {emptyGroups: true}, "Successfully fetch user info with default home when not provided": {emptyHomeDir: true}, - "Successfully fetch user info ignoring different casing in name": {userToken: "uppercased-name"}, "Error when token can not be validated": {userToken: "invalid", wantErr: true}, "Error when ID token claims are invalid": {userToken: "invalid-id", wantErr: true}, diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index d6c0abde4d..8ab458cc11 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -133,8 +133,6 @@ func generateCachedInfo(t *testing.T, preexistentToken, issuer string) *broker.A username = "" case "other-name": username = "other-user@email.com" - case "uppercased-name": - username = "TEST-USER@EMAIL.COM" default: username = "test-user@email.com" } diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 1ff1dd0d6d..511396a63d 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -247,6 +247,15 @@ func (p Provider) CurrentAuthenticationModesOffered( return offeredModes, nil } +// VerifyUsername checks if the authenticated username matches the requested username. +func (p Provider) VerifyUsername(requestedUsername, authenticatedUsername string) error { + // Microsoft Entra usernames are case-insensitive. + if !strings.EqualFold(requestedUsername, authenticatedUsername) { + return fmt.Errorf("requested username %q does not match the authenticated user %q", requestedUsername, authenticatedUsername) + } + return nil +} + type azureTokenCredential struct { token *oauth2.Token } diff --git a/internal/providers/msentraid/msentraid_test.go b/internal/providers/msentraid/msentraid_test.go index 8d5e80d084..f839dee776 100644 --- a/internal/providers/msentraid/msentraid_test.go +++ b/internal/providers/msentraid/msentraid_test.go @@ -51,3 +51,34 @@ func TestCheckTokenScopes(t *testing.T) { }) } } + +func TestVerifyUsername(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + requestedUsername string + authenticatedUser string + + wantErr bool + }{ + "Success when usernames are the same": {requestedUsername: "foo@bar", authenticatedUser: "foo@bar"}, + "Success when usernames differ in case": {requestedUsername: "foo@bar", authenticatedUser: "Foo@bar"}, + + "Error when usernames differ": {requestedUsername: "foo@bar", authenticatedUser: "bar@foo", wantErr: true}, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + p := msentraid.New() + + err := p.VerifyUsername(tc.requestedUsername, tc.authenticatedUser) + if tc.wantErr { + require.Error(t, err, "VerifyUsername should return an error") + return + } + + require.NoError(t, err, "VerifyUsername should not return an error") + }) + } +} diff --git a/internal/providers/noprovider/noprovider.go b/internal/providers/noprovider/noprovider.go index 1c64079bc4..670031ffc7 100644 --- a/internal/providers/noprovider/noprovider.go +++ b/internal/providers/noprovider/noprovider.go @@ -98,6 +98,14 @@ func (p NoProvider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, ), nil } +// VerifyUsername checks if the requested username matches the authenticated user. +func (p NoProvider) VerifyUsername(requestedUsername, username string) error { + if requestedUsername != username { + return fmt.Errorf("requested username %q does not match the authenticated user %q", requestedUsername, username) + } + return nil +} + type claims struct { PreferredUserName string `json:"preferred_username"` Sub string `json:"sub"` diff --git a/internal/providers/providers.go b/internal/providers/providers.go index e2b29fd75d..b73628a7e2 100644 --- a/internal/providers/providers.go +++ b/internal/providers/providers.go @@ -23,4 +23,5 @@ type ProviderInfoer interface { currentAuthStep int, ) ([]string, error) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken) (info.User, error) + VerifyUsername(requestedUsername, authenticatedUsername string) error } diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index be9d370b6a..9febb988c4 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -363,3 +363,11 @@ func (p MockProviderInfoer) CurrentAuthenticationModesOffered( return offeredModes, nil } + +// VerifyUsername checks if the requested username matches the authenticated user. +func (p *MockProviderInfoer) VerifyUsername(requestedUsername, username string) error { + if requestedUsername != username { + return fmt.Errorf("requested username %q does not match the authenticated user %q", requestedUsername, username) + } + return nil +} From 62182901d79b0cfffab42f4202fc660d2ac0c9b1 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 12 Sep 2024 13:49:44 +0200 Subject: [PATCH 0143/1670] Capitalize test names for consistency --- internal/providers/msentraid/msentraid_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/providers/msentraid/msentraid_test.go b/internal/providers/msentraid/msentraid_test.go index f839dee776..ace499aa4e 100644 --- a/internal/providers/msentraid/msentraid_test.go +++ b/internal/providers/msentraid/msentraid_test.go @@ -23,12 +23,12 @@ func TestCheckTokenScopes(t *testing.T) { wantErr bool }{ - "success when checking all scopes are present": {scopes: msentraid.AllExpectedScopes()}, - "success even if getting more scopes than requested": {scopes: msentraid.AllExpectedScopes() + " extra-scope"}, + "Success when checking all scopes are present": {scopes: msentraid.AllExpectedScopes()}, + "Success even if getting more scopes than requested": {scopes: msentraid.AllExpectedScopes() + " extra-scope"}, - "error with missing scopes": {scopes: "profile email", wantErr: true}, - "error without extra scope field": {noExtraScopeField: true, wantErr: true}, - "error with empty scopes": {scopes: "", wantErr: true}, + "Error with missing scopes": {scopes: "profile email", wantErr: true}, + "Error without extra scope field": {noExtraScopeField: true, wantErr: true}, + "Error with empty scopes": {scopes: "", wantErr: true}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { From 4b7f8f35d89ba2a9720a691f133a323015c9c827 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 12 Sep 2024 13:35:23 +0200 Subject: [PATCH 0144/1670] Check that usernames comply with the Microsoft Entra username policy We must avoid that multiple users of the provider map to the same local user. We currently use `strings.EqualFold` to compare the usernames, which considers some different Unicode characters the same (for example the "KELVIN SIGN" (U+212A) and the ASCII "K"). To avoid that, we considered using a custom `toLowercase` function which only converts ASCII characters to lowercase. However, we can just check if the username is valid under the Microsoft Entra username policy. Microsoft Entra enforces the username policy for the User Principal Name (UPN). We currently don't use the UPN as the username though, we use the `preferred_username` field from the ID token. The value of that field is equal to the UPN in some cases, in other cases it's equal to the user's email address. It's not clear if the username policy is enforced for all possible values of the `preferred_username` field. So to ensure that we don't have to care about the Unicode case-folding when comparing the usernames, we enforce the username policy ourselves. UDENG-4517 --- internal/providers/msentraid/msentraid.go | 20 +++++++++++++++++-- .../providers/msentraid/msentraid_test.go | 6 ++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 511396a63d..6f640b2f80 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "log/slog" + "regexp" "slices" "strings" @@ -247,12 +248,27 @@ func (p Provider) CurrentAuthenticationModesOffered( return offeredModes, nil } -// VerifyUsername checks if the authenticated username matches the requested username. +// VerifyUsername checks if the authenticated username matches the requested username and that both are valid. func (p Provider) VerifyUsername(requestedUsername, authenticatedUsername string) error { - // Microsoft Entra usernames are case-insensitive. + // Microsoft Entra usernames are case-insensitive. We can safely use strings.EqualFold here without worrying about + // different Unicode characters that fold to the same lowercase letter, because the Microsoft Entra username policy + // (which we checked above) ensures that the username only contains ASCII characters. if !strings.EqualFold(requestedUsername, authenticatedUsername) { return fmt.Errorf("requested username %q does not match the authenticated user %q", requestedUsername, authenticatedUsername) } + + // Check that the usernames only contain the characters allowed by the Microsoft Entra username policy + // https://learn.microsoft.com/en-us/entra/identity/authentication/concept-sspr-policy#username-policies + usernameRegexp := regexp.MustCompile(`^[a-zA-Z0-9'.-_!#^~]+$`) + if !usernameRegexp.MatchString(authenticatedUsername) { + // If this error occurs, we should investigate and probably relax the username policy, so we ask the user + // explicitly to report this error. + return providerErrors.NewForDisplayError("the authenticated username %q contains invalid characters. Please report this error on https://github.com/ubuntu/authd/issues", authenticatedUsername) + } + if !usernameRegexp.MatchString(requestedUsername) { + return fmt.Errorf("requested username %q contains invalid characters", requestedUsername) + } + return nil } diff --git a/internal/providers/msentraid/msentraid_test.go b/internal/providers/msentraid/msentraid_test.go index ace499aa4e..6cb793b349 100644 --- a/internal/providers/msentraid/msentraid_test.go +++ b/internal/providers/msentraid/msentraid_test.go @@ -65,6 +65,12 @@ func TestVerifyUsername(t *testing.T) { "Success when usernames differ in case": {requestedUsername: "foo@bar", authenticatedUser: "Foo@bar"}, "Error when usernames differ": {requestedUsername: "foo@bar", authenticatedUser: "bar@foo", wantErr: true}, + "Error when requested username contains invalid characters": { + requestedUsername: "fóó@bar", authenticatedUser: "foo@bar", wantErr: true, + }, + "Error when authenticated username contains invalid characters": { + requestedUsername: "foo@bar", authenticatedUser: "fóó@bar", wantErr: true, + }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { From 8a9586ad6eae8e1c5061f72541aa46d2f71d14f3 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 13 Sep 2024 13:39:39 +0200 Subject: [PATCH 0145/1670] Revert "Check that usernames comply with the Microsoft Entra username policy" Commit 4b7f8f35d89ba2a9720a691f133a323015c9c827 broke device authentication with Microsoft Entra because it doesn't allow "@" in the username, but all Microsoft Entra user principal names (and emails) include an "@". This reverts commit 4b7f8f35d89ba2a9720a691f133a323015c9c827. --- internal/providers/msentraid/msentraid.go | 20 ++----------------- .../providers/msentraid/msentraid_test.go | 6 ------ 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 6f640b2f80..511396a63d 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "log/slog" - "regexp" "slices" "strings" @@ -248,27 +247,12 @@ func (p Provider) CurrentAuthenticationModesOffered( return offeredModes, nil } -// VerifyUsername checks if the authenticated username matches the requested username and that both are valid. +// VerifyUsername checks if the authenticated username matches the requested username. func (p Provider) VerifyUsername(requestedUsername, authenticatedUsername string) error { - // Microsoft Entra usernames are case-insensitive. We can safely use strings.EqualFold here without worrying about - // different Unicode characters that fold to the same lowercase letter, because the Microsoft Entra username policy - // (which we checked above) ensures that the username only contains ASCII characters. + // Microsoft Entra usernames are case-insensitive. if !strings.EqualFold(requestedUsername, authenticatedUsername) { return fmt.Errorf("requested username %q does not match the authenticated user %q", requestedUsername, authenticatedUsername) } - - // Check that the usernames only contain the characters allowed by the Microsoft Entra username policy - // https://learn.microsoft.com/en-us/entra/identity/authentication/concept-sspr-policy#username-policies - usernameRegexp := regexp.MustCompile(`^[a-zA-Z0-9'.-_!#^~]+$`) - if !usernameRegexp.MatchString(authenticatedUsername) { - // If this error occurs, we should investigate and probably relax the username policy, so we ask the user - // explicitly to report this error. - return providerErrors.NewForDisplayError("the authenticated username %q contains invalid characters. Please report this error on https://github.com/ubuntu/authd/issues", authenticatedUsername) - } - if !usernameRegexp.MatchString(requestedUsername) { - return fmt.Errorf("requested username %q contains invalid characters", requestedUsername) - } - return nil } diff --git a/internal/providers/msentraid/msentraid_test.go b/internal/providers/msentraid/msentraid_test.go index 6cb793b349..ace499aa4e 100644 --- a/internal/providers/msentraid/msentraid_test.go +++ b/internal/providers/msentraid/msentraid_test.go @@ -65,12 +65,6 @@ func TestVerifyUsername(t *testing.T) { "Success when usernames differ in case": {requestedUsername: "foo@bar", authenticatedUser: "Foo@bar"}, "Error when usernames differ": {requestedUsername: "foo@bar", authenticatedUser: "bar@foo", wantErr: true}, - "Error when requested username contains invalid characters": { - requestedUsername: "fóó@bar", authenticatedUser: "foo@bar", wantErr: true, - }, - "Error when authenticated username contains invalid characters": { - requestedUsername: "foo@bar", authenticatedUser: "fóó@bar", wantErr: true, - }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { From dd2318f1a85f88de37567beb2b7a19f7a3d263c2 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 13 Sep 2024 13:45:29 +0200 Subject: [PATCH 0146/1670] Reapply "Check that usernames comply with the Microsoft Entra username policy" The next commit(s) will fix it. This reverts commit 8a9586ad6eae8e1c5061f72541aa46d2f71d14f3. --- internal/providers/msentraid/msentraid.go | 20 +++++++++++++++++-- .../providers/msentraid/msentraid_test.go | 6 ++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 511396a63d..6f640b2f80 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "log/slog" + "regexp" "slices" "strings" @@ -247,12 +248,27 @@ func (p Provider) CurrentAuthenticationModesOffered( return offeredModes, nil } -// VerifyUsername checks if the authenticated username matches the requested username. +// VerifyUsername checks if the authenticated username matches the requested username and that both are valid. func (p Provider) VerifyUsername(requestedUsername, authenticatedUsername string) error { - // Microsoft Entra usernames are case-insensitive. + // Microsoft Entra usernames are case-insensitive. We can safely use strings.EqualFold here without worrying about + // different Unicode characters that fold to the same lowercase letter, because the Microsoft Entra username policy + // (which we checked above) ensures that the username only contains ASCII characters. if !strings.EqualFold(requestedUsername, authenticatedUsername) { return fmt.Errorf("requested username %q does not match the authenticated user %q", requestedUsername, authenticatedUsername) } + + // Check that the usernames only contain the characters allowed by the Microsoft Entra username policy + // https://learn.microsoft.com/en-us/entra/identity/authentication/concept-sspr-policy#username-policies + usernameRegexp := regexp.MustCompile(`^[a-zA-Z0-9'.-_!#^~]+$`) + if !usernameRegexp.MatchString(authenticatedUsername) { + // If this error occurs, we should investigate and probably relax the username policy, so we ask the user + // explicitly to report this error. + return providerErrors.NewForDisplayError("the authenticated username %q contains invalid characters. Please report this error on https://github.com/ubuntu/authd/issues", authenticatedUsername) + } + if !usernameRegexp.MatchString(requestedUsername) { + return fmt.Errorf("requested username %q contains invalid characters", requestedUsername) + } + return nil } diff --git a/internal/providers/msentraid/msentraid_test.go b/internal/providers/msentraid/msentraid_test.go index ace499aa4e..6cb793b349 100644 --- a/internal/providers/msentraid/msentraid_test.go +++ b/internal/providers/msentraid/msentraid_test.go @@ -65,6 +65,12 @@ func TestVerifyUsername(t *testing.T) { "Success when usernames differ in case": {requestedUsername: "foo@bar", authenticatedUser: "Foo@bar"}, "Error when usernames differ": {requestedUsername: "foo@bar", authenticatedUser: "bar@foo", wantErr: true}, + "Error when requested username contains invalid characters": { + requestedUsername: "fóó@bar", authenticatedUser: "foo@bar", wantErr: true, + }, + "Error when authenticated username contains invalid characters": { + requestedUsername: "foo@bar", authenticatedUser: "fóó@bar", wantErr: true, + }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { From 29af6230509dd7f9f9ec6ea2b7e17e0721a64a16 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 13 Sep 2024 13:33:43 +0200 Subject: [PATCH 0147/1670] Allow "@" and "-" in usernames The Microsoft Entra username policy allows one "@" in it (which separates the username and the domain) and also "-", which we didn't escape properly before. --- internal/providers/msentraid/msentraid.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 6f640b2f80..3947d9f328 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -259,7 +259,7 @@ func (p Provider) VerifyUsername(requestedUsername, authenticatedUsername string // Check that the usernames only contain the characters allowed by the Microsoft Entra username policy // https://learn.microsoft.com/en-us/entra/identity/authentication/concept-sspr-policy#username-policies - usernameRegexp := regexp.MustCompile(`^[a-zA-Z0-9'.-_!#^~]+$`) + usernameRegexp := regexp.MustCompile(`^[a-zA-Z0-9'.\-_!#^~@]+$`) if !usernameRegexp.MatchString(authenticatedUsername) { // If this error occurs, we should investigate and probably relax the username policy, so we ask the user // explicitly to report this error. From bc8213a6fa5fbc3912a10e8574388667d354bdff Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 13 Sep 2024 13:55:41 +0200 Subject: [PATCH 0148/1670] Add regression test for UDENG-4563 --- internal/providers/msentraid/msentraid_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/providers/msentraid/msentraid_test.go b/internal/providers/msentraid/msentraid_test.go index 6cb793b349..5295e7ba74 100644 --- a/internal/providers/msentraid/msentraid_test.go +++ b/internal/providers/msentraid/msentraid_test.go @@ -61,15 +61,15 @@ func TestVerifyUsername(t *testing.T) { wantErr bool }{ - "Success when usernames are the same": {requestedUsername: "foo@bar", authenticatedUser: "foo@bar"}, - "Success when usernames differ in case": {requestedUsername: "foo@bar", authenticatedUser: "Foo@bar"}, + "Success when usernames are the same": {requestedUsername: "foo-bar@example", authenticatedUser: "foo-bar@example"}, + "Success when usernames differ in case": {requestedUsername: "foo-bar@example", authenticatedUser: "Foo-Bar@example"}, - "Error when usernames differ": {requestedUsername: "foo@bar", authenticatedUser: "bar@foo", wantErr: true}, + "Error when usernames differ": {requestedUsername: "foo@example", authenticatedUser: "bar@foo", wantErr: true}, "Error when requested username contains invalid characters": { - requestedUsername: "fóó@bar", authenticatedUser: "foo@bar", wantErr: true, + requestedUsername: "fóó@example", authenticatedUser: "foo@example", wantErr: true, }, "Error when authenticated username contains invalid characters": { - requestedUsername: "foo@bar", authenticatedUser: "fóó@bar", wantErr: true, + requestedUsername: "foo@example", authenticatedUser: "fóó@example", wantErr: true, }, } for name, tc := range tests { From 798912746e6f5d18deba7bd51091f11237e4403e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 08:10:51 +0000 Subject: [PATCH 0149/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.47.0 to 1.48.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.47.0...v1.48.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 4741d4fff0..6c06562f40 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/godbus/dbus/v5 v5.1.0 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.47.0 + github.com/microsoftgraph/msgraph-sdk-go v1.48.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 github.com/otiai10/copy v1.14.0 github.com/spf13/cobra v1.8.1 @@ -38,7 +38,7 @@ require ( github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect - github.com/microsoft/kiota-abstractions-go v1.6.1 // indirect + github.com/microsoft/kiota-abstractions-go v1.7.0 // indirect github.com/microsoft/kiota-authentication-azure-go v1.1.0 // indirect github.com/microsoft/kiota-http-go v1.4.4 // indirect github.com/microsoft/kiota-serialization-form-go v1.0.0 // indirect diff --git a/go.sum b/go.sum index 35071dfb95..91efaa8169 100644 --- a/go.sum +++ b/go.sum @@ -51,8 +51,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/microsoft/kiota-abstractions-go v1.6.1 h1:NXK50S3BwJn9Wj6bO0YFuAig7y2WVgdQ/ie1ktMl2J4= -github.com/microsoft/kiota-abstractions-go v1.6.1/go.mod h1:FI1I2OHg0E7bK5t8DPnw+9C/CHVyLP6XeqDBT+95pTE= +github.com/microsoft/kiota-abstractions-go v1.7.0 h1:/0OKSSEe94Z1qgpcGE7ZFI9P+4iAnsDQo9v9UOk+R8E= +github.com/microsoft/kiota-abstractions-go v1.7.0/go.mod h1:FI1I2OHg0E7bK5t8DPnw+9C/CHVyLP6XeqDBT+95pTE= github.com/microsoft/kiota-authentication-azure-go v1.1.0 h1:HudH57Enel9zFQ4TEaJw6lMiyZ5RbBdrRHwdU0NP2RY= github.com/microsoft/kiota-authentication-azure-go v1.1.0/go.mod h1:zfPFOiLdEqM77Hua5B/2vpcXrVaGqSWjHSRzlvAWEgc= github.com/microsoft/kiota-http-go v1.4.4 h1:HM0KT/Q7o+JsGatFkkbTIqJL24Jzo5eMI5NNe9N4TQ4= @@ -65,8 +65,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.0.0 h1:3O5sb5Zj+moLBiJy github.com/microsoft/kiota-serialization-multipart-go v1.0.0/go.mod h1:yauLeBTpANk4L03XD985akNysG24SnRJGaveZf+p4so= github.com/microsoft/kiota-serialization-text-go v1.0.0 h1:XOaRhAXy+g8ZVpcq7x7a0jlETWnWrEum0RhmbYrTFnA= github.com/microsoft/kiota-serialization-text-go v1.0.0/go.mod h1:sM1/C6ecnQ7IquQOGUrUldaO5wj+9+v7G2W3sQ3fy6M= -github.com/microsoftgraph/msgraph-sdk-go v1.47.0 h1:qXfmDij9md6mPsSAJjiDNmS4hxqKo0R489GiVMZVmmY= -github.com/microsoftgraph/msgraph-sdk-go v1.47.0/go.mod h1:Gnws5D7d/930uS9J4qlCm4BAR/zenqECMk9tgMDXeZQ= +github.com/microsoftgraph/msgraph-sdk-go v1.48.0 h1:JYKXW90/rjMzbD9kIzD+PzoBBbiNh4dRW6riKg+Bm94= +github.com/microsoftgraph/msgraph-sdk-go v1.48.0/go.mod h1:ghZjDbiV52URFLjZGFZckbLqMW1IgWvQKW4AqLgOm5E= github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 h1:P1wpmn3xxfPMFJHg+PJPcusErfRkl63h6OdAnpDbkS8= github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1/go.mod h1:vFmWQGWyLlhxCESNLv61vlE4qesBU+eWmEVH7DJSESA= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= From 25d47108d59f6d37635562a170b30002218b35fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Sep 2024 08:16:08 +0000 Subject: [PATCH 0150/1670] deps(go-tools): bump github.com/golangci/golangci-lint in /tools Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.60.3 to 1.61.0. - [Release notes](https://github.com/golangci/golangci-lint/releases) - [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md) - [Commits](https://github.com/golangci/golangci-lint/compare/v1.60.3...v1.61.0) --- updated-dependencies: - dependency-name: github.com/golangci/golangci-lint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- tools/go.mod | 32 +++++++++++----------- tools/go.sum | 76 ++++++++++++++++++++++++++-------------------------- 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index 5d9cfaf7f2..f6aaf03291 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -2,21 +2,21 @@ module github.com/ubuntu/authd-oidc-brokers/tools go 1.23.0 -require github.com/golangci/golangci-lint v1.60.3 +require github.com/golangci/golangci-lint v1.61.0 require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 4d63.com/gochecknoglobals v0.2.1 // indirect github.com/4meepo/tagalign v1.3.4 // indirect - github.com/Abirdcfly/dupword v0.0.14 // indirect + github.com/Abirdcfly/dupword v0.1.1 // indirect github.com/Antonboom/errname v0.1.13 // indirect github.com/Antonboom/nilnil v0.1.9 // indirect github.com/Antonboom/testifylint v1.4.3 // indirect github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect - github.com/Crocmagnon/fatcontext v0.4.0 // indirect + github.com/Crocmagnon/fatcontext v0.5.2 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect - github.com/Masterminds/semver/v3 v3.2.1 // indirect + github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect github.com/alecthomas/go-check-sumtype v0.1.4 // indirect github.com/alexkohler/nakedret/v2 v2.0.4 // indirect @@ -37,9 +37,9 @@ require ( github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/charithe/durationcheck v0.0.10 // indirect github.com/chavacava/garif v0.1.0 // indirect - github.com/ckaznocha/intrange v0.1.2 // indirect + github.com/ckaznocha/intrange v0.2.0 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect - github.com/daixiang0/gci v0.13.4 // indirect + github.com/daixiang0/gci v0.13.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect github.com/ettle/strcase v0.2.0 // indirect @@ -57,7 +57,7 @@ require ( github.com/go-toolsmith/astp v1.1.0 // indirect github.com/go-toolsmith/strparse v1.1.0 // indirect github.com/go-toolsmith/typep v1.1.0 // indirect - github.com/go-viper/mapstructure/v2 v2.0.0 // indirect + github.com/go-viper/mapstructure/v2 v2.1.0 // indirect github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gofrs/flock v0.12.1 // indirect @@ -114,25 +114,25 @@ require ( github.com/nunnatsa/ginkgolinter v0.16.2 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polyfloyd/go-errorlint v1.6.0 // indirect github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect - github.com/quasilyte/go-ruleguard v0.4.2 // indirect + github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect github.com/quasilyte/gogrep v0.5.0 // indirect github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect - github.com/ryancurrah/gomodguard v1.3.3 // indirect + github.com/ryancurrah/gomodguard v1.3.5 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.27.0 // indirect - github.com/securego/gosec/v2 v2.20.1-0.20240822074752-ab3f6c1c83a0 // indirect + github.com/securego/gosec/v2 v2.21.2 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect @@ -151,7 +151,7 @@ require ( github.com/stretchr/testify v1.9.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/tdakkota/asciicheck v0.2.0 // indirect - github.com/tetafro/godot v1.4.16 // indirect + github.com/tetafro/godot v1.4.17 // indirect github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect github.com/timonwong/loggercheck v0.9.4 // indirect github.com/tomarrell/wrapcheck/v2 v2.9.0 // indirect @@ -170,12 +170,12 @@ require ( go.uber.org/automaxprocs v1.5.3 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect + golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect - golang.org/x/mod v0.20.0 // indirect + golang.org/x/mod v0.21.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.23.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect golang.org/x/tools v0.24.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/tools/go.sum b/tools/go.sum index 750b93716f..de6bd9d31a 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -37,8 +37,8 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/4meepo/tagalign v1.3.4 h1:P51VcvBnf04YkHzjfclN6BbsopfJR5rxs1n+5zHt+w8= github.com/4meepo/tagalign v1.3.4/go.mod h1:M+pnkHH2vG8+qhE5bVc/zeP7HS/j910Fwa9TUSyZVI0= -github.com/Abirdcfly/dupword v0.0.14 h1:3U4ulkc8EUo+CaT105/GJ1BQwtgyj6+VaBVbAX11Ba8= -github.com/Abirdcfly/dupword v0.0.14/go.mod h1:VKDAbxdY8YbKUByLGg8EETzYSuC4crm9WwI6Y3S0cLI= +github.com/Abirdcfly/dupword v0.1.1 h1:Bsxe0fIw6OwBtXMIncaTxCLHYO5BB+3mcsR5E8VXloY= +github.com/Abirdcfly/dupword v0.1.1/go.mod h1:B49AcJdTYYkpd4HjgAcutNGG9HZ2JWwKunH9Y2BA6sM= github.com/Antonboom/errname v0.1.13 h1:JHICqsewj/fNckzrfVSe+T33svwQxmjC+1ntDsHOVvM= github.com/Antonboom/errname v0.1.13/go.mod h1:uWyefRYRN54lBg6HseYCFhs6Qjcy41Y3Jl/dVhA87Ns= github.com/Antonboom/nilnil v0.1.9 h1:eKFMejSxPSA9eLSensFmjW2XTgTwJMjZ8hUHtV4s/SQ= @@ -49,14 +49,14 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Crocmagnon/fatcontext v0.4.0 h1:4ykozu23YHA0JB6+thiuEv7iT6xq995qS1vcuWZq0tg= -github.com/Crocmagnon/fatcontext v0.4.0/go.mod h1:ZtWrXkgyfsYPzS6K3O88va6t2GEglG93vnII/F94WC0= +github.com/Crocmagnon/fatcontext v0.5.2 h1:vhSEg8Gqng8awhPju2w7MKHqMlg4/NI+gSDHtR3xgwA= +github.com/Crocmagnon/fatcontext v0.5.2/go.mod h1:87XhRMaInHP44Q7Tlc7jkgKKB7kZAOPiDkFMdKCC+74= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 h1:/fTUt5vmbkAcMBt4YQiuC23cV0kEsN1MVMNqeOW43cU= github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0/go.mod h1:ONJg5sxcbsdQQ4pOW8TGdTidT2TMAUy/2Xhr8mrYaao= -github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= -github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ= github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= @@ -115,15 +115,15 @@ github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+U github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/ckaznocha/intrange v0.1.2 h1:3Y4JAxcMntgb/wABQ6e8Q8leMd26JbX2790lIss9MTI= -github.com/ckaznocha/intrange v0.1.2/go.mod h1:RWffCw/vKBwHeOEwWdCikAtY0q4gGt8VhJZEEA5n+RE= +github.com/ckaznocha/intrange v0.2.0 h1:FykcZuJ8BD7oX93YbO1UY9oZtkRbp+1/kJcDjkefYLs= +github.com/ckaznocha/intrange v0.2.0/go.mod h1:r5I7nUlAAG56xmkOpw4XVr16BXhwYTUdcuRFeevn1oE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= -github.com/daixiang0/gci v0.13.4 h1:61UGkmpoAcxHM2hhNkZEf5SzwQtWJXTSws7jaPyqwlw= -github.com/daixiang0/gci v0.13.4/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= +github.com/daixiang0/gci v0.13.5 h1:kThgmH1yBmZSBCh1EJVxQ7JsHpm5Oms0AMed/0LaH4c= +github.com/daixiang0/gci v0.13.5/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -186,8 +186,8 @@ github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQi github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= -github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc= -github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w= +github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U= github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= @@ -228,8 +228,8 @@ github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9 github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 h1:/1322Qns6BtQxUZDTAT4SdcoxknUki7IAoK4SAXr8ME= github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9/go.mod h1:Oesb/0uFAyWoaw1U1qS5zyjCg5NP9C9iwjnI4tIsXEE= -github.com/golangci/golangci-lint v1.60.3 h1:l38A5de24ZeDlcFF+EB7m3W5joPD99/hS5SIHJPyZa0= -github.com/golangci/golangci-lint v1.60.3/go.mod h1:J4vOpcjzRI+lDL2DKNGBZVB3EQSBfCBCMpaydWLtJNo= +github.com/golangci/golangci-lint v1.61.0 h1:VvbOLaRVWmyxCnUIMTbf1kDsaJbTzH20FAMXTAlQGu8= +github.com/golangci/golangci-lint v1.61.0/go.mod h1:e4lztIrJJgLPhWvFPDkhiMwEFRrWlmFbrZea3FsJyN8= github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= github.com/golangci/modinfo v0.3.4 h1:oU5huX3fbxqQXdfspamej74DFX0kyGLkw1ppvXoJ8GA= @@ -266,8 +266,8 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= -github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA= +github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -395,10 +395,10 @@ github.com/nunnatsa/ginkgolinter v0.16.2 h1:8iLqHIZvN4fTLDC0Ke9tbSZVcyVHoBs0HIbn github.com/nunnatsa/ginkgolinter v0.16.2/go.mod h1:4tWRinDN1FeJgU+iJANW/kz7xKN5nYRAOfJDQUS9dOQ= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.20.0 h1:PE84V2mHqoT1sglvHc8ZdQtPcwmvvt29WLEEO3xmdZw= -github.com/onsi/ginkgo/v2 v2.20.0/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI= -github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= -github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= +github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= +github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= +github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= @@ -408,8 +408,8 @@ github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT9 github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -442,8 +442,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/quasilyte/go-ruleguard v0.4.2 h1:htXcXDK6/rO12kiTHKfHuqR4kr3Y4M0J0rOL6CH/BYs= -github.com/quasilyte/go-ruleguard v0.4.2/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= +github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 h1:+Wl/0aFp0hpuHM3H//KMft64WQ1yX9LdJY64Qm/gFCo= +github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= @@ -456,8 +456,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryancurrah/gomodguard v1.3.3 h1:eiSQdJVNr9KTNxY2Niij8UReSwR8Xrte3exBrAZfqpg= -github.com/ryancurrah/gomodguard v1.3.3/go.mod h1:rsKQjj4l3LXe8N344Ow7agAy5p9yjsWOtRzUMYmA0QY= +github.com/ryancurrah/gomodguard v1.3.5 h1:cShyguSwUEeC0jS7ylOiG/idnd1TpJ1LfHGpV3oJmPU= +github.com/ryancurrah/gomodguard v1.3.5/go.mod h1:MXlEPQRxgfPQa62O8wzK3Ozbkv9Rkqr+wKjSxTdsNJE= github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/NsJFwFK7Uxc= @@ -468,8 +468,8 @@ github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tM github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.27.0 h1:t/3jZpSXtRPRf2xr0m63i32ZrusyurIGT9E5wAvXQnI= github.com/sashamelentyev/usestdlibvars v1.27.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= -github.com/securego/gosec/v2 v2.20.1-0.20240822074752-ab3f6c1c83a0 h1:VqD4JMoqwuuCz8GZlBDsIDyE6K4YUsWJpbNtuOWHoFk= -github.com/securego/gosec/v2 v2.20.1-0.20240822074752-ab3f6c1c83a0/go.mod h1:iyeMMRw8QEmueUSZ2VqmkQMiDyDcobfPnG00CV/NWdE= +github.com/securego/gosec/v2 v2.21.2 h1:deZp5zmYf3TWwU7A7cR2+SolbTpZ3HQiwFqnzQyEl3M= +github.com/securego/gosec/v2 v2.21.2/go.mod h1:au33kg78rNseF5PwPnTWhuYBFf534bvJRvOrgZ/bFzU= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= @@ -527,8 +527,8 @@ github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= -github.com/tetafro/godot v1.4.16 h1:4ChfhveiNLk4NveAZ9Pu2AN8QZ2nkUGFuadM9lrr5D0= -github.com/tetafro/godot v1.4.16/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= +github.com/tetafro/godot v1.4.17 h1:pGzu+Ye7ZUEFx7LHU0dAKmCOXWsPjl7qA6iMGndsjPs= +github.com/tetafro/godot v1.4.17/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 h1:quvGphlmUVU+nhpFa4gg4yJyTRJ13reZMDHrKwYw53M= github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966/go.mod h1:27bSVNWSBOHm+qRp1T9qzaIpsWEP6TbUnei/43HK+PQ= github.com/timonwong/loggercheck v0.9.4 h1:HKKhqrjcVj8sxL7K77beXh0adEm6DLjV/QOGeMXEVi4= @@ -599,8 +599,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8= @@ -633,8 +633,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -749,8 +749,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -767,8 +767,8 @@ 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.6.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.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From da1935d19d536cd52f860e9a9c2b86f14289de2b Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 17 Sep 2024 12:32:18 +0200 Subject: [PATCH 0151/1670] Explicitly request the required permissions A user reports that they added the required permissions ("GroupMember.Read.All" and "User.Read") to the Entra app but login fails with: level=WARN msg="missing required scopes: GroupMember.Read.All, User.Read" level=ERROR msg="could not get user into: the Microsoft Entra ID app is missing the GroupMember.Read.All permission" That means that the response to the token request did contain the default permissions ("openid", "profile" and "email") which we specified in the "scope" parameter in the request, but not the other permissions ("GroupMember.Read.All" and "User.Read") which we expect to be there because the user added them to the app. The API reference [1] for the "scope" parameter of the response states: Because the UserInfo endpoint is hosted on Microsoft Graph, it's possible for scope to contain others previously granted to the application (for example, User.Read). [1] https://learn.microsoft.com/en-us/entra/identity-platform/v2-protocols-oidc#successful-token-response The "It's possible" wording doesn't sound like we can rely on those permissions being included in all cases. Lets see if requesting them explicitly fixes the issue. --- internal/providers/msentraid/msentraid.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 511396a63d..1e9a7756d5 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -42,7 +42,7 @@ func New() Provider { // AdditionalScopes returns the generic scopes required by the EntraID provider. func (p Provider) AdditionalScopes() []string { - return []string{oidc.ScopeOfflineAccess} + return []string{oidc.ScopeOfflineAccess, "GroupMember.Read.All", "User.Read"} } // AuthOptions returns the generic auth options required by the EntraID provider. From 7f322c4d8f0df96dcf805b67f0bb80bb38457fc0 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 9 Sep 2024 16:38:14 +0200 Subject: [PATCH 0152/1670] Replace deprecated linter 'exportloopref' with 'copyloopvar' golangci-lint complained: WARN The linter 'exportloopref' is deprecated (since v1.60.2) due to: Since Go1.22 (loopvar) this linter is no longer relevant. Replaced by copyloopvar. --- .golangci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.golangci.yaml b/.golangci.yaml index f3b2c50f9b..dba5860e16 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -4,11 +4,11 @@ linters: # linters to run in addition to default ones enable: + - copyloopvar - dupl - durationcheck - errname - errorlint - - exportloopref - forbidigo - forcetypeassert - gci From 1a0701659172a4a59ec41342b489123b2708f117 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Wed, 18 Sep 2024 16:45:39 +0200 Subject: [PATCH 0153/1670] generate snap version from current git tree state We are starting to generate the snap version using the following rules: When considering a tag, if starting with "-" it will have its prefix removed. For instance: * 0.1 -> 0.1 * msentraid-0.1 -> 0.1 on msentraid branch. 1. If current commit is tagged, the version is directly the tag name. 2. If current commit is not tagged, the version is: a. + for main branch. b. +. for other branches. Any of those version will be annoted with +dirty if there are local changes. --- snap/get_version | 91 +++++++++++++++++++++++++++++++++++++++++++++ snap/snapcraft.yaml | 9 ++++- 2 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 snap/get_version diff --git a/snap/get_version b/snap/get_version new file mode 100644 index 0000000000..9635bf5240 --- /dev/null +++ b/snap/get_version @@ -0,0 +1,91 @@ +#!/bin/sh +set -eu + +# Script to build the version to set to the snap. + +# When considering a tag, if starting with "-" it will have its prefix removed. +# For instance: +# * 0.1 -> 0.1 +# * msentraid-0.1 -> 0.1 on msentraid branch. + +# 1. If current commit is tagged, the version is directly the tag name. +# 2. If current commit is not tagged, the version is: +# a. + for main branch. +# b. +. for other branches. +# +# Any of those version will be annoted with +dirty if there are local changes. + +# set_version will markup the version in the snapcraft.yaml file after amending it with a dirty markup if necessary. +# $1: version: the version to set. +set_version() { + version="${1}" + + version=$(annotate_with_dirty "${version}") + craftctl set version="${version}" +} + +# annotate_with_dirty may amend the version with a dirty markup if there are local changes. +# $1: version: the version to annotate. +annotate_with_dirty() { + version="${1}" + + # check if current tree content is dirty. + is_dirty=$(git -C "${SNAPCRAFT_PART_SRC}" status --porcelain) + if [ -n "${is_dirty}" ]; then + version="${version}+dirty" + fi + + echo "${version}" +} + +# strip_branch_tag_prefix will remove the branch name prefix from the tag name. +# $1: tag: the tag name to strip the prefix from. +# $2: current_branch: the branch name to strip from the tag. +strip_branch_tag_prefix() { + tag="${1}" + current_branch="${2}" + + echo "${tag#"${current_branch}-"}" +} + + +current_branch=$(git -C "${SNAPCRAFT_PART_SRC}" branch --show-current) + +# Try to get most recent tag on that branch not coming from main. +# Main will just get the most recent tag merged into it. +tag_cmd_suffix="" +if [ "${current_branch}" != "main" ]; then + tag_cmd_suffix="--no-merged=main" +fi + +# Get most recent tag on that branch not coming from the other branch. +tag=$(git tag --sort=-v:refname --merged="${current_branch}" ${tag_cmd_suffix} | head -1) + +version="${tag}" +if [ -z "${version}" ]; then + # No tag found, use "notag" as version. + version="notag" +fi +version=$(strip_branch_tag_prefix "${version}" "${current_branch}") + +# If the most recent tag is on the current commit, taking it as is once transformed as a version. +if [ -n "${tag}" ] && [ "$(git describe --tags --exact-match 2>/dev/null)" = "${tag}" ]; then + set_version "${version}" + exit 0 +fi + +# Current commit is not tagged, append commit(s) sha. +version="${version}+$(git -C ${SNAPCRAFT_PART_SRC} rev-parse --short=7 HEAD)" + +# Main branch will be set as is. +if [ "${current_branch}" = "main" ]; then + set_version "${version}" + exit 0 +fi + +# Get the short version of last commit merged from the main branch. +last_commit_on_main=$(git -C "${SNAPCRAFT_PART_SRC}" merge-base main HEAD) +last_commit_on_main=$(git -C "${SNAPCRAFT_PART_SRC}" rev-parse --short=7 "${last_commit_on_main}") +version="${version}.${last_commit_on_main}" + +set_version "${version}" diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 860c7d9333..24c23a0c2d 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -2,7 +2,7 @@ name: authd-oidc summary: OIDC Broker for authd description: | Broker that enables OIDC authentication for authd. -version: git +adopt-info: version grade: stable base: core24 confinement: strict @@ -38,3 +38,10 @@ parts: organize: "authd.conf": "conf/authd/oidc.conf" "broker.conf": "conf/broker.conf.orig" + # Build the snap version from the git repository and current tree state. + version: + source: . + plugin: nil + build-packages: + - git # The script needs Git. + override-build: ./snap/get_version From 3fe848e2b688c8774ad8f92daaef08aefb6470f2 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Fri, 20 Sep 2024 15:20:27 +0200 Subject: [PATCH 0154/1670] Fix computing version script executable Snapcraft version on launchpad does not make build script executable. Make it then explicitely executable. --- snap/get_version | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 snap/get_version diff --git a/snap/get_version b/snap/get_version old mode 100644 new mode 100755 From 5931ce940c92957babd811e9a1803478e22566da Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Thu, 19 Sep 2024 07:38:04 -0400 Subject: [PATCH 0155/1670] Rework ID token generation for the tests We used to ignore the JWT format in the tests, so we could manually write the ID Token. Now that we decided to implement the JWT logic also, the ID token structure becomes more complex: {header}.{payload}.{signature_string} To avoid generating all of this manually, it's better to switch to a package that already handles this parsing with the right encryption and encoding. --- go.mod | 1 + go.sum | 2 ++ internal/broker/helper_test.go | 23 +++++++++++--------- internal/testutils/provider.go | 39 +++++++++++++++++++++++----------- 4 files changed, 43 insertions(+), 22 deletions(-) diff --git a/go.mod b/go.mod index 6c06562f40..9379505ac5 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/coreos/go-oidc/v3 v3.11.0 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/godbus/dbus/v5 v5.1.0 + github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible github.com/microsoftgraph/msgraph-sdk-go v1.48.0 diff --git a/go.sum b/go.sum index 91efaa8169..78d1d53d95 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index 8ab458cc11..4bb87d67ac 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -6,11 +6,11 @@ import ( "crypto/sha512" "crypto/x509" "encoding/base64" - "fmt" "strings" "testing" "time" + "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/require" "github.com/ubuntu/authd-oidc-brokers/internal/broker" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" @@ -142,15 +142,18 @@ func generateCachedInfo(t *testing.T, preexistentToken, issuer string) *broker.A username = preexistentToken } - idToken := fmt.Sprintf(`{ - "iss": "%s", - "sub": "saved-user-id", - "aud": "test-client-id", - "name": "saved-user", - "exp": 9999999999, - "preferred_username": "%s" - }`, issuer, username) - encodedToken := fmt.Sprintf(".%s.", base64.RawURLEncoding.EncodeToString([]byte(idToken))) + idToken := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{ + "iss": issuer, + "sub": "saved-user-id", + "aud": "test-client-id", + "exp": 9999999999, + "name": "test-user", + "preferred_username": username, + "email": "test-user@anotheremail.com", + "email_verified": true, + }) + encodedToken, err := idToken.SignedString(testutils.MockKey) + require.NoError(t, err, "Setup: signing token should not have failed") tok, ok := testTokens[preexistentToken] if !ok { diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index 9febb988c4..d56415caec 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -2,7 +2,8 @@ package testutils import ( "context" - "encoding/base64" + "crypto/rand" + "crypto/rsa" "errors" "fmt" "net" @@ -14,11 +15,23 @@ import ( "time" "github.com/coreos/go-oidc/v3/oidc" + "github.com/golang-jwt/jwt/v5" "github.com/ubuntu/authd-oidc-brokers/internal/consts" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "golang.org/x/oauth2" ) +// MockKey is the RSA key used to sign the JWTs for the mock provider. +var MockKey *rsa.PrivateKey + +func init() { + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + panic(fmt.Sprintf("Setup: Could not generate RSA key for the Mock: %v", err)) + } + MockKey = key +} + // ProviderHandler is a function that handles a request to the mock provider. type ProviderHandler func(http.ResponseWriter, *http.Request) @@ -135,19 +148,21 @@ func DefaultTokenHandler(serverURL string, scopes []string) ProviderHandler { // Mimics user going through auth process time.Sleep(2 * time.Second) - idToken := fmt.Sprintf(`{ - "iss": "%s", - "sub": "test-user-id", - "aud": "test-client-id", - "exp": 9999999999, - "name": "test-user", + idToken := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{ + "iss": serverURL, + "sub": "test-user-id", + "aud": "test-client-id", + "exp": 9999999999, + "name": "test-user", "preferred_username": "test-user@email.com", - "email": "test-user@anotheremail.com", - "email_verified": true - }`, serverURL) + "email": "test-user@anotheremail.com", + "email_verified": true, + }) - // The token must be JWT formatted, even though we ignore the validation in the broker during the tests. - rawToken := fmt.Sprintf(".%s.", base64.RawURLEncoding.EncodeToString([]byte(idToken))) + rawToken, err := idToken.SignedString(MockKey) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + } response := fmt.Sprintf(`{ "access_token": "accesstoken", From 99d6a9f3a0a8393bc8e3967c50e31f7766eea801 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Thu, 19 Sep 2024 07:40:09 -0400 Subject: [PATCH 0156/1670] Add /keys endpoint for the Provider Mock This is one of the key (pun intended) endpoints of an OIDC provider. It returns the required keys for decrypting some of the payloads. We only care about the JWK needed to decrypt the tokens, so it only returns that one. --- go.mod | 2 +- internal/testutils/provider.go | 59 +++++++++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 9379505ac5..f248de1111 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 github.com/coreos/go-oidc/v3 v3.11.0 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf + github.com/go-jose/go-jose/v4 v4.0.2 github.com/godbus/dbus/v5 v5.1.0 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.0 @@ -29,7 +30,6 @@ require ( github.com/cjlapao/common-go v0.0.39 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-jose/go-jose/v4 v4.0.2 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index d56415caec..e20f8ec49f 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -4,8 +4,11 @@ import ( "context" "crypto/rand" "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" "errors" "fmt" + "math/big" "net" "net/http" "net/http/httptest" @@ -15,6 +18,7 @@ import ( "time" "github.com/coreos/go-oidc/v3/oidc" + "github.com/go-jose/go-jose/v4" "github.com/golang-jwt/jwt/v5" "github.com/ubuntu/authd-oidc-brokers/internal/consts" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" @@ -24,12 +28,39 @@ import ( // MockKey is the RSA key used to sign the JWTs for the mock provider. var MockKey *rsa.PrivateKey +var mockCertificate *x509.Certificate + func init() { key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { panic(fmt.Sprintf("Setup: Could not generate RSA key for the Mock: %v", err)) } MockKey = key + + certTemplate := &x509.Certificate{ + SerialNumber: big.NewInt(2024), + Subject: pkix.Name{ + Organization: []string{"Mocks ltd."}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(0, 0, 1), + SubjectKeyId: []byte{1, 2, 3, 4, 5}, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, + KeyUsage: x509.KeyUsageDigitalSignature, + IsCA: true, + BasicConstraintsValid: true, + } + + c, err := x509.CreateCertificate(rand.Reader, certTemplate, certTemplate, &MockKey.PublicKey, MockKey) + if err != nil { + panic("Setup: Could not create certificate for the Mock") + } + + cert, err := x509.ParseCertificate(c) + if err != nil { + panic("Setup: Could not parse certificate for the Mock") + } + mockCertificate = cert } // ProviderHandler is a function that handles a request to the mock provider. @@ -68,6 +99,7 @@ func StartMockProvider(address string, args ...OptionProvider) (*httptest.Server "/.well-known/openid-configuration": DefaultOpenIDHandler(server.URL), "/device_auth": DefaultDeviceAuthHandler(), "/token": DefaultTokenHandler(server.URL, consts.DefaultScopes), + "/keys": DefaultJWKHandler(), }, } for _, arg := range args { @@ -174,10 +206,35 @@ func DefaultTokenHandler(serverURL string, scopes []string) ProviderHandler { }`, strings.Join(scopes, " "), rawToken) w.Header().Add("Content-Type", "application/json") - _, err := w.Write([]byte(response)) + if _, err := w.Write([]byte(response)); err != nil { + w.WriteHeader(http.StatusInternalServerError) + } + } +} + +// DefaultJWKHandler returns a handler that provides the signing keys from the broker. +// +// Meant to be used an the endpoint for /keys. +func DefaultJWKHandler() ProviderHandler { + return func(w http.ResponseWriter, r *http.Request) { + jwk := jose.JSONWebKey{ + Key: &MockKey.PublicKey, + KeyID: "fa834459-66c6-475a-852f-444262a07c13_sig_rs256", + Algorithm: "RS256", + Use: "sig", + Certificates: []*x509.Certificate{mockCertificate}, + } + + encodedJWK, err := jwk.MarshalJSON() if err != nil { w.WriteHeader(http.StatusInternalServerError) } + + response := fmt.Sprintf(`{"keys": [%s]}`, encodedJWK) + w.Header().Add("Content-Type", "application/json") + if _, err := w.Write([]byte(response)); err != nil { + w.WriteHeader(http.StatusInternalServerError) + } } } From 844f3b2b6dd9b032fd02282e991743523cd732f9 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Thu, 19 Sep 2024 07:40:48 -0400 Subject: [PATCH 0157/1670] Remove SkipJWTSignature check option from Broker Now that the mock respects the JWT format and safety, we no longer need this option in the tests, so it should be removed. --- internal/broker/broker.go | 10 +++------- internal/broker/broker_test.go | 2 +- internal/broker/helper_test.go | 1 - internal/broker/options_test.go | 7 ------- 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 3e280842a5..5bd63edd36 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -90,9 +90,7 @@ type isAuthenticatedCtx struct { } type option struct { - // skipJWTSignatureCheck is used to skip the JWT validation done by the oidc web server. - skipJWTSignatureCheck bool - providerInfo providers.ProviderInfoer + providerInfo providers.ProviderInfoer } // Option is a func that allows to override some of the broker default settings. @@ -103,9 +101,7 @@ func New(cfg Config, args ...Option) (b *Broker, err error) { defer decorate.OnError(&err, "could not create broker") opts := option{ - // This is to avoid too much complexity in the tests. - skipJWTSignatureCheck: false, - providerInfo: providers.CurrentProviderInfo(), + providerInfo: providers.CurrentProviderInfo(), } for _, arg := range args { arg(&opts) @@ -139,7 +135,7 @@ func New(cfg Config, args ...Option) (b *Broker, err error) { b = &Broker{ providerInfo: opts.providerInfo, issuerURL: cfg.IssuerURL, - oidcCfg: oidc.Config{ClientID: cfg.ClientID, InsecureSkipSignatureCheck: opts.skipJWTSignatureCheck}, + oidcCfg: oidc.Config{ClientID: cfg.ClientID}, cachePath: cfg.CachePath, homeDirPath: homeDirPath, allowedSSHSuffixes: cfg.AllowedSSHSuffixes, diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 07d22f9762..621d383d40 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -770,7 +770,7 @@ func TestFetchUserInfo(t *testing.T) { mockInfoer.Groups = []info.Group{} } - b, err := broker.New(brokerCfg, broker.WithSkipSignatureCheck(), broker.WithCustomProviderInfo(mockInfoer)) + b, err := broker.New(brokerCfg, broker.WithCustomProviderInfo(mockInfoer)) require.NoError(t, err, "Setup: New should not have returned an error") if tc.username == "" { diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index 4bb87d67ac..3435035f03 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -35,7 +35,6 @@ func newBrokerForTests(t *testing.T, cfg broker.Config) (b *broker.Broker) { b, err := broker.New( cfg, - broker.WithSkipSignatureCheck(), broker.WithCustomProviderInfo(&testutils.MockProviderInfoer{ Groups: []info.Group{ {Name: "remote-group", UGID: "12345"}, diff --git a/internal/broker/options_test.go b/internal/broker/options_test.go index 94880b6012..9858d4dce9 100644 --- a/internal/broker/options_test.go +++ b/internal/broker/options_test.go @@ -2,13 +2,6 @@ package broker import "github.com/ubuntu/authd-oidc-brokers/internal/providers" -// WithSkipSignatureCheck returns an option that skips the JWT signature check. -func WithSkipSignatureCheck() Option { - return func(o *option) { - o.skipJWTSignatureCheck = true - } -} - // WithCustomProviderInfo returns an option that sets a custom provider infoer for the broker. func WithCustomProviderInfo(p providers.ProviderInfoer) Option { return func(o *option) { From 29e96ce252fa38e4b7c80d7b672a7a010650b3e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 08:51:21 +0000 Subject: [PATCH 0158/1670] deps(go): bump golang.org/x/crypto from 0.27.0 to 0.28.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.27.0 to 0.28.0. - [Commits](https://github.com/golang/crypto/compare/v0.27.0...v0.28.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 6c06562f40..ca0e6ebf0e 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/stretchr/testify v1.9.0 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 - golang.org/x/crypto v0.27.0 + golang.org/x/crypto v0.28.0 golang.org/x/oauth2 v0.23.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 @@ -64,6 +64,6 @@ require ( golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/net v0.27.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect ) diff --git a/go.sum b/go.sum index 91efaa8169..2622885bfd 100644 --- a/go.sum +++ b/go.sum @@ -130,8 +130,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -153,16 +153,16 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc 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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From 5fc4296a9ded699678228c309e03f55e2fb9db4b Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 9 Oct 2024 21:58:19 +0200 Subject: [PATCH 0159/1670] Use paging to get all groups from graph API By default, Microsoft Graph API only returns 100 objects per call. If there are more objects, it returns a "@odata.nextLink" property with a link to the next page of results. Closes #549 --- internal/providers/msentraid/msentraid.go | 51 +++++++++++++---------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 1bd925ea54..f4ace3cb52 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -143,30 +143,13 @@ func (p Provider) getGroups(token *oauth2.Token) ([]info.Group, error) { // Get the groups (only the groups, not directory roles or administrative units, because that would require // additional permissions) which the user is a member of. - graphGroups, err := client.Me().TransitiveMemberOf().GraphGroup().Get(context.Background(), nil) + graphGroups, err := getAllUserGroups(client) if err != nil { return nil, fmt.Errorf("failed to get user groups: %v", err) } var groups []info.Group - for _, obj := range graphGroups.GetValue() { - unknown := "Unknown" - msGroup, ok := obj.(*msgraphmodels.Group) - if !ok { - id, oType := obj.GetId(), obj.GetOdataType() - if id == nil { - id = &unknown - } - if oType == nil { - oType = &unknown - } - slog.Debug(fmt.Sprintf( - "Found non-group object with ID: %q of type: %q in graphsdk response. Ignoring it", - *id, *oType, - )) - continue - } - + for _, msGroup := range graphGroups { v, err := msGroup.GetBackingStore().Get("displayName") if err != nil { return nil, fmt.Errorf("failed to get displayName from group object: %v", err) @@ -175,9 +158,10 @@ func (p Provider) getGroups(token *oauth2.Token) ([]info.Group, error) { if !ok || name == nil { id := msGroup.GetId() if id == nil { + unknown := "Unknown" id = &unknown } - slog.Warn(pp.Sprintf("Could not get displayName from group object (ID: %s) found: %v", *id, *msGroup)) + slog.Warn(pp.Sprintf("Could not get displayName from group object (ID: %s) found: %v", *id, msGroup)) return nil, errors.New("could not parse group name") } groupName := strings.ToLower(*name) @@ -195,7 +179,7 @@ func (p Provider) getGroups(token *oauth2.Token) ([]info.Group, error) { } id, ok := v.(*string) if !ok || id == nil { - slog.Warn(pp.Sprintf("Could not get ID for group %q: %v", groupName, *msGroup)) + slog.Warn(pp.Sprintf("Could not get ID for group %q: %v", groupName, msGroup)) return nil, errors.New("could not parse group id") } @@ -205,6 +189,31 @@ func (p Provider) getGroups(token *oauth2.Token) ([]info.Group, error) { return groups, nil } +func getAllUserGroups(client *msgraphsdk.GraphServiceClient) ([]msgraphmodels.Groupable, error) { + // Initial request to get groups + requestBuilder := client.Me().TransitiveMemberOf().GraphGroup() + result, err := requestBuilder.Get(context.Background(), nil) + if err != nil { + return nil, fmt.Errorf("failed to get user groups: %v", err) + } + + groups := result.GetValue() + + // Continue fetching groups using paging if a next link is available + for result.GetOdataNextLink() != nil { + nextLink := *result.GetOdataNextLink() + + result, err = requestBuilder.WithUrl(nextLink).Get(context.Background(), nil) + if err != nil { + return nil, fmt.Errorf("failed to get next page of user groups: %v", err) + } + + groups = append(groups, result.GetValue()...) + } + + return groups, nil +} + // CurrentAuthenticationModesOffered returns the generic authentication modes supported by the provider. // // Token validity is not considered, only the presence of a token. From a7ca0e55bd15d9dc1ac486ab7f15cfbf2049916c Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 10 Oct 2024 15:48:43 +0200 Subject: [PATCH 0160/1670] refactor: Simplify getGroups * Use GetID() and GetDisplayName() instead of GetBackingStore().Get() * Get the ID before the display name, so that we don't have to get it via a separate call when getting the display name fails. That means that we're also getting the ID if we don't need it at all (in the case that getting the display name succeeds and it's a local group, so we don't return the ID), but that's fine because its value is already stored in memory, so it's cheap to get it. --- internal/providers/msentraid/msentraid.go | 39 ++++++++--------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index f4ace3cb52..664c829b45 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -150,40 +150,29 @@ func (p Provider) getGroups(token *oauth2.Token) ([]info.Group, error) { var groups []info.Group for _, msGroup := range graphGroups { - v, err := msGroup.GetBackingStore().Get("displayName") - if err != nil { - return nil, fmt.Errorf("failed to get displayName from group object: %v", err) + idPtr := msGroup.GetId() + if idPtr == nil { + slog.Warn(pp.Sprintf("Could not get ID for group: %v", msGroup)) + return nil, errors.New("could not get group id") } - name, ok := v.(*string) - if !ok || name == nil { - id := msGroup.GetId() - if id == nil { - unknown := "Unknown" - id = &unknown - } - slog.Warn(pp.Sprintf("Could not get displayName from group object (ID: %s) found: %v", *id, msGroup)) - return nil, errors.New("could not parse group name") + id := *idPtr + + groupNamePtr := msGroup.GetDisplayName() + if groupNamePtr == nil { + slog.Warn(pp.Sprintf("Could not get display name for group object (ID: %s): %v", id, msGroup)) + return nil, errors.New("could not get group name") } - groupName := strings.ToLower(*name) + groupName := strings.ToLower(*groupNamePtr) - // Local group + // Check if the group is a local group, in which case we don't set the UGID (because that's how the user manager + // differentiates between local and remote groups). if strings.HasPrefix(groupName, localGroupPrefix) { groupName = strings.TrimPrefix(groupName, localGroupPrefix) groups = append(groups, info.Group{Name: groupName}) continue } - v, err = msGroup.GetBackingStore().Get("id") - if err != nil { - return nil, fmt.Errorf("failed to get id from group object: %v", err) - } - id, ok := v.(*string) - if !ok || id == nil { - slog.Warn(pp.Sprintf("Could not get ID for group %q: %v", groupName, msGroup)) - return nil, errors.New("could not parse group id") - } - - groups = append(groups, info.Group{Name: groupName, UGID: *id}) + groups = append(groups, info.Group{Name: groupName, UGID: id}) } return groups, nil From 9840f17dbeb799f54b8c0ae0f10d9b7c9789be66 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 6 Sep 2024 12:28:42 +0200 Subject: [PATCH 0161/1670] Always try to refresh the user info on local password login Before this commit, we only refreshed the user info during local password login when we retrieved a new access token (because the old one was expired). There's no reason to only do it then - as long as we have a valid access token, we can always try to refresh the user info. With this commit, we always try to refresh the user info. If that fails for any reason, we log a warning and keep using the cached user info. Closes #520 --- internal/broker/broker.go | 17 +++++++++++------ .../first_call | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 5bd63edd36..916f52082d 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -478,12 +478,17 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo return AuthRetry, errorMessage{Message: "could not load cached info"} } - if authInfo.UserInfo.Name == "" { - authInfo.UserInfo, err = b.fetchUserInfo(ctx, session, &authInfo) - if err != nil { - slog.Error(err.Error()) - return AuthDenied, errorMessageForDisplay(err, "could not fetch user info") - } + userInfo, err := b.fetchUserInfo(ctx, session, &authInfo) + if err != nil && authInfo.UserInfo.Name == "" { + // We don't have a valid user info, so we can't proceed. + slog.Error(err.Error()) + return AuthDenied, errorMessageForDisplay(err, "could not fetch user info") + } + if err != nil { + // We couldn't fetch the user info, but we have a valid cached one. + slog.Warn(fmt.Sprintf("Could not fetch user info: %v. Using cached user info.", err)) + } else { + authInfo.UserInfo = userInfo } if session.mode == "passwd" { diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call index 2928c3a331..cc046e18ae 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"saved-remote-group","ugid":"12345"},{"name":"saved-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' err: From 97dc7a8df88a8722d2d1fdcbc944aebeb715b2ac Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 6 Sep 2024 12:29:50 +0200 Subject: [PATCH 0162/1670] Add debug output --- internal/providers/msentraid/msentraid.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 664c829b45..e2c2572a7b 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -119,6 +119,8 @@ func (p Provider) userClaims(idToken *oidc.IDToken) (claims, error) { // getGroups access the Microsoft Graph API to get the groups the user is a member of. func (p Provider) getGroups(token *oauth2.Token) ([]info.Group, error) { + slog.Debug("Getting user groups from Microsoft Graph API") + // Check if the token has the GroupMember.Read.All scope scopes, err := p.getTokenScopes(token) if err != nil { @@ -200,6 +202,7 @@ func getAllUserGroups(client *msgraphsdk.GraphServiceClient) ([]msgraphmodels.Gr groups = append(groups, result.GetValue()...) } + slog.Debug(fmt.Sprintf("Got groups: %v", groups)) return groups, nil } From 4318f5ce4cdb52b876bb93e5fa5b9772da3b9de3 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 9 Oct 2024 23:44:46 +0200 Subject: [PATCH 0163/1670] Store the extra field "scope" of the token persistently So that we can also check it when refreshing the UserInfo with a token loaded from disk. --- internal/broker/broker.go | 24 ++++++++++++++++----- internal/providers/msentraid/msentraid.go | 7 ++++++ internal/providers/noprovider/noprovider.go | 5 +++++ internal/providers/providers.go | 1 + internal/testutils/provider.go | 5 +++++ 5 files changed, 37 insertions(+), 5 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 916f52082d..4a91604d8a 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -461,7 +461,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo return AuthDenied, errorMessage{Message: "could not get ID token"} } - authInfo = authCachedInfo{Token: t, RawIDToken: rawIDToken} + authInfo = b.newAuthCachedInfo(t, rawIDToken) authInfo.UserInfo, err = b.fetchUserInfo(ctx, session, &authInfo) if err != nil { slog.Error(err.Error()) @@ -628,9 +628,18 @@ func (b *Broker) updateSession(sessionID string, session sessionInfo) error { // authCachedInfo represents the token that will be saved on disk for offline authentication. type authCachedInfo struct { - Token *oauth2.Token - RawIDToken string - UserInfo info.User + Token *oauth2.Token + ExtraFields map[string]interface{} + RawIDToken string + UserInfo info.User +} + +func (b *Broker) newAuthCachedInfo(t *oauth2.Token, idToken string) authCachedInfo { + return authCachedInfo{ + Token: t, + RawIDToken: idToken, + ExtraFields: b.providerInfo.GetExtraFields(t), + } } // cacheAuthInfo serializes the access token and cache it. @@ -674,6 +683,11 @@ func (b *Broker) loadAuthInfo(ctx context.Context, session *sessionInfo, passwor return authCachedInfo{}, fmt.Errorf("could not unmarshal token: %v", err) } + // Set the extra fields of the token. + if cachedInfo.ExtraFields != nil { + cachedInfo.Token = cachedInfo.Token.WithExtra(cachedInfo.ExtraFields) + } + // If the token is still valid, we return it. Ideally, we would refresh it online, but the TokenSource API also uses // this logic to decide whether the token needs refreshing, so we should run it early to control the returned values. if cachedInfo.Token.Valid() || session.isOffline { @@ -694,7 +708,7 @@ func (b *Broker) loadAuthInfo(ctx context.Context, session *sessionInfo, passwor refreshedIDToken = cachedInfo.RawIDToken } - return authCachedInfo{Token: tok, RawIDToken: refreshedIDToken}, nil + return b.newAuthCachedInfo(tok, refreshedIDToken), nil } func (b *Broker) fetchUserInfo(ctx context.Context, session *sessionInfo, t *authCachedInfo) (userInfo info.User, err error) { diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index e2c2572a7b..08679081e9 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -78,6 +78,13 @@ func (p Provider) getTokenScopes(token *oauth2.Token) ([]string, error) { return strings.Split(scopesStr, " "), nil } +// GetExtraFields returns the extra fields of the token which should be stored persistently. +func (p Provider) GetExtraFields(token *oauth2.Token) map[string]interface{} { + return map[string]interface{}{ + "scope": token.Extra("scope"), + } +} + // GetUserInfo is a no-op when no specific provider is in use. func (p Provider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken) (info.User, error) { userClaims, err := p.userClaims(idToken) diff --git a/internal/providers/noprovider/noprovider.go b/internal/providers/noprovider/noprovider.go index 670031ffc7..a3c281c148 100644 --- a/internal/providers/noprovider/noprovider.go +++ b/internal/providers/noprovider/noprovider.go @@ -76,6 +76,11 @@ func (p NoProvider) CurrentAuthenticationModesOffered( return offeredModes, nil } +// GetExtraFields returns the extra fields of the token which should be stored persistently. +func (p NoProvider) GetExtraFields(token *oauth2.Token) map[string]interface{} { + return nil +} + // GetUserInfo is a no-op when no specific provider is in use. func (p NoProvider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken) (info.User, error) { userClaims, err := p.userClaims(idToken) diff --git a/internal/providers/providers.go b/internal/providers/providers.go index b73628a7e2..ca111e1ba1 100644 --- a/internal/providers/providers.go +++ b/internal/providers/providers.go @@ -22,6 +22,7 @@ type ProviderInfoer interface { endpoints map[string]struct{}, currentAuthStep int, ) ([]string, error) + GetExtraFields(token *oauth2.Token) map[string]interface{} GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken) (info.User, error) VerifyUsername(requestedUsername, authenticatedUsername string) error } diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index e20f8ec49f..aa308d1c74 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -335,6 +335,11 @@ func (p *MockProviderInfoer) AuthOptions() []oauth2.AuthCodeOption { return []oauth2.AuthCodeOption{} } +// GetExtraFields returns the extra fields of the token which should be stored persistently. +func (p *MockProviderInfoer) GetExtraFields(token *oauth2.Token) map[string]interface{} { + return nil +} + // GetUserInfo is a no-op when no specific provider is in use. func (p *MockProviderInfoer) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken) (info.User, error) { userClaims, err := p.userClaims(idToken) From fbaff271b1bad7e4260f3a829da069d8858bb60d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Oct 2024 08:37:57 +0000 Subject: [PATCH 0164/1670] deps(go): bump github.com/go-jose/go-jose/v4 from 4.0.2 to 4.0.4 Bumps [github.com/go-jose/go-jose/v4](https://github.com/go-jose/go-jose) from 4.0.2 to 4.0.4. - [Release notes](https://github.com/go-jose/go-jose/releases) - [Changelog](https://github.com/go-jose/go-jose/blob/main/CHANGELOG.md) - [Commits](https://github.com/go-jose/go-jose/compare/v4.0.2...v4.0.4) --- updated-dependencies: - dependency-name: github.com/go-jose/go-jose/v4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a5d4a3c327..e1160ded89 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 github.com/coreos/go-oidc/v3 v3.11.0 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf - github.com/go-jose/go-jose/v4 v4.0.2 + github.com/go-jose/go-jose/v4 v4.0.4 github.com/godbus/dbus/v5 v5.1.0 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum index 99a41a5a35..1b082bd75d 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 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/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk= -github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= +github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= +github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= From b47ebd68ad951ed1ddab3659ef8007dba85b72f0 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Tue, 15 Oct 2024 10:30:52 +0200 Subject: [PATCH 0165/1670] Use email for generic provider as the identifier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Most web login platform are using the user email address as an identifier. Let’s align the generic "no provider" on this, which can then be overriden by specific provider code. --- internal/providers/noprovider/noprovider.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/providers/noprovider/noprovider.go b/internal/providers/noprovider/noprovider.go index a3c281c148..60cd94ca1f 100644 --- a/internal/providers/noprovider/noprovider.go +++ b/internal/providers/noprovider/noprovider.go @@ -94,7 +94,7 @@ func (p NoProvider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, } return info.NewUser( - userClaims.PreferredUserName, + userClaims.Email, userClaims.Home, userClaims.Sub, userClaims.Shell, @@ -112,11 +112,11 @@ func (p NoProvider) VerifyUsername(requestedUsername, username string) error { } type claims struct { - PreferredUserName string `json:"preferred_username"` - Sub string `json:"sub"` - Home string `json:"home"` - Shell string `json:"shell"` - Gecos string `json:"gecos"` + Email string `json:"email"` + Sub string `json:"sub"` + Home string `json:"home"` + Shell string `json:"shell"` + Gecos string `json:"gecos"` } // userClaims returns the user claims parsed from the ID token. From 6ecb82b583f3c8d457511a3585226b6ac79d2897 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Tue, 15 Oct 2024 11:01:50 +0200 Subject: [PATCH 0166/1670] Align tests on using emails as identifier for generic provider --- internal/broker/helper_test.go | 4 ++-- internal/testutils/provider.go | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index 3435035f03..9fcabfc91a 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -147,8 +147,8 @@ func generateCachedInfo(t *testing.T, preexistentToken, issuer string) *broker.A "aud": "test-client-id", "exp": 9999999999, "name": "test-user", - "preferred_username": username, - "email": "test-user@anotheremail.com", + "preferred_username": "test-user-preferred-username@email.com", + "email": username, "email_verified": true, }) encodedToken, err := idToken.SignedString(testutils.MockKey) diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index aa308d1c74..7f1d875f25 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -186,8 +186,8 @@ func DefaultTokenHandler(serverURL string, scopes []string) ProviderHandler { "aud": "test-client-id", "exp": 9999999999, "name": "test-user", - "preferred_username": "test-user@email.com", - "email": "test-user@anotheremail.com", + "preferred_username": "test-user-preferred-username@email.com", + "email": "test-user@email.com", "email_verified": true, }) @@ -354,8 +354,8 @@ func (p *MockProviderInfoer) GetUserInfo(ctx context.Context, accessToken *oauth // This is a special case for testing purposes. If the username starts with "user-timeout-", we will delay the // return for a while to control the authentication order for multiple users. - if strings.HasPrefix(userClaims.PreferredUserName, "user-timeout") { - d, err := strconv.Atoi(strings.TrimPrefix(userClaims.PreferredUserName, "user-timeout-")) + if strings.HasPrefix(userClaims.Email, "user-timeout") { + d, err := strconv.Atoi(strings.TrimPrefix(userClaims.Email, "user-timeout-")) if err != nil { return info.User{}, err } @@ -363,7 +363,7 @@ func (p *MockProviderInfoer) GetUserInfo(ctx context.Context, accessToken *oauth } return info.NewUser( - userClaims.PreferredUserName, + userClaims.Email, userClaims.Home, userClaims.Sub, userClaims.Shell, @@ -373,11 +373,11 @@ func (p *MockProviderInfoer) GetUserInfo(ctx context.Context, accessToken *oauth } type claims struct { - PreferredUserName string `json:"preferred_username"` - Sub string `json:"sub"` - Home string `json:"home"` - Shell string `json:"shell"` - Gecos string `json:"gecos"` + Email string `json:"email"` + Sub string `json:"sub"` + Home string `json:"home"` + Shell string `json:"shell"` + Gecos string `json:"gecos"` } // userClaims returns the user claims parsed from the ID token. From cd82926d2cebaf6d1a454851da5e2ba5b53657e2 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Tue, 15 Oct 2024 11:25:22 +0200 Subject: [PATCH 0167/1670] google specific provider We need to override the generic provider (which should be renamed from NoProvider to BaseProvider in the long term) to remove the offline_access scope. For TV and limited input devices apps, Google APIs will reject any request with this scope, while still allowing non interactive user access token refresh. All the rest of the functionalities is aligned with the NoProvider one. --- internal/providers/google/google.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 internal/providers/google/google.go diff --git a/internal/providers/google/google.go b/internal/providers/google/google.go new file mode 100644 index 0000000000..d3c2ac4f48 --- /dev/null +++ b/internal/providers/google/google.go @@ -0,0 +1,28 @@ +// Package google is the google specific extension. +package google + +import ( + "github.com/ubuntu/authd-oidc-brokers/internal/providers/noprovider" +) + +// Provider is the google provider implementation. +type Provider struct { + noprovider.NoProvider +} + +// New returns a new GoogleProvider. +func New() Provider { + return Provider{ + NoProvider: noprovider.New(), + } +} + +// AdditionalScopes returns the generic scopes required by the provider. +// Note that we do not return oidc.ScopeOfflineAccess, as for TV/limited input devices, the API call will fail as not +// supported by this application type. However, the refresh token will be acquired and is functional to refresh without +// user interaction. +// If we start to support other kinds of applications, we should revisit this. +// More info on https://developers.google.com/identity/protocols/oauth2/limited-input-device#allowedscopes. +func (Provider) AdditionalScopes() []string { + return []string{} +} From 2216e64094b6a128144053a8d3ba3f40ea70dd2b Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Tue, 15 Oct 2024 11:32:53 +0200 Subject: [PATCH 0168/1670] First set of tests for the google provider Even if those tests are trivial, this will set the base so that when we need to make adjustements, we can handle them. Note that by default, New() returns an instances of empty NoProvider too. --- internal/providers/google/google_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 internal/providers/google/google_test.go diff --git a/internal/providers/google/google_test.go b/internal/providers/google/google_test.go new file mode 100644 index 0000000000..eaff2c7b77 --- /dev/null +++ b/internal/providers/google/google_test.go @@ -0,0 +1,24 @@ +package google_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/google" +) + +func TestNew(t *testing.T) { + t.Parallel() + + p := google.New() + + require.Empty(t, p, "New should return the default provider implementation with no parameters") +} + +func TestAdditionalScopes(t *testing.T) { + t.Parallel() + + p := google.New() + + require.Empty(t, p.AdditionalScopes(), "Google provider should not require additional scopes") +} From e64935dbca2e55fe130710f27ac9ea53f0d68653 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Tue, 15 Oct 2024 11:34:00 +0200 Subject: [PATCH 0169/1670] withgoogle build tag to build with the google provider We will have a dedicated snap and its own branch to build with it. --- internal/providers/default.go | 2 +- internal/providers/withgoogle.go | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 internal/providers/withgoogle.go diff --git a/internal/providers/default.go b/internal/providers/default.go index f3b406dcc1..494a50f688 100644 --- a/internal/providers/default.go +++ b/internal/providers/default.go @@ -1,4 +1,4 @@ -//go:build !withmsentraid +//go:build !withgoogle && !withmsentraid package providers diff --git a/internal/providers/withgoogle.go b/internal/providers/withgoogle.go new file mode 100644 index 0000000000..caa758db12 --- /dev/null +++ b/internal/providers/withgoogle.go @@ -0,0 +1,10 @@ +//go:build withgoogle + +package providers + +import "github.com/ubuntu/authd-oidc-brokers/internal/providers/google" + +// CurrentProviderInfo returns a Google provider implementation. +func CurrentProviderInfo() ProviderInfoer { + return google.New() +} From afdfd450df207ad26e93799e8a814fd9871ee757 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Tue, 5 Nov 2024 09:48:21 +0100 Subject: [PATCH 0170/1670] Viper/daemon related configuration marked as paths-config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have a mixup of configuration: * one for viper/daemon, mostly for system paths * one for the authentication itself The --config is referring to the first one, where user probably think it’s the second one. For now, split them as other PRs in flights are changing configuration handling. --- cmd/authd-oidc/daemon/config.go | 2 +- cmd/authd-oidc/daemon/daemon.go | 2 ++ cmd/authd-oidc/daemon/daemon_test.go | 2 +- cmd/authd-oidc/daemon/export_test.go | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cmd/authd-oidc/daemon/config.go b/cmd/authd-oidc/daemon/config.go index d2ff172e01..39f4d951bf 100644 --- a/cmd/authd-oidc/daemon/config.go +++ b/cmd/authd-oidc/daemon/config.go @@ -30,7 +30,7 @@ func initViperConfig(name string, cmd *cobra.Command, vip *viper.Viper) (err err setVerboseMode(v) // Handle configuration. - if v, err := cmd.Flags().GetString("config"); err == nil && v != "" { + if v, err := cmd.Flags().GetString("paths-config"); err == nil && v != "" { vip.SetConfigFile(v) } else { vip.SetConfigName(name) diff --git a/cmd/authd-oidc/daemon/daemon.go b/cmd/authd-oidc/daemon/daemon.go index 27b9a5ff35..5417058133 100644 --- a/cmd/authd-oidc/daemon/daemon.go +++ b/cmd/authd-oidc/daemon/daemon.go @@ -90,6 +90,8 @@ func New(name string) *App { installVerbosityFlag(&a.rootCmd, a.viper) installConfigFlag(&a.rootCmd) + // FIXME: This option is for the viper path configuration. We should merge --config and this one in the future. + a.rootCmd.PersistentFlags().StringP("paths-config", "", "", "use a specific paths configuration file") // subcommands a.installVersion() diff --git a/cmd/authd-oidc/daemon/daemon_test.go b/cmd/authd-oidc/daemon/daemon_test.go index 96eb24f44a..6fe836682c 100644 --- a/cmd/authd-oidc/daemon/daemon_test.go +++ b/cmd/authd-oidc/daemon/daemon_test.go @@ -287,7 +287,7 @@ func TestNoConfigSetDefaults(t *testing.T) { func TestBadConfigReturnsError(t *testing.T) { a := daemon.New(t.Name()) // Use version to still run preExec to load no config but without running server - a.SetArgs("version", "--config", "/does/not/exist.yaml") + a.SetArgs("version", "--paths-config", "/does/not/exist.yaml") err := a.Run() require.Error(t, err, "Run should return an error on config file") diff --git a/cmd/authd-oidc/daemon/export_test.go b/cmd/authd-oidc/daemon/export_test.go index 2f33bbf1fb..1a8263d3e9 100644 --- a/cmd/authd-oidc/daemon/export_test.go +++ b/cmd/authd-oidc/daemon/export_test.go @@ -20,7 +20,7 @@ func NewForTests(t *testing.T, conf *DaemonConfig, providerURL string, args ...s t.Helper() p := GenerateTestConfig(t, conf, providerURL) - argsWithConf := []string{"--config", p} + argsWithConf := []string{"--paths-config", p} argsWithConf = append(argsWithConf, args...) a := New(t.Name()) From ac5e5605962f64eec5ca966041b4155d5cd834ac Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Tue, 5 Nov 2024 09:50:40 +0100 Subject: [PATCH 0171/1670] Allow overriding broker configuration with --config flag This is the flag the user is expecting when they are specifying a configuration. --- cmd/authd-oidc/daemon/daemon.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/authd-oidc/daemon/daemon.go b/cmd/authd-oidc/daemon/daemon.go index 5417058133..c883b85c42 100644 --- a/cmd/authd-oidc/daemon/daemon.go +++ b/cmd/authd-oidc/daemon/daemon.go @@ -73,6 +73,11 @@ func New(name string) *App { return fmt.Errorf("unable to decode configuration into struct: %w", err) } + // FIXME: for now, config is only the broker.conf file. It should be merged with the viper configuration. + if v, err := cmd.Flags().GetString("config"); err == nil && v != "" { + a.config.Paths.BrokerConf = v + } + setVerboseMode(a.config.Verbosity) slog.Debug("Debug mode is enabled") From c4f2d5a039e7cdb556d0e79eef2993de2739691b Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Tue, 5 Nov 2024 09:51:57 +0100 Subject: [PATCH 0172/1670] Split the helper to generate a broker configuration This helper is really about generating the oidc part itself inside the configuration. --- cmd/authd-oidc/daemon/export_test.go | 36 ++++++++++++++++++---------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/cmd/authd-oidc/daemon/export_test.go b/cmd/authd-oidc/daemon/export_test.go index 1a8263d3e9..f898c45f66 100644 --- a/cmd/authd-oidc/daemon/export_test.go +++ b/cmd/authd-oidc/daemon/export_test.go @@ -50,19 +50,7 @@ func GenerateTestConfig(t *testing.T, origConf *daemonConfig, providerURL string conf.Paths.BrokerConf = filepath.Join(t.TempDir(), strings.ReplaceAll(t.Name(), "/", "_")+".yaml") } - brokerCfg := fmt.Sprintf(` -[authd] -name = %[1]s -brand_icon = broker_icon.png -dbus_name = com.ubuntu.authd.%[1]s -dbus_object = /com/ubuntu/authd/%[1]s - -[oidc] -issuer = %[2]s -client_id = client_id -`, strings.ReplaceAll(t.Name(), "/", "_"), providerURL) - err := os.WriteFile(conf.Paths.BrokerConf, []byte(brokerCfg), 0600) - require.NoError(t, err, "Setup: could not create broker configuration for tests") + GenerateBrokerConfig(t, conf.Paths.BrokerConf, providerURL) d, err := yaml.Marshal(conf) require.NoError(t, err, "Setup: could not marshal configuration for tests") @@ -74,6 +62,28 @@ client_id = client_id return confPath } +// GenerateBrokerConfig creates a broker configuration file for tests. +func GenerateBrokerConfig(t *testing.T, p, providerURL string) { + t.Helper() + + err := os.MkdirAll(filepath.Dir(p), 0700) + require.NoError(t, err, "Setup: could not create parent broker configuration directory for tests") + + brokerCfg := fmt.Sprintf(` + [authd] + name = %[1]s + brand_icon = broker_icon.png + dbus_name = com.ubuntu.authd.%[1]s + dbus_object = /com/ubuntu/authd/%[1]s + + [oidc] + issuer = %[2]s + client_id = client_id + `, strings.ReplaceAll(t.Name(), "/", "_"), providerURL) + err = os.WriteFile(p, []byte(brokerCfg), 0600) + require.NoError(t, err, "Setup: could not create broker configuration for tests") +} + // Config returns a DaemonConfig for tests. // //nolint:revive // DaemonConfig is a type alias for tests From 63d02f7d0c8740b20b7f441cbb4a350a881c64d6 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Tue, 5 Nov 2024 09:52:33 +0100 Subject: [PATCH 0173/1670] Test to ensure user defined config override the system/paths ones --config has precedence over the default and any broker config set with --paths-config --- cmd/authd-oidc/daemon/daemon_test.go | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/cmd/authd-oidc/daemon/daemon_test.go b/cmd/authd-oidc/daemon/daemon_test.go index 6fe836682c..8442208408 100644 --- a/cmd/authd-oidc/daemon/daemon_test.go +++ b/cmd/authd-oidc/daemon/daemon_test.go @@ -236,6 +236,38 @@ func TestConfigLoad(t *testing.T) { require.Equal(t, config, a.Config(), "Config is loaded") } +func TestConfigHasPrecedenceOverPathsConfig(t *testing.T) { + tmpDir := t.TempDir() + config := daemon.DaemonConfig{ + Verbosity: 1, + Paths: daemon.SystemPaths{ + BrokerConf: filepath.Join(tmpDir, "broker.conf"), + Cache: filepath.Join(tmpDir, "cache"), + }, + } + + overrideBrokerConfPath := filepath.Join(tmpDir, "override", "via", "config", "broker.conf") + daemon.GenerateBrokerConfig(t, overrideBrokerConfPath, mockProvider.URL) + a := daemon.NewForTests(t, &config, mockProvider.URL, "--config", overrideBrokerConfPath) + + wg := sync.WaitGroup{} + wg.Add(1) + go func() { + defer wg.Done() + err := a.Run() + require.NoError(t, err, "Run should exits without any error") + }() + a.WaitReady() + time.Sleep(50 * time.Millisecond) + + defer wg.Wait() + defer a.Quit() + + want := config + want.Paths.BrokerConf = overrideBrokerConfPath + require.Equal(t, want, a.Config(), "Config is loaded") +} + func TestAutoDetectConfig(t *testing.T) { tmpDir := t.TempDir() config := daemon.DaemonConfig{ From f1cd76bbd903c779d0b03ac25240828a2aaea977 Mon Sep 17 00:00:00 2001 From: Sebastien Bacher Date: Wed, 16 Oct 2024 12:26:37 +0200 Subject: [PATCH 0174/1670] Add instructions on how to report security issues --- SECURITY.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..b5b5523382 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,8 @@ +# How to report a security issue with authd oidc-based brokers + +The easiest way to report a security issue is through [GitHub](https://github.com/ubuntu/authd-oidc-brokers/security/advisories/new). +See [Privately reporting a security vulnerability](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability) +for instructions. + +The [Ubuntu Security disclosure and embargo policy](https://ubuntu.com/security/disclosure-policy) +contains more information about what you can expect when you contact us and what we expect from you. From e9266e4ac89ac5737bf789f72a3ea92a84156182 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 5 Nov 2024 14:12:33 +0100 Subject: [PATCH 0175/1670] Make snap/hooks/install executable That's done by `snapcraft --destructive-mode`, which I'm using because of spurious failures of `snapcraft --use-lxd`. --- snap/hooks/install | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 snap/hooks/install diff --git a/snap/hooks/install b/snap/hooks/install old mode 100644 new mode 100755 From 7c3c59e416b7c8e26f5c427e7c49ee5b20450b85 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 5 Nov 2024 14:14:52 +0100 Subject: [PATCH 0176/1670] Ignore directories created by `snapcraft --destructive-mode` --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index 42af04833c..45026a8c3e 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,9 @@ vendor/ # Go workspace file go.work + +# Directories created by `snapcraft --destructive-mode` +/parts +/prime +/stage + From 1a750d8e2cca46a9d87f3d28a0cf2b0287b38763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 24 Oct 2024 14:00:59 +0200 Subject: [PATCH 0177/1670] broker: Expose supported authentication modes in const variables We're using them everywhere in the various providers implementations, so prevent potential issues due to mistyping variables or anything like that, making also code easier to maintain and refactor if something is going to change. --- internal/broker/authmodes/consts.go | 13 +++++ internal/broker/broker.go | 21 ++++---- internal/broker/broker_test.go | 57 +++++++++++---------- internal/providers/msentraid/msentraid.go | 13 ++--- internal/providers/noprovider/noprovider.go | 13 ++--- internal/testutils/provider.go | 13 ++--- 6 files changed, 74 insertions(+), 56 deletions(-) create mode 100644 internal/broker/authmodes/consts.go diff --git a/internal/broker/authmodes/consts.go b/internal/broker/authmodes/consts.go new file mode 100644 index 0000000000..4d44bf3530 --- /dev/null +++ b/internal/broker/authmodes/consts.go @@ -0,0 +1,13 @@ +// Package authmodes lists the authentication modes that providers can support. +package authmodes + +const ( + // Password is the ID of the password authentication method. + Password = "password" + + // Device is the ID of the device authentication method. + Device = "device_auth" + + // NewPassword is the ID of the new password configuration method. + NewPassword = "newpassword" +) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 4a91604d8a..02a55cbe6f 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -20,6 +20,7 @@ import ( "github.com/coreos/go-oidc/v3/oidc" "github.com/google/uuid" + "github.com/ubuntu/authd-oidc-brokers/internal/broker/authmodes" "github.com/ubuntu/authd-oidc-brokers/internal/consts" "github.com/ubuntu/authd-oidc-brokers/internal/providers" providerErrors "github.com/ubuntu/authd-oidc-brokers/internal/providers/errors" @@ -218,7 +219,7 @@ func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []m endpoints := make(map[string]struct{}) if session.authCfg.provider != nil && session.authCfg.provider.Endpoint().DeviceAuthURL != "" { - endpoints["device_auth"] = struct{}{} + endpoints[authmodes.Device] = struct{}{} } availableModes, err := b.providerInfo.CurrentAuthenticationModesOffered( @@ -260,16 +261,16 @@ func (b *Broker) supportedAuthModesFromLayout(supportedUILayouts []map[string]st if !strings.Contains(layout["wait"], "true") { continue } - supportedModes["device_auth"] = "Device Authentication" + supportedModes[authmodes.Device] = "Device Authentication" case "form": if slices.Contains(supportedEntries, "chars_password") { - supportedModes["password"] = "Local Password Authentication" + supportedModes[authmodes.Password] = "Local Password Authentication" } case "newpassword": if slices.Contains(supportedEntries, "chars_password") { - supportedModes["newpassword"] = "Define your local password" + supportedModes[authmodes.NewPassword] = "Define your local password" } } } @@ -311,7 +312,7 @@ func (b *Broker) generateUILayout(session *sessionInfo, authModeID string) (map[ var uiLayout map[string]string switch authModeID { - case "device_auth": + case authmodes.Device: ctx, cancel := context.WithTimeout(context.Background(), maxRequestDuration) defer cancel() response, err := session.authCfg.oauth.DeviceAuth(ctx) @@ -332,14 +333,14 @@ func (b *Broker) generateUILayout(session *sessionInfo, authModeID string) (map[ "code": response.UserCode, } - case "password": + case authmodes.Password: uiLayout = map[string]string{ "type": "form", "label": "Enter your local password", "entry": "chars_password", } - case "newpassword": + case authmodes.NewPassword: label := "Create a local password" if session.mode == "passwd" { label = "Update your local password" @@ -433,7 +434,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo var authInfo authCachedInfo switch session.selectedMode { - case "device_auth": + case authmodes.Device: response, ok := session.authInfo["response"].(*oauth2.DeviceAuthResponse) if !ok { slog.Error("could not get device auth response") @@ -471,7 +472,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo session.authInfo["auth_info"] = authInfo return AuthNext, nil - case "password": + case authmodes.Password: authInfo, err = b.loadAuthInfo(ctx, session, challenge) if err != nil { slog.Error(err.Error()) @@ -496,7 +497,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo return AuthNext, nil } - case "newpassword": + case authmodes.NewPassword: if challenge == "" { return AuthRetry, errorMessage{Message: "empty challenge"} } diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 621d383d40..ee67dd46d1 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ubuntu/authd-oidc-brokers/internal/broker" + "github.com/ubuntu/authd-oidc-brokers/internal/broker/authmodes" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "github.com/ubuntu/authd-oidc-brokers/internal/testutils" "golang.org/x/oauth2" @@ -271,19 +272,19 @@ func TestSelectAuthenticationMode(t *testing.T) { wantErr bool }{ - "Successfully select password": {modeName: "password", tokenExists: true}, - "Successfully select device_auth": {modeName: "device_auth"}, - "Successfully select newpassword": {modeName: "newpassword", secondAuthStep: true}, + "Successfully select password": {modeName: authmodes.Password, tokenExists: true}, + "Successfully select device_auth": {modeName: authmodes.Device}, + "Successfully select newpassword": {modeName: authmodes.NewPassword, secondAuthStep: true}, - "Selected newpassword shows correct label in passwd session": {modeName: "newpassword", passwdSession: true, tokenExists: true, secondAuthStep: true}, + "Selected newpassword shows correct label in passwd session": {modeName: authmodes.NewPassword, passwdSession: true, tokenExists: true, secondAuthStep: true}, "Error when selecting invalid mode": {modeName: "invalid", wantErr: true}, - "Error when selecting device_auth but provider is unavailable": {modeName: "device_auth", wantErr: true, + "Error when selecting device_auth but provider is unavailable": {modeName: authmodes.Device, wantErr: true, customHandlers: map[string]testutils.ProviderHandler{ "/device_auth": testutils.UnavailableHandler(), }, }, - "Error when selecting device_auth but request times out": {modeName: "device_auth", wantErr: true, + "Error when selecting device_auth but request times out": {modeName: authmodes.Device, wantErr: true, customHandlers: map[string]testutils.ProviderHandler{ "/device_auth": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, @@ -371,19 +372,19 @@ func TestIsAuthenticated(t *testing.T) { readOnlyCacheDir bool }{ "Successfully authenticate user with QRCode+newpassword": {firstChallenge: "-", wantSecondCall: true}, - "Successfully authenticate user with password": {firstMode: "password", preexistentToken: "valid"}, + "Successfully authenticate user with password": {firstMode: authmodes.Password, preexistentToken: "valid"}, "Authenticating with qrcode reacquires token": {firstChallenge: "-", wantSecondCall: true, preexistentToken: "valid"}, - "Authenticating with password refreshes expired token": {firstMode: "password", preexistentToken: "expired"}, + "Authenticating with password refreshes expired token": {firstMode: authmodes.Password, preexistentToken: "expired"}, "Authenticating with password still allowed if server is unreachable": { - firstMode: "password", + firstMode: authmodes.Password, preexistentToken: "valid", customHandlers: map[string]testutils.ProviderHandler{ "/.well-known/openid-configuration": testutils.UnavailableHandler(), }, }, "Authenticating with password still allowed if token is expired and server is unreachable": { - firstMode: "password", + firstMode: authmodes.Password, preexistentToken: "expired", customHandlers: map[string]testutils.ProviderHandler{ "/.well-known/openid-configuration": testutils.UnavailableHandler(), @@ -399,27 +400,27 @@ func TestIsAuthenticated(t *testing.T) { }, "Error when authentication data is invalid": {invalidAuthData: true}, - "Error when challenge can not be decrypted": {firstMode: "password", badFirstKey: true}, - "Error when provided wrong challenge": {firstMode: "password", preexistentToken: "valid", firstChallenge: "wrongpassword"}, + "Error when challenge can not be decrypted": {firstMode: authmodes.Password, badFirstKey: true}, + "Error when provided wrong challenge": {firstMode: authmodes.Password, preexistentToken: "valid", firstChallenge: "wrongpassword"}, "Error when can not cache token": {firstChallenge: "-", wantSecondCall: true, readOnlyCacheDir: true}, "Error when IsAuthenticated is ongoing for session": {dontWaitForFirstCall: true, wantSecondCall: true}, - "Error when mode is password and token does not exist": {firstMode: "password"}, + "Error when mode is password and token does not exist": {firstMode: authmodes.Password}, "Error when mode is password but server returns error": { - firstMode: "password", + firstMode: authmodes.Password, preexistentToken: "expired", customHandlers: map[string]testutils.ProviderHandler{ "/token": testutils.BadRequestHandler(), }, }, - "Error when mode is password and token is invalid": {firstMode: "password", preexistentToken: "invalid"}, - "Error when mode is password and cached token can't be refreshed": {firstMode: "password", preexistentToken: "no-refresh"}, - "Error when mode is password and token refresh times out": {firstMode: "password", preexistentToken: "expired", + "Error when mode is password and token is invalid": {firstMode: authmodes.Password, preexistentToken: "invalid"}, + "Error when mode is password and cached token can't be refreshed": {firstMode: authmodes.Password, preexistentToken: "no-refresh"}, + "Error when mode is password and token refresh times out": {firstMode: authmodes.Password, preexistentToken: "expired", customHandlers: map[string]testutils.ProviderHandler{ "/token": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, }, - "Error when mode is password and can not fetch user info": {firstMode: "password", preexistentToken: "invalid-id"}, + "Error when mode is password and can not fetch user info": {firstMode: authmodes.Password, preexistentToken: "invalid-id"}, "Error when mode is qrcode and response is invalid": {firstAuthInfo: map[string]any{"response": "not a valid response"}}, "Error when mode is qrcode and link expires": { @@ -439,15 +440,15 @@ func TestIsAuthenticated(t *testing.T) { }, "Error when empty challenge is provided for local password": {firstChallenge: "-", wantSecondCall: true, secondChallenge: "-"}, "Error when mode is newpassword and token is not set": { - firstMode: "newpassword", + firstMode: authmodes.NewPassword, firstAuthInfo: map[string]any{"token": nil}, }, "Error when mode is newpassword and id token is not set": { - firstMode: "newpassword", + firstMode: authmodes.NewPassword, firstAuthInfo: map[string]any{"token": &oauth2.Token{}}, }, "Error when mode is newpassword and can not fetch user info": { - firstMode: "newpassword", + firstMode: authmodes.NewPassword, firstAuthInfo: map[string]any{ "token": (&oauth2.Token{}).WithExtra(map[string]interface{}{"id_token": "invalid"}), }, @@ -523,7 +524,7 @@ func TestIsAuthenticated(t *testing.T) { defer close(firstCallDone) if tc.firstMode == "" { - tc.firstMode = "device_auth" + tc.firstMode = authmodes.Device } updateAuthModes(t, b, sessionID, tc.firstMode) @@ -566,7 +567,7 @@ func TestIsAuthenticated(t *testing.T) { go func() { defer close(secondCallDone) - updateAuthModes(t, b, sessionID, "newpassword") + updateAuthModes(t, b, sessionID, authmodes.NewPassword) access, data, err := b.IsAuthenticated(sessionID, secondAuthData) require.True(t, json.Valid([]byte(data)), "IsAuthenticated returned data must be a valid JSON") @@ -641,12 +642,12 @@ func TestConcurrentIsAuthenticated(t *testing.T) { firstSession, firstKey := newSessionForTests(t, b, tc.firstUser, "") tok := generateCachedInfo(t, tc.firstUser, defaultProvider.URL) - err = b.CacheAuthInfo(firstSession, tok, "password") + err = b.CacheAuthInfo(firstSession, tok, authmodes.Password) require.NoError(t, err, "Setup: SaveToken should not have returned an error") secondSession, secondKey := newSessionForTests(t, b, tc.secondUser, "") tok = generateCachedInfo(t, tc.secondUser, defaultProvider.URL) - err = b.CacheAuthInfo(secondSession, tok, "password") + err = b.CacheAuthInfo(secondSession, tok, authmodes.Password) require.NoError(t, err, "Setup: SaveToken should not have returned an error") firstCallDone := make(chan struct{}) @@ -654,7 +655,7 @@ func TestConcurrentIsAuthenticated(t *testing.T) { t.Logf("%s: First auth starting", t.Name()) defer close(firstCallDone) - updateAuthModes(t, b, firstSession, "password") + updateAuthModes(t, b, firstSession, authmodes.Password) authData := `{"challenge":"` + encryptChallenge(t, "password", firstKey) + `"}` @@ -678,7 +679,7 @@ func TestConcurrentIsAuthenticated(t *testing.T) { t.Logf("%s: Second auth starting", t.Name()) defer close(secondCallDone) - updateAuthModes(t, b, secondSession, "password") + updateAuthModes(t, b, secondSession, authmodes.Password) authData := `{"challenge":"` + encryptChallenge(t, "password", secondKey) + `"}` @@ -810,7 +811,7 @@ func TestCancelIsAuthenticated(t *testing.T) { b := newBrokerForTests(t, broker.Config{IssuerURL: provider.URL}) sessionID, _ := newSessionForTests(t, b, "", "") - updateAuthModes(t, b, sessionID, "device_auth") + updateAuthModes(t, b, sessionID, authmodes.Device) stopped := make(chan struct{}) go func() { diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 08679081e9..35c320f4f0 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -17,6 +17,7 @@ import ( msgraphsdk "github.com/microsoftgraph/msgraph-sdk-go" msgraphauth "github.com/microsoftgraph/msgraph-sdk-go-core/authentication" msgraphmodels "github.com/microsoftgraph/msgraph-sdk-go/models" + "github.com/ubuntu/authd-oidc-brokers/internal/broker/authmodes" "github.com/ubuntu/authd-oidc-brokers/internal/consts" providerErrors "github.com/ubuntu/authd-oidc-brokers/internal/providers/errors" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" @@ -230,20 +231,20 @@ func (p Provider) CurrentAuthenticationModesOffered( if !tokenExists { return nil, errors.New("user has no cached token") } - offeredModes = []string{"password"} + offeredModes = []string{authmodes.Password} if currentAuthStep > 0 { - offeredModes = []string{"newpassword"} + offeredModes = []string{authmodes.NewPassword} } default: // auth mode - if _, ok := endpoints["device_auth"]; ok && providerReachable { - offeredModes = []string{"device_auth"} + if _, ok := endpoints[authmodes.Device]; ok && providerReachable { + offeredModes = []string{authmodes.Device} } if tokenExists { - offeredModes = append([]string{"password"}, offeredModes...) + offeredModes = append([]string{authmodes.Password}, offeredModes...) } if currentAuthStep > 0 { - offeredModes = []string{"newpassword"} + offeredModes = []string{authmodes.NewPassword} } } diff --git a/internal/providers/noprovider/noprovider.go b/internal/providers/noprovider/noprovider.go index 60cd94ca1f..707a667c06 100644 --- a/internal/providers/noprovider/noprovider.go +++ b/internal/providers/noprovider/noprovider.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/coreos/go-oidc/v3/oidc" + "github.com/ubuntu/authd-oidc-brokers/internal/broker/authmodes" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "golang.org/x/oauth2" ) @@ -50,20 +51,20 @@ func (p NoProvider) CurrentAuthenticationModesOffered( if !tokenExists { return nil, errors.New("user has no cached token") } - offeredModes = []string{"password"} + offeredModes = []string{authmodes.Password} if currentAuthStep > 0 { - offeredModes = []string{"newpassword"} + offeredModes = []string{authmodes.NewPassword} } default: // auth mode - if _, ok := endpoints["device_auth"]; ok && providerReachable { - offeredModes = []string{"device_auth"} + if _, ok := endpoints[authmodes.Device]; ok && providerReachable { + offeredModes = []string{authmodes.Device} } if tokenExists { - offeredModes = append([]string{"password"}, offeredModes...) + offeredModes = append([]string{authmodes.Password}, offeredModes...) } if currentAuthStep > 0 { - offeredModes = []string{"newpassword"} + offeredModes = []string{authmodes.NewPassword} } } diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index 7f1d875f25..a5af2e3ced 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -20,6 +20,7 @@ import ( "github.com/coreos/go-oidc/v3/oidc" "github.com/go-jose/go-jose/v4" "github.com/golang-jwt/jwt/v5" + "github.com/ubuntu/authd-oidc-brokers/internal/broker/authmodes" "github.com/ubuntu/authd-oidc-brokers/internal/consts" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "golang.org/x/oauth2" @@ -415,20 +416,20 @@ func (p MockProviderInfoer) CurrentAuthenticationModesOffered( if !tokenExists { return nil, errors.New("user has no cached token") } - offeredModes = []string{"password"} + offeredModes = []string{authmodes.Password} if currentAuthStep > 0 { - offeredModes = []string{"newpassword"} + offeredModes = []string{authmodes.NewPassword} } default: // auth mode - if _, ok := endpoints["device_auth"]; ok && providerReachable { - offeredModes = []string{"device_auth"} + if _, ok := endpoints[authmodes.Device]; ok && providerReachable { + offeredModes = []string{authmodes.Device} } if tokenExists { - offeredModes = append([]string{"password"}, offeredModes...) + offeredModes = append([]string{authmodes.Password}, offeredModes...) } if currentAuthStep > 0 { - offeredModes = []string{"newpassword"} + offeredModes = []string{authmodes.NewPassword} } } From 3cc5a619bf0c7150f62780d0341f834ecf3bfbf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 24 Oct 2024 14:53:49 +0200 Subject: [PATCH 0178/1670] testutils/provider: Use NoProvider as base for the mock provider So that we can test that too if there are no mock specific things and we can just reuse some logic of the base --- internal/testutils/provider.go | 61 +++------------------------------- 1 file changed, 4 insertions(+), 57 deletions(-) diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index a5af2e3ced..98537913c1 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -20,9 +20,9 @@ import ( "github.com/coreos/go-oidc/v3/oidc" "github.com/go-jose/go-jose/v4" "github.com/golang-jwt/jwt/v5" - "github.com/ubuntu/authd-oidc-brokers/internal/broker/authmodes" "github.com/ubuntu/authd-oidc-brokers/internal/consts" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/noprovider" "golang.org/x/oauth2" ) @@ -294,6 +294,7 @@ func ExpiryDeviceAuthHandler() ProviderHandler { // MockProviderInfoer is a mock that implements the ProviderInfoer interface. type MockProviderInfoer struct { + noprovider.NoProvider Scopes []string Options []oauth2.AuthCodeOption Groups []info.Group @@ -325,7 +326,7 @@ func (p *MockProviderInfoer) AdditionalScopes() []string { if p.Scopes != nil { return p.Scopes } - return []string{oidc.ScopeOfflineAccess} + return p.NoProvider.AdditionalScopes() } // AuthOptions returns the additional options required by the provider. @@ -333,12 +334,7 @@ func (p *MockProviderInfoer) AuthOptions() []oauth2.AuthCodeOption { if p.Options != nil { return p.Options } - return []oauth2.AuthCodeOption{} -} - -// GetExtraFields returns the extra fields of the token which should be stored persistently. -func (p *MockProviderInfoer) GetExtraFields(token *oauth2.Token) map[string]interface{} { - return nil + return p.NoProvider.AuthOptions() } // GetUserInfo is a no-op when no specific provider is in use. @@ -400,52 +396,3 @@ func (p *MockProviderInfoer) getGroups(*oauth2.Token) ([]info.Group, error) { } return nil, nil } - -// CurrentAuthenticationModesOffered returns the authentication modes supported by the provider. -func (p MockProviderInfoer) CurrentAuthenticationModesOffered( - sessionMode string, - supportedAuthModes map[string]string, - tokenExists bool, - providerReachable bool, - endpoints map[string]struct{}, - currentAuthStep int, -) ([]string, error) { - var offeredModes []string - switch sessionMode { - case "passwd": - if !tokenExists { - return nil, errors.New("user has no cached token") - } - offeredModes = []string{authmodes.Password} - if currentAuthStep > 0 { - offeredModes = []string{authmodes.NewPassword} - } - - default: // auth mode - if _, ok := endpoints[authmodes.Device]; ok && providerReachable { - offeredModes = []string{authmodes.Device} - } - if tokenExists { - offeredModes = append([]string{authmodes.Password}, offeredModes...) - } - if currentAuthStep > 0 { - offeredModes = []string{authmodes.NewPassword} - } - } - - for _, mode := range offeredModes { - if _, ok := supportedAuthModes[mode]; !ok { - return nil, fmt.Errorf("auth mode %q required by the provider, but is not supported locally", mode) - } - } - - return offeredModes, nil -} - -// VerifyUsername checks if the requested username matches the authenticated user. -func (p *MockProviderInfoer) VerifyUsername(requestedUsername, username string) error { - if requestedUsername != username { - return fmt.Errorf("requested username %q does not match the authenticated user %q", requestedUsername, username) - } - return nil -} From 65dfecf49efaa2084171f146f0ed66e89c6e4e4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 24 Oct 2024 14:57:54 +0200 Subject: [PATCH 0179/1670] broker: Support Device Authentication without Qr code We still return the device authentication with qr code rendering by default, but if authd explicitly tells use that the interface does not support qr code rendering, then we adapt our strings to that case --- internal/broker/authmodes/consts.go | 3 ++ internal/broker/broker.go | 37 ++++++++++++++----- internal/broker/broker_test.go | 10 ++--- .../get_device_auth_if_there_is_no_token | 2 +- ...t_password_and_device_auth_if_token_exists | 2 +- internal/providers/msentraid/msentraid.go | 4 +- internal/providers/noprovider/noprovider.go | 4 +- 7 files changed, 44 insertions(+), 18 deletions(-) diff --git a/internal/broker/authmodes/consts.go b/internal/broker/authmodes/consts.go index 4d44bf3530..19fa8aedb0 100644 --- a/internal/broker/authmodes/consts.go +++ b/internal/broker/authmodes/consts.go @@ -8,6 +8,9 @@ const ( // Device is the ID of the device authentication method. Device = "device_auth" + // DeviceQr is the ID of the device authentication method when QrCode rendering is enabled. + DeviceQr = "device_auth_qr" + // NewPassword is the ID of the new password configuration method. NewPassword = "newpassword" ) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 02a55cbe6f..56db37fe8a 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -219,7 +219,14 @@ func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []m endpoints := make(map[string]struct{}) if session.authCfg.provider != nil && session.authCfg.provider.Endpoint().DeviceAuthURL != "" { - endpoints[authmodes.Device] = struct{}{} + authMode := authmodes.DeviceQr + if _, ok := supportedAuthModes[authMode]; ok { + endpoints[authMode] = struct{}{} + } + authMode = authmodes.Device + if _, ok := supportedAuthModes[authMode]; ok { + endpoints[authMode] = struct{}{} + } } availableModes, err := b.providerInfo.CurrentAuthenticationModesOffered( @@ -261,7 +268,11 @@ func (b *Broker) supportedAuthModesFromLayout(supportedUILayouts []map[string]st if !strings.Contains(layout["wait"], "true") { continue } - supportedModes[authmodes.Device] = "Device Authentication" + deviceAuthID := authmodes.DeviceQr + if layout["renders_qrcode"] == "false" { + deviceAuthID = authmodes.Device + } + supportedModes[deviceAuthID] = "Device Authentication" case "form": if slices.Contains(supportedEntries, "chars_password") { @@ -312,21 +323,29 @@ func (b *Broker) generateUILayout(session *sessionInfo, authModeID string) (map[ var uiLayout map[string]string switch authModeID { - case authmodes.Device: + case authmodes.Device, authmodes.DeviceQr: ctx, cancel := context.WithTimeout(context.Background(), maxRequestDuration) defer cancel() response, err := session.authCfg.oauth.DeviceAuth(ctx) if err != nil { - return nil, fmt.Errorf("could not generate QR code layout: %v", err) + return nil, fmt.Errorf("could not generate Device Authentication code layout: %v", err) } session.authInfo["response"] = response - uiLayout = map[string]string{ - "type": "qrcode", - "label": fmt.Sprintf( + label := fmt.Sprintf( + "Access %q and use the provided login code", + response.VerificationURI, + ) + if authModeID == authmodes.DeviceQr { + label = fmt.Sprintf( "Scan the QR code or access %q and use the provided login code", response.VerificationURI, - ), + ) + } + + uiLayout = map[string]string{ + "type": "qrcode", + "label": label, "wait": "true", "button": "Request new login code", "content": response.VerificationURI, @@ -434,7 +453,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo var authInfo authCachedInfo switch session.selectedMode { - case authmodes.Device: + case authmodes.Device, authmodes.DeviceQr: response, ok := session.authInfo["response"].(*oauth2.DeviceAuthResponse) if !ok { slog.Error("could not get device auth response") diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index ee67dd46d1..43cee569f9 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -273,18 +273,18 @@ func TestSelectAuthenticationMode(t *testing.T) { wantErr bool }{ "Successfully select password": {modeName: authmodes.Password, tokenExists: true}, - "Successfully select device_auth": {modeName: authmodes.Device}, + "Successfully select device_auth": {modeName: authmodes.DeviceQr}, "Successfully select newpassword": {modeName: authmodes.NewPassword, secondAuthStep: true}, "Selected newpassword shows correct label in passwd session": {modeName: authmodes.NewPassword, passwdSession: true, tokenExists: true, secondAuthStep: true}, "Error when selecting invalid mode": {modeName: "invalid", wantErr: true}, - "Error when selecting device_auth but provider is unavailable": {modeName: authmodes.Device, wantErr: true, + "Error when selecting device_auth but provider is unavailable": {modeName: authmodes.DeviceQr, wantErr: true, customHandlers: map[string]testutils.ProviderHandler{ "/device_auth": testutils.UnavailableHandler(), }, }, - "Error when selecting device_auth but request times out": {modeName: authmodes.Device, wantErr: true, + "Error when selecting device_auth but request times out": {modeName: authmodes.DeviceQr, wantErr: true, customHandlers: map[string]testutils.ProviderHandler{ "/device_auth": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, @@ -524,7 +524,7 @@ func TestIsAuthenticated(t *testing.T) { defer close(firstCallDone) if tc.firstMode == "" { - tc.firstMode = authmodes.Device + tc.firstMode = authmodes.DeviceQr } updateAuthModes(t, b, sessionID, tc.firstMode) @@ -811,7 +811,7 @@ func TestCancelIsAuthenticated(t *testing.T) { b := newBrokerForTests(t, broker.Config{IssuerURL: provider.URL}) sessionID, _ := newSessionForTests(t, b, "", "") - updateAuthModes(t, b, sessionID, authmodes.Device) + updateAuthModes(t, b, sessionID, authmodes.DeviceQr) stopped := make(chan struct{}) go func() { diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_device_auth_if_there_is_no_token b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_device_auth_if_there_is_no_token index b4398710b8..0742ddffe6 100644 --- a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_device_auth_if_there_is_no_token +++ b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_device_auth_if_there_is_no_token @@ -1,2 +1,2 @@ -- id: device_auth +- id: device_auth_qr label: Device Authentication diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_password_and_device_auth_if_token_exists b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_password_and_device_auth_if_token_exists index 39b3df5a1d..6327db6363 100644 --- a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_password_and_device_auth_if_token_exists +++ b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_password_and_device_auth_if_token_exists @@ -1,4 +1,4 @@ - id: password label: Local Password Authentication -- id: device_auth +- id: device_auth_qr label: Device Authentication diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 35c320f4f0..47d1abfbca 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -237,7 +237,9 @@ func (p Provider) CurrentAuthenticationModesOffered( } default: // auth mode - if _, ok := endpoints[authmodes.Device]; ok && providerReachable { + if _, ok := endpoints[authmodes.DeviceQr]; ok && providerReachable { + offeredModes = []string{authmodes.DeviceQr} + } else if _, ok := endpoints[authmodes.Device]; ok && providerReachable { offeredModes = []string{authmodes.Device} } if tokenExists { diff --git a/internal/providers/noprovider/noprovider.go b/internal/providers/noprovider/noprovider.go index 707a667c06..e0cbec709d 100644 --- a/internal/providers/noprovider/noprovider.go +++ b/internal/providers/noprovider/noprovider.go @@ -57,7 +57,9 @@ func (p NoProvider) CurrentAuthenticationModesOffered( } default: // auth mode - if _, ok := endpoints[authmodes.Device]; ok && providerReachable { + if _, ok := endpoints[authmodes.DeviceQr]; ok && providerReachable { + offeredModes = []string{authmodes.DeviceQr} + } else if _, ok := endpoints[authmodes.Device]; ok && providerReachable { offeredModes = []string{authmodes.Device} } if tokenExists { From ecd12381d0f93c2c0ef93a9921aaa1af65461922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 24 Oct 2024 15:10:04 +0200 Subject: [PATCH 0180/1670] broker: Rename test cases using device_auth_qr to be more explicit --- internal/broker/broker_test.go | 28 +++++++++---------- ...> get_device_auth_qr_if_there_is_no_token} | 0 ...already_authenticated_with_device_auth_qr} | 0 ..._provider_does_not_support_device_auth_qr} | 0 ...ssword_and_device_auth_qr_if_token_exists} | 0 ...uth => successfully_select_device_auth_qr} | 0 6 files changed, 14 insertions(+), 14 deletions(-) rename internal/broker/testdata/TestGetAuthenticationModes/golden/{get_device_auth_if_there_is_no_token => get_device_auth_qr_if_there_is_no_token} (100%) rename internal/broker/testdata/TestGetAuthenticationModes/golden/{get_newpassword_if_already_authenticated_with_device_auth => get_newpassword_if_already_authenticated_with_device_auth_qr} (100%) rename internal/broker/testdata/TestGetAuthenticationModes/golden/{get_only_password_if_token_exists_and_provider_does_not_support_device_auth => get_only_password_if_token_exists_and_provider_does_not_support_device_auth_qr} (100%) rename internal/broker/testdata/TestGetAuthenticationModes/golden/{get_password_and_device_auth_if_token_exists => get_password_and_device_auth_qr_if_token_exists} (100%) rename internal/broker/testdata/TestSelectAuthenticationMode/golden/{successfully_select_device_auth => successfully_select_device_auth_qr} (100%) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 43cee569f9..78954c7f7e 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -167,12 +167,12 @@ func TestGetAuthenticationModes(t *testing.T) { wantErr bool }{ // Auth Session - "Get device_auth if there is no token": {}, - "Get newpassword if already authenticated with device_auth": {secondAuthStep: true}, - "Get password and device_auth if token exists": {tokenExists: true}, + "Get device_auth_qr if there is no token": {}, + "Get newpassword if already authenticated with device_auth_qr": {secondAuthStep: true}, + "Get password and device_auth_qr if token exists": {tokenExists: true}, - "Get only password if token exists and provider is not available": {tokenExists: true, providerAddress: "127.0.0.1:31310", unavailableProvider: true}, - "Get only password if token exists and provider does not support device_auth": {tokenExists: true, providerAddress: "127.0.0.1:31311", deviceAuthUnsupported: true}, + "Get only password if token exists and provider is not available": {tokenExists: true, providerAddress: "127.0.0.1:31310", unavailableProvider: true}, + "Get only password if token exists and provider does not support device_auth_qr": {tokenExists: true, providerAddress: "127.0.0.1:31311", deviceAuthUnsupported: true}, // Passwd Session "Get only password if token exists and session is passwd": {sessionMode: "passwd", tokenExists: true}, @@ -181,10 +181,10 @@ func TestGetAuthenticationModes(t *testing.T) { "Error if there is no session": {sessionID: "-", wantErr: true}, // General errors - "Error if no authentication mode is supported": {providerAddress: "127.0.0.1:31312", deviceAuthUnsupported: true, wantErr: true}, - "Error if expecting device_auth but not supported": {supportedLayouts: []string{"qrcode-without-wait"}, wantErr: true}, - "Error if expecting newpassword but not supported": {supportedLayouts: []string{"newpassword-without-entry"}, wantErr: true}, - "Error if expecting password but not supported": {supportedLayouts: []string{"form-without-entry"}, wantErr: true}, + "Error if no authentication mode is supported": {providerAddress: "127.0.0.1:31312", deviceAuthUnsupported: true, wantErr: true}, + "Error if expecting device_auth_qr but not supported": {supportedLayouts: []string{"qrcode-without-wait"}, wantErr: true}, + "Error if expecting newpassword but not supported": {supportedLayouts: []string{"newpassword-without-entry"}, wantErr: true}, + "Error if expecting password but not supported": {supportedLayouts: []string{"form-without-entry"}, wantErr: true}, // Passwd session errors "Error if session is passwd but token does not exist": {sessionMode: "passwd", wantErr: true}, @@ -272,19 +272,19 @@ func TestSelectAuthenticationMode(t *testing.T) { wantErr bool }{ - "Successfully select password": {modeName: authmodes.Password, tokenExists: true}, - "Successfully select device_auth": {modeName: authmodes.DeviceQr}, - "Successfully select newpassword": {modeName: authmodes.NewPassword, secondAuthStep: true}, + "Successfully select password": {modeName: authmodes.Password, tokenExists: true}, + "Successfully select device_auth_qr": {modeName: authmodes.DeviceQr}, + "Successfully select newpassword": {modeName: authmodes.NewPassword, secondAuthStep: true}, "Selected newpassword shows correct label in passwd session": {modeName: authmodes.NewPassword, passwdSession: true, tokenExists: true, secondAuthStep: true}, "Error when selecting invalid mode": {modeName: "invalid", wantErr: true}, - "Error when selecting device_auth but provider is unavailable": {modeName: authmodes.DeviceQr, wantErr: true, + "Error when selecting device_auth_qr but provider is unavailable": {modeName: authmodes.DeviceQr, wantErr: true, customHandlers: map[string]testutils.ProviderHandler{ "/device_auth": testutils.UnavailableHandler(), }, }, - "Error when selecting device_auth but request times out": {modeName: authmodes.DeviceQr, wantErr: true, + "Error when selecting device_auth_qr but request times out": {modeName: authmodes.DeviceQr, wantErr: true, customHandlers: map[string]testutils.ProviderHandler{ "/device_auth": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_device_auth_if_there_is_no_token b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_device_auth_qr_if_there_is_no_token similarity index 100% rename from internal/broker/testdata/TestGetAuthenticationModes/golden/get_device_auth_if_there_is_no_token rename to internal/broker/testdata/TestGetAuthenticationModes/golden/get_device_auth_qr_if_there_is_no_token diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_device_auth b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_device_auth_qr similarity index 100% rename from internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_device_auth rename to internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_device_auth_qr diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_provider_does_not_support_device_auth b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_provider_does_not_support_device_auth_qr similarity index 100% rename from internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_provider_does_not_support_device_auth rename to internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_provider_does_not_support_device_auth_qr diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_password_and_device_auth_if_token_exists b/internal/broker/testdata/TestGetAuthenticationModes/golden/get_password_and_device_auth_qr_if_token_exists similarity index 100% rename from internal/broker/testdata/TestGetAuthenticationModes/golden/get_password_and_device_auth_if_token_exists rename to internal/broker/testdata/TestGetAuthenticationModes/golden/get_password_and_device_auth_qr_if_token_exists diff --git a/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_device_auth b/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_device_auth_qr similarity index 100% rename from internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_device_auth rename to internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_device_auth_qr From ccaa82e7b12e592a4d6cd9846d8889ae0dad5347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 24 Oct 2024 15:42:01 +0200 Subject: [PATCH 0181/1670] broker: Remove unused golden file --- ...y_fetch_user_info_ignoring_different_casing_in_name | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_ignoring_different_casing_in_name diff --git a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_ignoring_different_casing_in_name b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_ignoring_different_casing_in_name deleted file mode 100644 index 6b8a8aafc3..0000000000 --- a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_ignoring_different_casing_in_name +++ /dev/null @@ -1,10 +0,0 @@ -name: TEST-USER@EMAIL.COM -uuid: saved-user-id -home: /home/userInfoTests/TEST-USER@EMAIL.COM -shell: /usr/bin/bash -gecos: TEST-USER@EMAIL.COM -groups: - - name: remote-group - ugid: "12345" - - name: linux-local-group - ugid: "" From 5a59439f6a95a3c1601a052474c85da51c08b70d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 24 Oct 2024 15:36:09 +0200 Subject: [PATCH 0182/1670] broker: Add tests for using qrcode mode without qrcode rendering --- internal/broker/broker.go | 3 + internal/broker/broker_test.go | 69 +++++++++++++++++-- .../provider_url/test-user@email.com.cache | 0 .../first_call | 0 .../second_call | 0 .../cache/.empty | 0 .../first_call | 3 + .../cache/.empty | 0 .../first_call | 3 + .../cache/.empty | 0 .../first_call | 3 + .../cache/.empty | 0 .../first_call | 3 + .../provider_url/test-user@email.com.cache | 1 + .../first_call | 3 + .../second_call | 3 + .../provider_url/test-user@email.com.cache | 1 + .../first_call | 3 + .../second_call | 3 + .../golden/successfully_select_device_auth | 6 ++ 20 files changed, 97 insertions(+), 7 deletions(-) rename internal/broker/testdata/TestIsAuthenticated/golden/{successfully_authenticate_user_with_qrcode+newpassword => authenticating_with_link_code_reacquires_token}/cache/provider_url/test-user@email.com.cache (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{successfully_authenticate_user_with_qrcode+newpassword => authenticating_with_link_code_reacquires_token}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{successfully_authenticate_user_with_qrcode+newpassword => authenticating_with_link_code_reacquires_token}/second_call (100%) create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token/cache/.empty create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/cache/.empty create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_link_expires/cache/.empty create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_link_expires/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_response_is_invalid/cache/.empty create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_response_is_invalid/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/cache/provider_url/test-user@email.com.cache create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/second_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/cache/provider_url/test-user@email.com.cache create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/second_call create mode 100644 internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_device_auth diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 56db37fe8a..f4437c7307 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -213,6 +213,9 @@ func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []m supportedAuthModes := b.supportedAuthModesFromLayout(supportedUILayouts) + slog.Debug(fmt.Sprintf("Supported UI Layouts for session %s: %#v", sessionID, supportedUILayouts)) + slog.Debug(fmt.Sprintf("Supported Authentication modes for session %s: %#v", sessionID, supportedAuthModes)) + // Checks if the token exists in the cache. _, err = os.Stat(session.cachePath) tokenExists := err == nil diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 78954c7f7e..505759f20b 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -140,6 +140,15 @@ var supportedUILayouts = map[string]map[string]string{ "qrcode-without-wait": { "type": "qrcode", }, + "qrcode-without-qrcode": { + "type": "qrcode", + "renders_qrcode": "false", + "wait": "true", + }, + "qrcode-without-wait-and-qrcode": { + "type": "qrcode", + "renders_qrcode": "false", + }, "newpassword": { "type": "newpassword", @@ -183,6 +192,7 @@ func TestGetAuthenticationModes(t *testing.T) { // General errors "Error if no authentication mode is supported": {providerAddress: "127.0.0.1:31312", deviceAuthUnsupported: true, wantErr: true}, "Error if expecting device_auth_qr but not supported": {supportedLayouts: []string{"qrcode-without-wait"}, wantErr: true}, + "Error if expecting device_auth but not supported": {supportedLayouts: []string{"qrcode-without-wait-and-qrcode"}, wantErr: true}, "Error if expecting newpassword but not supported": {supportedLayouts: []string{"newpassword-without-entry"}, wantErr: true}, "Error if expecting password but not supported": {supportedLayouts: []string{"form-without-entry"}, wantErr: true}, @@ -259,21 +269,29 @@ var supportedLayouts = []map[string]string{ supportedUILayouts["newpassword"], } +var supportedLayoutsWithoutQrCode = []map[string]string{ + supportedUILayouts["form"], + supportedUILayouts["qrcode-without-qrcode"], + supportedUILayouts["newpassword"], +} + func TestSelectAuthenticationMode(t *testing.T) { t.Parallel() tests := map[string]struct { modeName string - tokenExists bool - secondAuthStep bool - passwdSession bool - customHandlers map[string]testutils.ProviderHandler + tokenExists bool + secondAuthStep bool + passwdSession bool + customHandlers map[string]testutils.ProviderHandler + supportedLayouts []map[string]string wantErr bool }{ "Successfully select password": {modeName: authmodes.Password, tokenExists: true}, "Successfully select device_auth_qr": {modeName: authmodes.DeviceQr}, + "Successfully select device_auth": {supportedLayouts: supportedLayoutsWithoutQrCode, modeName: authmodes.Device}, "Successfully select newpassword": {modeName: authmodes.NewPassword, secondAuthStep: true}, "Selected newpassword shows correct label in passwd session": {modeName: authmodes.NewPassword, passwdSession: true, tokenExists: true, secondAuthStep: true}, @@ -284,11 +302,27 @@ func TestSelectAuthenticationMode(t *testing.T) { "/device_auth": testutils.UnavailableHandler(), }, }, + "Error when selecting device_auth but provider is unavailable": { + supportedLayouts: supportedLayoutsWithoutQrCode, + modeName: authmodes.Device, + customHandlers: map[string]testutils.ProviderHandler{ + "/device_auth": testutils.UnavailableHandler(), + }, + wantErr: true, + }, "Error when selecting device_auth_qr but request times out": {modeName: authmodes.DeviceQr, wantErr: true, customHandlers: map[string]testutils.ProviderHandler{ "/device_auth": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, }, + "Error when selecting device_auth but request times out": { + supportedLayouts: supportedLayoutsWithoutQrCode, + modeName: authmodes.Device, + customHandlers: map[string]testutils.ProviderHandler{ + "/device_auth": testutils.HangingHandler(broker.MaxRequestDuration + 1), + }, + wantErr: true, + }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { @@ -322,9 +356,12 @@ func TestSelectAuthenticationMode(t *testing.T) { if tc.secondAuthStep { b.UpdateSessionAuthStep(sessionID, 1) } + if tc.supportedLayouts == nil { + tc.supportedLayouts = supportedLayouts + } // We need to do a GAM call first to get all the modes. - _, err := b.GetAuthenticationModes(sessionID, supportedLayouts) + _, err := b.GetAuthenticationModes(sessionID, tc.supportedLayouts) require.NoError(t, err, "Setup: GetAuthenticationModes should not have returned an error") got, err := b.SelectAuthenticationMode(sessionID, tc.modeName) @@ -371,10 +408,12 @@ func TestIsAuthenticated(t *testing.T) { dontWaitForFirstCall bool readOnlyCacheDir bool }{ - "Successfully authenticate user with QRCode+newpassword": {firstChallenge: "-", wantSecondCall: true}, - "Successfully authenticate user with password": {firstMode: authmodes.Password, preexistentToken: "valid"}, + "Successfully authenticate user with qrcode and newpassword": {firstChallenge: "-", wantSecondCall: true}, + "Successfully authenticate user with link code and newpassword": {firstMode: authmodes.Device, firstChallenge: "-", wantSecondCall: true}, + "Successfully authenticate user with password": {firstMode: authmodes.Password, preexistentToken: "valid"}, "Authenticating with qrcode reacquires token": {firstChallenge: "-", wantSecondCall: true, preexistentToken: "valid"}, + "Authenticating with link code reacquires token": {firstMode: authmodes.Device, firstChallenge: "-", wantSecondCall: true, preexistentToken: "valid"}, "Authenticating with password refreshes expired token": {firstMode: authmodes.Password, preexistentToken: "expired"}, "Authenticating with password still allowed if server is unreachable": { firstMode: authmodes.Password, @@ -438,6 +477,22 @@ func TestIsAuthenticated(t *testing.T) { "/token": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, }, + "Error when mode is link code and response is invalid": {firstAuthInfo: map[string]any{"response": "not a valid response"}}, + "Error when mode is link code and link expires": { + customHandlers: map[string]testutils.ProviderHandler{ + "/device_auth": testutils.ExpiryDeviceAuthHandler(), + }, + }, + "Error when mode is link code and can not get token": { + customHandlers: map[string]testutils.ProviderHandler{ + "/token": testutils.UnavailableHandler(), + }, + }, + "Error when mode is link code and can not get token due to timeout": { + customHandlers: map[string]testutils.ProviderHandler{ + "/token": testutils.HangingHandler(broker.MaxRequestDuration + 1), + }, + }, "Error when empty challenge is provided for local password": {firstChallenge: "-", wantSecondCall: true, secondChallenge: "-"}, "Error when mode is newpassword and token is not set": { firstMode: authmodes.NewPassword, diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/cache/provider_url/test-user@email.com.cache similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/cache/provider_url/test-user@email.com.cache rename to internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/cache/provider_url/test-user@email.com.cache diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/second_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/second_call rename to internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/second_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token/cache/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token/first_call new file mode 100644 index 0000000000..78e15876e9 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token/first_call @@ -0,0 +1,3 @@ +access: retry +data: '{"message":"authentication failure: could not authenticate user remotely"}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/cache/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/first_call new file mode 100644 index 0000000000..78e15876e9 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/first_call @@ -0,0 +1,3 @@ +access: retry +data: '{"message":"authentication failure: could not authenticate user remotely"}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_link_expires/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_link_expires/cache/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_link_expires/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_link_expires/first_call new file mode 100644 index 0000000000..78e15876e9 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_link_expires/first_call @@ -0,0 +1,3 @@ +access: retry +data: '{"message":"authentication failure: could not authenticate user remotely"}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_response_is_invalid/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_response_is_invalid/cache/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_response_is_invalid/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_response_is_invalid/first_call new file mode 100644 index 0000000000..51c8345079 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_response_is_invalid/first_call @@ -0,0 +1,3 @@ +access: denied +data: '{"message":"authentication failure: could not get required response"}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/cache/provider_url/test-user@email.com.cache new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/cache/provider_url/test-user@email.com.cache @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/first_call new file mode 100644 index 0000000000..d0887a134f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/first_call @@ -0,0 +1,3 @@ +access: next +data: '{}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/second_call new file mode 100644 index 0000000000..3c4c96bc15 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/second_call @@ -0,0 +1,3 @@ +access: granted +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/cache/provider_url/test-user@email.com.cache new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/cache/provider_url/test-user@email.com.cache @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/first_call new file mode 100644 index 0000000000..d0887a134f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/first_call @@ -0,0 +1,3 @@ +access: next +data: '{}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/second_call new file mode 100644 index 0000000000..3c4c96bc15 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/second_call @@ -0,0 +1,3 @@ +access: granted +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +err: diff --git a/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_device_auth b/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_device_auth new file mode 100644 index 0000000000..ca9a149523 --- /dev/null +++ b/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_device_auth @@ -0,0 +1,6 @@ +button: Request new login code +code: user_code +content: https://verification_uri.com +label: Access "https://verification_uri.com" and use the provided login code +type: qrcode +wait: "true" From ba3cdb91fac15c29c86345319fb062e9e3239171 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 6 Nov 2024 18:32:35 +0100 Subject: [PATCH 0183/1670] Fix D-Bus XML "a{s}s" is not a valid GVariant type, it should be "a{ss}" instead. --- internal/dbusservice/dbusservice.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/dbusservice/dbusservice.go b/internal/dbusservice/dbusservice.go index 273501b4e2..258410c47b 100644 --- a/internal/dbusservice/dbusservice.go +++ b/internal/dbusservice/dbusservice.go @@ -26,13 +26,13 @@ const intro = ` - - + + - + From ad80cb4b909e40fcee7226b02a8ed71ee909b139 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 14 Oct 2024 19:28:12 +0200 Subject: [PATCH 0184/1670] Fix debug output --- internal/providers/msentraid/msentraid.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 47d1abfbca..e2e200b82c 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -210,7 +210,15 @@ func getAllUserGroups(client *msgraphsdk.GraphServiceClient) ([]msgraphmodels.Gr groups = append(groups, result.GetValue()...) } - slog.Debug(fmt.Sprintf("Got groups: %v", groups)) + var groupNames []string + for _, group := range groups { + groupNamePtr := group.GetDisplayName() + if groupNamePtr != nil { + groupNames = append(groupNames, *groupNamePtr) + } + } + slog.Debug(fmt.Sprintf("Got groups: %s", strings.Join(groupNames, ", "))) + return groups, nil } From c00d2334f92d2c6df2c51b2a56f82179f552e5b7 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 4 Sep 2024 23:30:27 +0200 Subject: [PATCH 0185/1670] Add debug output These messages should help debug cases where no authentication mode is offered. --- internal/providers/msentraid/msentraid.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index e2e200b82c..c488d2f1cb 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -233,6 +233,7 @@ func (p Provider) CurrentAuthenticationModesOffered( endpoints map[string]struct{}, currentAuthStep int, ) ([]string, error) { + slog.Debug(fmt.Sprintf("In CurrentAuthenticationModesOffered: sessionMode=%q, supportedAuthModes=%q, tokenExists=%t, providerReachable=%t, endpoints=%q, currentAuthStep=%d\n", sessionMode, supportedAuthModes, tokenExists, providerReachable, endpoints, currentAuthStep)) var offeredModes []string switch sessionMode { case "passwd": @@ -257,6 +258,7 @@ func (p Provider) CurrentAuthenticationModesOffered( offeredModes = []string{authmodes.NewPassword} } } + slog.Debug(fmt.Sprintf("Offered modes: %q", offeredModes)) for _, mode := range offeredModes { if _, ok := supportedAuthModes[mode]; !ok { From 5aa5fe221f62070eb30bb16f9d65161aa35187d6 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 4 Sep 2024 20:52:51 +0200 Subject: [PATCH 0186/1670] refactor: Re-use Config struct in Broker struct ... to avoid having to pass all fields from the Config struct to the Broker struct. --- internal/broker/broker.go | 35 ++++++++++++++-------------------- internal/broker/export_test.go | 2 +- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index f4437c7307..c10fa4c9bd 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -45,14 +45,11 @@ type Config struct { // Broker is the real implementation of the broker to track sessions and process oidc calls. type Broker struct { + cfg Config + providerInfo providers.ProviderInfoer - issuerURL string oidcCfg oidc.Config - cachePath string - homeDirPath string - allowedSSHSuffixes []string - currentSessions map[string]sessionInfo currentSessionsMu sync.RWMutex @@ -121,9 +118,8 @@ func New(cfg Config, args ...Option) (b *Broker, err error) { return nil, err } - homeDirPath := "/home" - if cfg.HomeBaseDir != "" { - homeDirPath = cfg.HomeBaseDir + if cfg.HomeBaseDir == "" { + cfg.HomeBaseDir = "/home" } // Generate a new private key for the broker. @@ -134,13 +130,10 @@ func New(cfg Config, args ...Option) (b *Broker, err error) { } b = &Broker{ - providerInfo: opts.providerInfo, - issuerURL: cfg.IssuerURL, - oidcCfg: oidc.Config{ClientID: cfg.ClientID}, - cachePath: cfg.CachePath, - homeDirPath: homeDirPath, - allowedSSHSuffixes: cfg.AllowedSSHSuffixes, - privateKey: privateKey, + cfg: cfg, + providerInfo: opts.providerInfo, + oidcCfg: oidc.Config{ClientID: cfg.ClientID}, + privateKey: privateKey, currentSessions: make(map[string]sessionInfo), currentSessionsMu: sync.RWMutex{}, @@ -167,10 +160,10 @@ func (b *Broker) NewSession(username, lang, mode string) (sessionID, encryptionK return "", "", err } - _, url, _ := strings.Cut(b.issuerURL, "://") + _, url, _ := strings.Cut(b.cfg.IssuerURL, "://") url = strings.ReplaceAll(url, "/", "_") url = strings.ReplaceAll(url, ":", "_") - session.cachePath = filepath.Join(b.cachePath, url, username+".cache") + session.cachePath = filepath.Join(b.cfg.CachePath, url, username+".cache") // Check whether to start the session in offline mode. session.authCfg, err = b.connectToProvider(context.Background()) @@ -190,7 +183,7 @@ func (b *Broker) connectToProvider(ctx context.Context) (authCfg authConfig, err ctx, cancel := context.WithTimeout(ctx, maxRequestDuration) defer cancel() - provider, err := oidc.NewProvider(ctx, b.issuerURL) + provider, err := oidc.NewProvider(ctx, b.cfg.IssuerURL) if err != nil { return authConfig{}, err } @@ -607,7 +600,7 @@ func (b *Broker) CancelIsAuthenticated(sessionID string) { // UserPreCheck checks if the user is valid and can be allowed to authenticate. func (b *Broker) UserPreCheck(username string) (string, error) { found := false - for _, suffix := range b.allowedSSHSuffixes { + for _, suffix := range b.cfg.AllowedSSHSuffixes { if strings.HasSuffix(username, suffix) { found = true break @@ -618,7 +611,7 @@ func (b *Broker) UserPreCheck(username string) (string, error) { return "", errors.New("username does not match the allowed suffixes") } - u := info.NewUser(username, filepath.Join(b.homeDirPath, username), "", "", "", nil) + u := info.NewUser(username, filepath.Join(b.cfg.HomeBaseDir, username), "", "", "", nil) encoded, err := json.Marshal(u) if err != nil { return "", fmt.Errorf("could not marshal user info: %v", err) @@ -755,7 +748,7 @@ func (b *Broker) fetchUserInfo(ctx context.Context, session *sessionInfo, t *aut // This means that home was not provided by the claims, so we need to set it to the broker default. if !filepath.IsAbs(userInfo.Home) { - userInfo.Home = filepath.Join(b.homeDirPath, userInfo.Home) + userInfo.Home = filepath.Join(b.cfg.HomeBaseDir, userInfo.Home) } return userInfo, err diff --git a/internal/broker/export_test.go b/internal/broker/export_test.go index b69d20c50a..e81adafcb8 100644 --- a/internal/broker/export_test.go +++ b/internal/broker/export_test.go @@ -24,7 +24,7 @@ func (b *Broker) TokenPathForSession(sessionID string) string { // CachePath returns the path to the cache directory for tests. func (b *Broker) CachePath() string { - return b.cachePath + return b.cfg.CachePath } // UpdateSessionAuthStep updates the current auth step for the given session. From 584e38c605c15d86280b592ca1acebdd07efb751 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 4 Sep 2024 22:36:35 +0200 Subject: [PATCH 0187/1670] Also check owner of the data directory --- cmd/authd-oidc/daemon/daemon.go | 2 +- cmd/authd-oidc/daemon/fs.go | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/cmd/authd-oidc/daemon/daemon.go b/cmd/authd-oidc/daemon/daemon.go index c883b85c42..efe505b342 100644 --- a/cmd/authd-oidc/daemon/daemon.go +++ b/cmd/authd-oidc/daemon/daemon.go @@ -108,7 +108,7 @@ func New(name string) *App { func (a *App) serve(config daemonConfig) error { ctx := context.Background() - if err := ensureDirWithPerms(config.Paths.Cache, 0700); err != nil { + if err := ensureDirWithPerms(config.Paths.Cache, 0700, os.Geteuid()); err != nil { close(a.ready) return fmt.Errorf("error initializing users cache directory at %q: %v", config.Paths.Cache, err) } diff --git a/cmd/authd-oidc/daemon/fs.go b/cmd/authd-oidc/daemon/fs.go index bd6bafd0be..dc5b9f4a27 100644 --- a/cmd/authd-oidc/daemon/fs.go +++ b/cmd/authd-oidc/daemon/fs.go @@ -9,7 +9,7 @@ import ( // ensureDirWithPerms creates a directory at path if it doesn't exist yet with perm as permissions. // If the path exists, it will check if it’s a directory with those perms. -func ensureDirWithPerms(path string, perm os.FileMode) error { +func ensureDirWithPerms(path string, perm os.FileMode, owner int) error { dir, err := os.Stat(path) if err == nil { if !dir.IsDir() { @@ -18,6 +18,13 @@ func ensureDirWithPerms(path string, perm os.FileMode) error { if dir.Mode() != (perm | fs.ModeDir) { return fmt.Errorf("permissions %v don't match what we desired: %v", dir.Mode(), perm|fs.ModeDir) } + stat, ok := dir.Sys().(*syscall.Stat_t) + if !ok { + return fmt.Errorf("failed to get syscall.Stat_t for %s", path) + } + if int(stat.Uid) != owner { + return fmt.Errorf("owner should be %d but is %d", owner, stat.Uid) + } return nil } From f1842c93ccce7c0e6018dbd7e6c21c03f668f299 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 4 Sep 2024 19:16:21 +0200 Subject: [PATCH 0188/1670] Improve wording in error message --- cmd/authd-oidc/daemon/fs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/authd-oidc/daemon/fs.go b/cmd/authd-oidc/daemon/fs.go index dc5b9f4a27..a5ce4e173f 100644 --- a/cmd/authd-oidc/daemon/fs.go +++ b/cmd/authd-oidc/daemon/fs.go @@ -16,7 +16,7 @@ func ensureDirWithPerms(path string, perm os.FileMode, owner int) error { return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR} } if dir.Mode() != (perm | fs.ModeDir) { - return fmt.Errorf("permissions %v don't match what we desired: %v", dir.Mode(), perm|fs.ModeDir) + return fmt.Errorf("permissions should be %v but are %v", perm|fs.ModeDir, dir.Mode()) } stat, ok := dir.Sys().(*syscall.Stat_t) if !ok { From dbcec3a56775f513911ef859601923894260763e Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 4 Sep 2024 22:04:12 +0200 Subject: [PATCH 0189/1670] refactor: Initialize broker in daemon The chain daemon -> dbusservice -> broker needed to pass configs through multiple layers, which made the code hard to navigate and maintain. --- cmd/authd-oidc/daemon/config.go | 27 +++++++++ .../authd-oidc/daemon}/consts.go | 2 +- cmd/authd-oidc/daemon/daemon.go | 25 ++++++++- cmd/authd-oidc/daemon/daemon_test.go | 9 +++ .../authd-oidc/daemon}/internal_test.go | 10 +--- ...values_contain_a_single_template_delimiter | 4 ++ .../golden/successfully_parse_config_file | 4 ++ ...lly_parse_config_file_with_optional_values | 7 +++ internal/dbusservice/dbusservice.go | 55 +------------------ 9 files changed, 79 insertions(+), 64 deletions(-) rename {internal/dbusservice => cmd/authd-oidc/daemon}/consts.go (97%) rename {internal/dbusservice => cmd/authd-oidc/daemon}/internal_test.go (94%) create mode 100644 cmd/authd-oidc/daemon/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter create mode 100644 cmd/authd-oidc/daemon/testdata/TestParseConfig/golden/successfully_parse_config_file create mode 100644 cmd/authd-oidc/daemon/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values diff --git a/cmd/authd-oidc/daemon/config.go b/cmd/authd-oidc/daemon/config.go index 39f4d951bf..9d760ea57a 100644 --- a/cmd/authd-oidc/daemon/config.go +++ b/cmd/authd-oidc/daemon/config.go @@ -13,6 +13,7 @@ import ( "github.com/ubuntu/authd-oidc-brokers/internal/consts" "github.com/ubuntu/authd-oidc-brokers/internal/log" "github.com/ubuntu/decorate" + "gopkg.in/ini.v1" ) // initViperConfig sets verbosity level and add config env variables and file support based on name prefix. @@ -102,3 +103,29 @@ func setVerboseMode(level int) { //slog.SetReportCaller(reportCaller) } + +// parseConfig parses the config file and returns a map with the configuration keys and values. +func parseConfig(cfgPath string) (map[string]map[string]string, error) { + iniCfg, err := ini.Load(cfgPath) + if err != nil { + return nil, err + } + + cfg := make(map[string]map[string]string) + for _, section := range iniCfg.Sections() { + cfg[section.Name()] = make(map[string]string) + for _, key := range section.Keys() { + if strings.Contains(key.String(), "<") && strings.Contains(key.String(), ">") { + err = errors.Join(err, fmt.Errorf("found invalid character in section %q, key %q", section.Name(), key.Name())) + continue + } + cfg[section.Name()][key.Name()] = key.String() + } + } + + // This means we found at least one section that was potentially not edited. + if err != nil { + return nil, fmt.Errorf("config file has invalid values, did you edit the file %q?\n%w", cfgPath, err) + } + return cfg, nil +} diff --git a/internal/dbusservice/consts.go b/cmd/authd-oidc/daemon/consts.go similarity index 97% rename from internal/dbusservice/consts.go rename to cmd/authd-oidc/daemon/consts.go index fe4bea98f4..d481643c9d 100644 --- a/internal/dbusservice/consts.go +++ b/cmd/authd-oidc/daemon/consts.go @@ -1,4 +1,4 @@ -package dbusservice +package daemon // Configuration sections and keys. const ( diff --git a/cmd/authd-oidc/daemon/daemon.go b/cmd/authd-oidc/daemon/daemon.go index efe505b342..a2714aef64 100644 --- a/cmd/authd-oidc/daemon/daemon.go +++ b/cmd/authd-oidc/daemon/daemon.go @@ -8,9 +8,11 @@ import ( "os" "path/filepath" "runtime" + "strings" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/ubuntu/authd-oidc-brokers/internal/broker" "github.com/ubuntu/authd-oidc-brokers/internal/daemon" "github.com/ubuntu/authd-oidc-brokers/internal/dbusservice" ) @@ -113,7 +115,28 @@ func (a *App) serve(config daemonConfig) error { return fmt.Errorf("error initializing users cache directory at %q: %v", config.Paths.Cache, err) } - s, err := dbusservice.New(ctx, config.Paths.BrokerConf, config.Paths.Cache) + cfg, err := parseConfig(config.Paths.BrokerConf) + if err != nil { + return err + } + + var allowedSSHSuffixes []string + if cfg[usersSection][sshSuffixesKey] != "" { + allowedSSHSuffixes = strings.Split(cfg[usersSection][sshSuffixesKey], ",") + } + + b, err := broker.New(broker.Config{ + IssuerURL: cfg[oidcSection][issuerKey], + ClientID: cfg[oidcSection][clientIDKey], + HomeBaseDir: cfg[usersSection][homeDirKey], + AllowedSSHSuffixes: allowedSSHSuffixes, + CachePath: config.Paths.Cache, + }) + if err != nil { + return err + } + + s, err := dbusservice.New(ctx, b) if err != nil { close(a.ready) return err diff --git a/cmd/authd-oidc/daemon/daemon_test.go b/cmd/authd-oidc/daemon/daemon_test.go index 8442208408..5b733263f4 100644 --- a/cmd/authd-oidc/daemon/daemon_test.go +++ b/cmd/authd-oidc/daemon/daemon_test.go @@ -2,6 +2,7 @@ package daemon_test import ( "bytes" + "flag" "fmt" "io" "net/http/httptest" @@ -407,5 +408,13 @@ func TestMain(m *testing.M) { defer cleanup() mockProvider = providerServer + testutils.InstallUpdateFlag() + flag.Parse() + // Remove the flag from the command line arguments if it is present, to avoid that it's being parsed by the cobra + // command in the tests. + if testutils.Update() { + os.Args = os.Args[:len(os.Args)-1] + } + m.Run() } diff --git a/internal/dbusservice/internal_test.go b/cmd/authd-oidc/daemon/internal_test.go similarity index 94% rename from internal/dbusservice/internal_test.go rename to cmd/authd-oidc/daemon/internal_test.go index e57b36e083..a5ae962358 100644 --- a/internal/dbusservice/internal_test.go +++ b/cmd/authd-oidc/daemon/internal_test.go @@ -1,7 +1,6 @@ -package dbusservice +package daemon import ( - "flag" "os" "path/filepath" "testing" @@ -90,10 +89,3 @@ func TestParseConfig(t *testing.T) { }) } } - -func TestMain(m *testing.M) { - testutils.InstallUpdateFlag() - flag.Parse() - - m.Run() -} diff --git a/cmd/authd-oidc/daemon/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter b/cmd/authd-oidc/daemon/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter new file mode 100644 index 0000000000..17a42fa4e9 --- /dev/null +++ b/cmd/authd-oidc/daemon/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter @@ -0,0 +1,4 @@ +DEFAULT: {} +oidc: + client_id: diff --git a/cmd/authd-oidc/daemon/testdata/TestParseConfig/golden/successfully_parse_config_file b/cmd/authd-oidc/daemon/testdata/TestParseConfig/golden/successfully_parse_config_file new file mode 100644 index 0000000000..622dbad7f1 --- /dev/null +++ b/cmd/authd-oidc/daemon/testdata/TestParseConfig/golden/successfully_parse_config_file @@ -0,0 +1,4 @@ +DEFAULT: {} +oidc: + client_id: client_id + issuer: https://issuer.url.com diff --git a/cmd/authd-oidc/daemon/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values b/cmd/authd-oidc/daemon/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values new file mode 100644 index 0000000000..53fe96579b --- /dev/null +++ b/cmd/authd-oidc/daemon/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values @@ -0,0 +1,7 @@ +DEFAULT: {} +oidc: + client_id: client_id + issuer: https://issuer.url.com +users: + allowed_ssh_suffixes: '@issuer.url.com' + home_base_dir: /home diff --git a/internal/dbusservice/dbusservice.go b/internal/dbusservice/dbusservice.go index 258410c47b..b0131fe575 100644 --- a/internal/dbusservice/dbusservice.go +++ b/internal/dbusservice/dbusservice.go @@ -3,15 +3,12 @@ package dbusservice import ( "context" - "errors" "fmt" - "strings" "github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5/introspect" "github.com/ubuntu/authd-oidc-brokers/internal/broker" "github.com/ubuntu/authd-oidc-brokers/internal/consts" - "gopkg.in/ini.v1" ) const intro = ` @@ -61,35 +58,13 @@ type Service struct { } // New returns a new dbus service after exporting to the system bus our name. -func New(_ context.Context, cfgPath, cachePath string) (s *Service, err error) { - cfg, err := parseConfig(cfgPath) - if err != nil { - return nil, err - } - - var allowedSSHSuffixes []string - if cfg[usersSection][sshSuffixesKey] != "" { - allowedSSHSuffixes = strings.Split(cfg[usersSection][sshSuffixesKey], ",") - } - - bCfg := broker.Config{ - IssuerURL: cfg[oidcSection][issuerKey], - ClientID: cfg[oidcSection][clientIDKey], - HomeBaseDir: cfg[usersSection][homeDirKey], - AllowedSSHSuffixes: allowedSSHSuffixes, - CachePath: cachePath, - } - b, err := broker.New(bCfg) - if err != nil { - return nil, err - } - +func New(_ context.Context, broker *broker.Broker) (s *Service, err error) { name := consts.DbusName object := dbus.ObjectPath(consts.DbusObject) iface := "com.ubuntu.authd.Broker" s = &Service{ name: name, - broker: b, + broker: broker, serve: make(chan struct{}), } @@ -118,32 +93,6 @@ func New(_ context.Context, cfgPath, cachePath string) (s *Service, err error) { return s, nil } -// parseConfig parses the config file and returns a map with the configuration keys and values. -func parseConfig(cfgPath string) (map[string]map[string]string, error) { - iniCfg, err := ini.Load(cfgPath) - if err != nil { - return nil, err - } - - cfg := make(map[string]map[string]string) - for _, section := range iniCfg.Sections() { - cfg[section.Name()] = make(map[string]string) - for _, key := range section.Keys() { - if strings.Contains(key.String(), "<") && strings.Contains(key.String(), ">") { - err = errors.Join(err, fmt.Errorf("found invalid character in section %q, key %q", section.Name(), key.Name())) - continue - } - cfg[section.Name()][key.Name()] = key.String() - } - } - - // This means we found at least one section that was potentially not edited. - if err != nil { - return nil, fmt.Errorf("config file has invalid values, did you edit the file %q?\n%w", cfgPath, err) - } - return cfg, nil -} - // Addr returns the address of the service. func (s *Service) Addr() string { return s.name From 35bbdec847590ce07bdbe122e479ca3ec4f448d9 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 5 Sep 2024 09:36:40 +0200 Subject: [PATCH 0190/1670] Improve error message --- internal/testutils/golden.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/testutils/golden.go b/internal/testutils/golden.go index 78ea94dcb2..adb7e3b134 100644 --- a/internal/testutils/golden.go +++ b/internal/testutils/golden.go @@ -158,9 +158,9 @@ func CompareTreesWithFiltering(t *testing.T, p, goldPath string, update bool) { } } - // Maps are not ordered, so we need to compare the contents through keys + // Maps are not ordered, so we need to compare the content and attributes of each file for key, value := range goldContent { - require.Equal(t, value, gotContent[key], "got and expected content differs") + require.Equal(t, value, gotContent[key], "Content or attributes are different for %s", key) delete(gotContent, key) } require.Empty(t, gotContent, "Some files are missing in the golden directory") From 524fb8216f6aef0781d3b0efea60f70cafa98d7c Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 3 Sep 2024 14:45:34 +0200 Subject: [PATCH 0191/1670] Store token unencrypted Before this commit, we encrypted the token with the local password before writing it to the filesystem. We decrypted the token whenever the user logs in and try to refresh it. That required us to always ask for the local password on login, even if the user authenticated via OIDC. We discussed various approaches to solve this issue. In the end we decided to go with the simplest approach, i.e. to store the token unencrypted. It is still protected from unprivileged users via filesystem permissions. To still be able to verify the local password, we now store a hash of the local password in a file. For a seamless transition, we still support logging in via a local password when there is no password file but an old encrypted token file. This should make the change completely transparent to users. This commit also uses the term "data directory" for what was previously known as the "cache directory", because we're storing the tokens and password file in there, which should not be considered "cache" (because if the user deletes those, they won't be able to log in with the local password anymore). --- cmd/authd-oidc/daemon/config.go | 27 --- cmd/authd-oidc/daemon/consts.go | 18 -- cmd/authd-oidc/daemon/daemon.go | 48 ++-- cmd/authd-oidc/daemon/daemon_test.go | 32 +-- cmd/authd-oidc/daemon/export_test.go | 6 +- internal/broker/broker.go | 224 ++++++++++++++---- internal/broker/broker_test.go | 139 +++++++---- internal/broker/config.go | 62 +++++ .../broker/config_test.go | 29 ++- internal/broker/encrypt.go | 31 --- internal/broker/export_test.go | 73 +++++- internal/broker/helper_test.go | 12 +- .../data/provider_url/user-timeout-0/password | 1 + .../provider_url/user-timeout-0/token.json} | 0 .../data/provider_url/user-timeout-1/password | 1 + .../provider_url/user-timeout-1/token.json} | 0 .../data/provider_url/user-timeout-1/password | 1 + .../provider_url/user-timeout-1/token.json} | 0 .../data/provider_url/user-timeout-3/password | 1 + .../provider_url/user-timeout-3/token.json} | 0 .../data/provider_url/user-timeout-2/password | 1 + .../provider_url/user-timeout-2/token.json} | 0 .../data/provider_url/user-timeout-3/password | 1 + .../provider_url/user-timeout-3/token.json} | 0 .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json} | 0 .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json} | 0 .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json} | 0 .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json} | 0 .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json} | 0 .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json} | 0 .../{cache => data}/.empty | 0 .../{cache => data}/.empty | 0 .../second_call | 2 +- .../{cache => data}/.empty | 0 .../{cache => data}/.empty | 0 .../{cache => data}/.empty | 0 .../{cache => data}/.empty | 0 .../{cache => data}/.empty | 0 .../{cache => data}/.empty | 0 .../{cache => data}/.empty | 0 .../{cache => data}/.empty | 0 .../{cache => data}/.empty | 0 .../{cache => data}/.empty | 0 .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json} | 0 .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json} | 0 .../{cache => data}/.empty | 0 .../first_call | 4 +- .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json} | 0 .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json} | 0 .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json} | 0 .../{cache => data}/.empty | 0 .../{cache => data}/.empty | 0 .../{cache => data}/.empty | 0 .../{cache => data}/.empty | 0 .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json} | 0 .../first_call | 2 +- .../{cache => data}/.empty | 0 .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json} | 0 .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json} | 0 .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json} | 0 .../config.txt | 4 + .../successfully_parse_config_file/config.txt | 4 + .../config.txt | 4 + ...values_contain_a_single_template_delimiter | 4 - .../golden/successfully_parse_config_file | 4 - ...lly_parse_config_file_with_optional_values | 7 - internal/fileutils/fileutils.go | 45 ++++ internal/fileutils/fileutils_test.go | 156 ++++++++++++ internal/password/password.go | 58 +++++ internal/password/password_test.go | 106 +++++++++ 85 files changed, 861 insertions(+), 261 deletions(-) delete mode 100644 cmd/authd-oidc/daemon/consts.go create mode 100644 internal/broker/config.go rename cmd/authd-oidc/daemon/internal_test.go => internal/broker/config_test.go (65%) create mode 100644 internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user-timeout-0/password rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/{cache/provider_url/user-timeout-0.cache => data/provider_url/user-timeout-0/token.json} (100%) create mode 100644 internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user-timeout-1/password rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/{cache/provider_url/user-timeout-1.cache => data/provider_url/user-timeout-1/token.json} (100%) create mode 100644 internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user-timeout-1/password rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/{cache/provider_url/user-timeout-1.cache => data/provider_url/user-timeout-1/token.json} (100%) create mode 100644 internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user-timeout-3/password rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/{cache/provider_url/user-timeout-3.cache => data/provider_url/user-timeout-3/token.json} (100%) create mode 100644 internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user-timeout-2/password rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/{cache/provider_url/user-timeout-2.cache => data/provider_url/user-timeout-2/token.json} (100%) create mode 100644 internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user-timeout-3/password rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/{cache/provider_url/user-timeout-3.cache => data/provider_url/user-timeout-3/token.json} (100%) create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/password rename internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/{cache/provider_url/test-user@email.com.cache => data/provider_url/test-user@email.com/token.json} (100%) create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/data/provider_url/test-user@email.com/password rename internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/{cache/provider_url/test-user@email.com.cache => data/provider_url/test-user@email.com/token.json} (100%) create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/password rename internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/{cache/provider_url/test-user@email.com.cache => data/provider_url/test-user@email.com/token.json} (100%) create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/password rename internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/{cache/provider_url/test-user@email.com.cache => data/provider_url/test-user@email.com/token.json} (100%) create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/password rename internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/{cache/provider_url/test-user@email.com.cache => data/provider_url/test-user@email.com/token.json} (100%) create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/password rename internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/{cache/provider_url/test-user@email.com.cache => data/provider_url/test-user@email.com/token.json} (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/error_when_authentication_data_is_invalid/{cache => data}/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/{cache => data}/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/error_when_challenge_can_not_be_decrypted/{cache => data}/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/{cache => data}/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/{cache => data}/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token/{cache => data}/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/{cache => data}/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_link_expires/{cache => data}/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_response_is_invalid/{cache => data}/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/{cache => data}/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/{cache => data}/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_token_is_not_set/{cache => data}/.empty (100%) create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/data/provider_url/test-user@email.com/password rename internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/{cache/provider_url/test-user@email.com.cache => data/provider_url/test-user@email.com/token.json} (100%) create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/data/provider_url/test-user@email.com/password rename internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/{cache/provider_url/test-user@email.com.cache => data/provider_url/test-user@email.com/token.json} (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/{cache => data}/.empty (100%) create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/password rename internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/{cache/provider_url/test-user@email.com.cache => data/provider_url/test-user@email.com/token.json} (100%) create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/password rename internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/{cache/provider_url/test-user@email.com.cache => data/provider_url/test-user@email.com/token.json} (100%) create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/password rename internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/{cache/provider_url/test-user@email.com.cache => data/provider_url/test-user@email.com/token.json} (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token/{cache => data}/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/{cache => data}/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_link_expires/{cache => data}/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_response_is_invalid/{cache => data}/.empty (100%) create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/password rename internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/{cache/provider_url/test-user@email.com.cache => data/provider_url/test-user@email.com/token.json} (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/{cache => data}/.empty (100%) create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/data/provider_url/test-user@email.com/password rename internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/{cache/provider_url/test-user@email.com.cache => data/provider_url/test-user@email.com/token.json} (100%) create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/password rename internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/{cache/provider_url/test-user@email.com.cache => data/provider_url/test-user@email.com/token.json} (100%) create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/data/provider_url/test-user@email.com/password rename internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/{cache/provider_url/test-user@email.com.cache => data/provider_url/test-user@email.com/token.json} (100%) create mode 100644 internal/broker/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter/config.txt create mode 100644 internal/broker/testdata/TestParseConfig/golden/successfully_parse_config_file/config.txt create mode 100644 internal/broker/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values/config.txt delete mode 100644 internal/dbusservice/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter delete mode 100644 internal/dbusservice/testdata/TestParseConfig/golden/successfully_parse_config_file delete mode 100644 internal/dbusservice/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values create mode 100644 internal/fileutils/fileutils.go create mode 100644 internal/fileutils/fileutils_test.go create mode 100644 internal/password/password.go create mode 100644 internal/password/password_test.go diff --git a/cmd/authd-oidc/daemon/config.go b/cmd/authd-oidc/daemon/config.go index 9d760ea57a..39f4d951bf 100644 --- a/cmd/authd-oidc/daemon/config.go +++ b/cmd/authd-oidc/daemon/config.go @@ -13,7 +13,6 @@ import ( "github.com/ubuntu/authd-oidc-brokers/internal/consts" "github.com/ubuntu/authd-oidc-brokers/internal/log" "github.com/ubuntu/decorate" - "gopkg.in/ini.v1" ) // initViperConfig sets verbosity level and add config env variables and file support based on name prefix. @@ -103,29 +102,3 @@ func setVerboseMode(level int) { //slog.SetReportCaller(reportCaller) } - -// parseConfig parses the config file and returns a map with the configuration keys and values. -func parseConfig(cfgPath string) (map[string]map[string]string, error) { - iniCfg, err := ini.Load(cfgPath) - if err != nil { - return nil, err - } - - cfg := make(map[string]map[string]string) - for _, section := range iniCfg.Sections() { - cfg[section.Name()] = make(map[string]string) - for _, key := range section.Keys() { - if strings.Contains(key.String(), "<") && strings.Contains(key.String(), ">") { - err = errors.Join(err, fmt.Errorf("found invalid character in section %q, key %q", section.Name(), key.Name())) - continue - } - cfg[section.Name()][key.Name()] = key.String() - } - } - - // This means we found at least one section that was potentially not edited. - if err != nil { - return nil, fmt.Errorf("config file has invalid values, did you edit the file %q?\n%w", cfgPath, err) - } - return cfg, nil -} diff --git a/cmd/authd-oidc/daemon/consts.go b/cmd/authd-oidc/daemon/consts.go deleted file mode 100644 index d481643c9d..0000000000 --- a/cmd/authd-oidc/daemon/consts.go +++ /dev/null @@ -1,18 +0,0 @@ -package daemon - -// Configuration sections and keys. -const ( - // oidcSection is the section name in the config file for the OIDC specific configuration. - oidcSection = "oidc" - // issuerKey is the key in the config file for the issuer. - issuerKey = "issuer" - // clientIDKey is the key in the config file for the client ID. - clientIDKey = "client_id" - - // usersSection is the section name in the config file for the users and broker specific configuration. - usersSection = "users" - // homeDirKey is the key in the config file for the home directory prefix. - homeDirKey = "home_base_dir" - // SSHSuffixKey is the key in the config file for the SSH allowed suffixes. - sshSuffixesKey = "ssh_allowed_suffixes" -) diff --git a/cmd/authd-oidc/daemon/daemon.go b/cmd/authd-oidc/daemon/daemon.go index a2714aef64..966a0a9526 100644 --- a/cmd/authd-oidc/daemon/daemon.go +++ b/cmd/authd-oidc/daemon/daemon.go @@ -8,7 +8,6 @@ import ( "os" "path/filepath" "runtime" - "strings" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -31,8 +30,9 @@ type App struct { // only overriable for tests. type systemPaths struct { - BrokerConf string - Cache string + BrokerConf string + DataDir string + OldEncryptedTokensDir string } // daemonConfig defines configuration parameters of the daemon. @@ -54,16 +54,23 @@ func New(name string) *App { a.rootCmd.SilenceUsage = true // Set config defaults - systemCache := filepath.Join("/var", "lib", name) + // Before version 0.2, we used to store the tokens encrypted + // in a different directory. For backward compatibility, we + // try to use the encrypted tokens from the old directory + // if they are not found in the new one. + oldEncryptedTokensDir := filepath.Join("/var", "lib", name) + dataDir := filepath.Join("/var", "lib", name) configDir := "." if snapData := os.Getenv("SNAP_DATA"); snapData != "" { - systemCache = filepath.Join(snapData, "cache") + oldEncryptedTokensDir = filepath.Join(snapData, "cache") + dataDir = snapData configDir = snapData } a.config = daemonConfig{ Paths: systemPaths{ - BrokerConf: filepath.Join(configDir, "broker.conf"), - Cache: systemCache, + BrokerConf: filepath.Join(configDir, "broker.conf"), + DataDir: dataDir, + OldEncryptedTokensDir: oldEncryptedTokensDir, }, } @@ -110,27 +117,18 @@ func New(name string) *App { func (a *App) serve(config daemonConfig) error { ctx := context.Background() - if err := ensureDirWithPerms(config.Paths.Cache, 0700, os.Geteuid()); err != nil { - close(a.ready) - return fmt.Errorf("error initializing users cache directory at %q: %v", config.Paths.Cache, err) - } - - cfg, err := parseConfig(config.Paths.BrokerConf) - if err != nil { - return err - } - - var allowedSSHSuffixes []string - if cfg[usersSection][sshSuffixesKey] != "" { - allowedSSHSuffixes = strings.Split(cfg[usersSection][sshSuffixesKey], ",") + // When the data directory is SNAP_DATA, it has permission 0755, else we want to create it with 0700. + if err := ensureDirWithPerms(config.Paths.DataDir, 0700, os.Geteuid()); err != nil { + if err := ensureDirWithPerms(config.Paths.DataDir, 0755, os.Geteuid()); err != nil { + close(a.ready) + return fmt.Errorf("error initializing data directory %q: %v", config.Paths.DataDir, err) + } } b, err := broker.New(broker.Config{ - IssuerURL: cfg[oidcSection][issuerKey], - ClientID: cfg[oidcSection][clientIDKey], - HomeBaseDir: cfg[usersSection][homeDirKey], - AllowedSSHSuffixes: allowedSSHSuffixes, - CachePath: config.Paths.Cache, + ConfigFile: config.Paths.BrokerConf, + DataDir: config.Paths.DataDir, + OldEncryptedTokensDir: config.Paths.OldEncryptedTokensDir, }) if err != nil { return err diff --git a/cmd/authd-oidc/daemon/daemon_test.go b/cmd/authd-oidc/daemon/daemon_test.go index 5b733263f4..4698ed2282 100644 --- a/cmd/authd-oidc/daemon/daemon_test.go +++ b/cmd/authd-oidc/daemon/daemon_test.go @@ -106,40 +106,40 @@ func TestAppCanQuitWithoutExecute(t *testing.T) { func TestAppRunFailsOnComponentsCreationAndQuit(t *testing.T) { const ( - // Cache errors + // DataDir errors dirIsFile = iota wrongPermission noParentDir ) tests := map[string]struct { - cachePathBehavior int - configBehavior int + dataDirBehavior int + configBehavior int }{ - "Error on existing cache path being a file": {cachePathBehavior: dirIsFile}, - "Error on cache path missing parent directory": {cachePathBehavior: noParentDir}, - "Error on wrong permission on cache path": {cachePathBehavior: wrongPermission}, + "Error on existing data dir being a file": {dataDirBehavior: dirIsFile}, + "Error on data dir missing parent directory": {dataDirBehavior: noParentDir}, + "Error on wrong permission on data dir": {dataDirBehavior: wrongPermission}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { tmpDir := t.TempDir() - cachePath := filepath.Join(tmpDir, "cache") + dataDir := filepath.Join(tmpDir, "data") - switch tc.cachePathBehavior { + switch tc.dataDirBehavior { case dirIsFile: - err := os.WriteFile(cachePath, []byte("file"), 0600) + err := os.WriteFile(dataDir, []byte("file"), 0600) require.NoError(t, err, "Setup: could not create cache file for tests") case wrongPermission: - err := os.Mkdir(cachePath, 0600) + err := os.Mkdir(dataDir, 0600) require.NoError(t, err, "Setup: could not create cache directory for tests") case noParentDir: - cachePath = filepath.Join(tmpDir, "doesnotexist", "cache") + dataDir = filepath.Join(dataDir, "doesnotexist", "data") } config := daemon.DaemonConfig{ Verbosity: 0, Paths: daemon.SystemPaths{ - Cache: cachePath, + DataDir: dataDir, }, } @@ -226,7 +226,7 @@ func TestConfigLoad(t *testing.T) { Verbosity: 1, Paths: daemon.SystemPaths{ BrokerConf: filepath.Join(tmpDir, "broker.conf"), - Cache: filepath.Join(tmpDir, "cache"), + DataDir: filepath.Join(tmpDir, "data"), }, } @@ -243,7 +243,7 @@ func TestConfigHasPrecedenceOverPathsConfig(t *testing.T) { Verbosity: 1, Paths: daemon.SystemPaths{ BrokerConf: filepath.Join(tmpDir, "broker.conf"), - Cache: filepath.Join(tmpDir, "cache"), + DataDir: filepath.Join(tmpDir, "data"), }, } @@ -275,7 +275,7 @@ func TestAutoDetectConfig(t *testing.T) { Verbosity: 1, Paths: daemon.SystemPaths{ BrokerConf: filepath.Join(tmpDir, "broker.conf"), - Cache: filepath.Join(tmpDir, "cache"), + DataDir: filepath.Join(tmpDir, "data"), }, } @@ -315,7 +315,7 @@ func TestNoConfigSetDefaults(t *testing.T) { require.Equal(t, 0, a.Config().Verbosity, "Default Verbosity") require.Equal(t, filepath.Join(tmpDir, "broker.conf"), a.Config().Paths.BrokerConf, "Default broker configuration path") - require.Equal(t, filepath.Join(tmpDir, "cache"), a.Config().Paths.Cache, "Default cache directory") + require.Equal(t, tmpDir, a.Config().Paths.DataDir, "Default data directory") } func TestBadConfigReturnsError(t *testing.T) { diff --git a/cmd/authd-oidc/daemon/export_test.go b/cmd/authd-oidc/daemon/export_test.go index f898c45f66..7406168952 100644 --- a/cmd/authd-oidc/daemon/export_test.go +++ b/cmd/authd-oidc/daemon/export_test.go @@ -40,10 +40,10 @@ func GenerateTestConfig(t *testing.T, origConf *daemonConfig, providerURL string if conf.Verbosity == 0 { conf.Verbosity = 2 } - if conf.Paths.Cache == "" { - conf.Paths.Cache = t.TempDir() + if conf.Paths.DataDir == "" { + conf.Paths.DataDir = t.TempDir() //nolint: gosec // This is a directory owned only by the current user for tests. - err := os.Chmod(conf.Paths.Cache, 0700) + err := os.Chmod(conf.Paths.DataDir, 0700) require.NoError(t, err, "Setup: could not change permission on cache directory for tests") } if conf.Paths.BrokerConf == "" { diff --git a/internal/broker/broker.go b/internal/broker/broker.go index c10fa4c9bd..e8eaf94cce 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -22,6 +22,8 @@ import ( "github.com/google/uuid" "github.com/ubuntu/authd-oidc-brokers/internal/broker/authmodes" "github.com/ubuntu/authd-oidc-brokers/internal/consts" + "github.com/ubuntu/authd-oidc-brokers/internal/fileutils" + "github.com/ubuntu/authd-oidc-brokers/internal/password" "github.com/ubuntu/authd-oidc-brokers/internal/providers" providerErrors "github.com/ubuntu/authd-oidc-brokers/internal/providers/errors" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" @@ -36,11 +38,18 @@ const ( // Config is the configuration for the broker. type Config struct { - IssuerURL string - ClientID string - CachePath string - HomeBaseDir string - AllowedSSHSuffixes []string + ConfigFile string + DataDir string + OldEncryptedTokensDir string + + userConfig +} + +type userConfig struct { + clientID string + issuerURL string + homeBaseDir string + allowedSSHSuffixes []string } // Broker is the real implementation of the broker to track sessions and process oidc calls. @@ -66,10 +75,13 @@ type sessionInfo struct { authModes []string attemptsPerMode map[string]int - authCfg authConfig - authInfo map[string]any - isOffline bool - cachePath string + authCfg authConfig + authInfo map[string]any + isOffline bool + userDataDir string + passwordPath string + tokenPath string + oldEncryptedTokenPath string currentAuthStep int @@ -98,6 +110,13 @@ type Option func(*option) func New(cfg Config, args ...Option) (b *Broker, err error) { defer decorate.OnError(&err, "could not create broker") + if cfg.ConfigFile != "" { + cfg.userConfig, err = parseConfigFile(cfg.ConfigFile) + if err != nil { + return nil, fmt.Errorf("could not parse config: %v", err) + } + } + opts := option{ providerInfo: providers.CurrentProviderInfo(), } @@ -105,21 +124,21 @@ func New(cfg Config, args ...Option) (b *Broker, err error) { arg(&opts) } - if cfg.CachePath == "" { + if cfg.DataDir == "" { err = errors.Join(err, errors.New("cache path is required and was not provided")) } - if cfg.IssuerURL == "" { + if cfg.issuerURL == "" { err = errors.Join(err, errors.New("issuer URL is required and was not provided")) } - if cfg.ClientID == "" { + if cfg.clientID == "" { err = errors.Join(err, errors.New("client ID is required and was not provided")) } if err != nil { return nil, err } - if cfg.HomeBaseDir == "" { - cfg.HomeBaseDir = "/home" + if cfg.homeBaseDir == "" { + cfg.homeBaseDir = "/home" } // Generate a new private key for the broker. @@ -132,7 +151,7 @@ func New(cfg Config, args ...Option) (b *Broker, err error) { b = &Broker{ cfg: cfg, providerInfo: opts.providerInfo, - oidcCfg: oidc.Config{ClientID: cfg.ClientID}, + oidcCfg: oidc.Config{ClientID: cfg.clientID}, privateKey: privateKey, currentSessions: make(map[string]sessionInfo), @@ -160,10 +179,15 @@ func (b *Broker) NewSession(username, lang, mode string) (sessionID, encryptionK return "", "", err } - _, url, _ := strings.Cut(b.cfg.IssuerURL, "://") - url = strings.ReplaceAll(url, "/", "_") - url = strings.ReplaceAll(url, ":", "_") - session.cachePath = filepath.Join(b.cfg.CachePath, url, username+".cache") + _, issuer, _ := strings.Cut(b.cfg.issuerURL, "://") + issuer = strings.ReplaceAll(issuer, "/", "_") + issuer = strings.ReplaceAll(issuer, ":", "_") + session.userDataDir = filepath.Join(b.cfg.DataDir, issuer, username) + // The token is stored in $DATA_DIR/$ISSUER/$USERNAME/token.json. + session.tokenPath = filepath.Join(session.userDataDir, "token.json") + // The password is stored in $DATA_DIR/$ISSUER/$USERNAME/password. + session.passwordPath = filepath.Join(session.userDataDir, "password") + session.oldEncryptedTokenPath = filepath.Join(b.cfg.OldEncryptedTokensDir, issuer, username+".cache") // Check whether to start the session in offline mode. session.authCfg, err = b.connectToProvider(context.Background()) @@ -183,7 +207,7 @@ func (b *Broker) connectToProvider(ctx context.Context) (authCfg authConfig, err ctx, cancel := context.WithTimeout(ctx, maxRequestDuration) defer cancel() - provider, err := oidc.NewProvider(ctx, b.cfg.IssuerURL) + provider, err := oidc.NewProvider(ctx, b.cfg.issuerURL) if err != nil { return authConfig{}, err } @@ -210,8 +234,17 @@ func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []m slog.Debug(fmt.Sprintf("Supported Authentication modes for session %s: %#v", sessionID, supportedAuthModes)) // Checks if the token exists in the cache. - _, err = os.Stat(session.cachePath) - tokenExists := err == nil + tokenExists, err := fileutils.FileExists(session.tokenPath) + if err != nil { + slog.Warn(fmt.Sprintf("Could not check if token exists: %v", err)) + } + if !tokenExists { + // Check the old encrypted token path. + tokenExists, err = fileutils.FileExists(session.oldEncryptedTokenPath) + if err != nil { + slog.Warn(fmt.Sprintf("Could not check if old encrypted token exists: %v", err)) + } + } endpoints := make(map[string]struct{}) if session.authCfg.provider != nil && session.authCfg.provider.Endpoint().DeviceAuthURL != "" { @@ -488,12 +521,51 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo return AuthNext, nil case authmodes.Password: - authInfo, err = b.loadAuthInfo(ctx, session, challenge) + var useOldEncryptedToken bool + exists, err := fileutils.FileExists(session.passwordPath) + if err != nil { + slog.Error(err.Error()) + return AuthDenied, errorMessage{Message: "could not check password file"} + } + if !exists { + // For backwards compatibility, we also check the old encrypted token path. + exists, err = fileutils.FileExists(session.oldEncryptedTokenPath) + if err != nil { + slog.Error(err.Error()) + return AuthDenied, errorMessage{Message: "could not check old encrypted token path"} + } + if !exists { + return AuthDenied, errorMessage{Message: "password file does not exist"} + } + useOldEncryptedToken = true + } + + if !useOldEncryptedToken { + ok, err := password.CheckPassword(challenge, session.passwordPath) + if err != nil { + slog.Error(err.Error()) + return AuthRetry, errorMessage{Message: "could not check password"} + } + if !ok { + return AuthRetry, errorMessage{Message: "incorrect password"} + } + } + + authInfo, err = b.loadAuthInfo(ctx, session, challenge, useOldEncryptedToken) if err != nil { slog.Error(err.Error()) return AuthRetry, errorMessage{Message: "could not load cached info"} } + if useOldEncryptedToken { + // We were able to decrypt the old token with the password, so we can now hash and store the password in the + // new format. + if err = password.HashAndStorePassword(challenge, session.passwordPath); err != nil { + slog.Error(err.Error()) + return AuthDenied, errorMessage{Message: "could not store password"} + } + } + userInfo, err := b.fetchUserInfo(ctx, session, &authInfo) if err != nil && authInfo.UserInfo.Name == "" { // We don't have a valid user info, so we can't proceed. @@ -524,17 +596,26 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo slog.Error("could not get required information") return AuthDenied, errorMessage{Message: "could not get required information"} } + + if err = password.HashAndStorePassword(challenge, session.passwordPath); err != nil { + slog.Error(err.Error()) + return AuthDenied, errorMessage{Message: "could not store password"} + } } if session.isOffline { return AuthGranted, userInfoMessage{UserInfo: authInfo.UserInfo} } - if err := b.cacheAuthInfo(session, authInfo, challenge); err != nil { + if err := b.cacheAuthInfo(session, authInfo); err != nil { slog.Error(err.Error()) return AuthDenied, errorMessage{Message: "could not cache user info"} } + // At this point we successfully stored the hashed password and a new token, so we can now safely remove any old + // encrypted token. + cleanupOldEncryptedToken(session.oldEncryptedTokenPath) + return AuthGranted, userInfoMessage{UserInfo: authInfo.UserInfo} } @@ -600,7 +681,7 @@ func (b *Broker) CancelIsAuthenticated(sessionID string) { // UserPreCheck checks if the user is valid and can be allowed to authenticate. func (b *Broker) UserPreCheck(username string) (string, error) { found := false - for _, suffix := range b.cfg.AllowedSSHSuffixes { + for _, suffix := range b.cfg.allowedSSHSuffixes { if strings.HasSuffix(username, suffix) { found = true break @@ -611,7 +692,7 @@ func (b *Broker) UserPreCheck(username string) (string, error) { return "", errors.New("username does not match the allowed suffixes") } - u := info.NewUser(username, filepath.Join(b.cfg.HomeBaseDir, username), "", "", "", nil) + u := info.NewUser(username, filepath.Join(b.cfg.homeBaseDir, username), "", "", "", nil) encoded, err := json.Marshal(u) if err != nil { return "", fmt.Errorf("could not marshal user info: %v", err) @@ -658,44 +739,46 @@ func (b *Broker) newAuthCachedInfo(t *oauth2.Token, idToken string) authCachedIn } } -// cacheAuthInfo serializes the access token and cache it. -func (b *Broker) cacheAuthInfo(session *sessionInfo, authInfo authCachedInfo, password string) (err error) { - content, err := json.Marshal(authInfo) +// cacheAuthInfo saves the token to the file. +func (b *Broker) cacheAuthInfo(session *sessionInfo, authInfo authCachedInfo) (err error) { + jsonData, err := json.Marshal(authInfo) if err != nil { return fmt.Errorf("could not marshal token: %v", err) } - serialized, err := encrypt(content, []byte(password)) - if err != nil { - return fmt.Errorf("could not encrypt token: %v", err) - } - // Create issuer specific cache directory if it doesn't exist. - if err = os.MkdirAll(filepath.Dir(session.cachePath), 0700); err != nil { + if err = os.MkdirAll(filepath.Dir(session.tokenPath), 0700); err != nil { return fmt.Errorf("could not create token directory: %v", err) } - if err = os.WriteFile(session.cachePath, serialized, 0600); err != nil { + if err = os.WriteFile(session.tokenPath, jsonData, 0600); err != nil { return fmt.Errorf("could not save token: %v", err) } return nil } -// loadAuthInfo deserializes the token from the cache and refreshes it if needed. -func (b *Broker) loadAuthInfo(ctx context.Context, session *sessionInfo, password string) (loadedInfo authCachedInfo, err error) { - s, err := os.ReadFile(session.cachePath) - if err != nil { - return authCachedInfo{}, fmt.Errorf("could not read token: %v", err) - } - - deserialized, err := decrypt(s, []byte(password)) - if err != nil { - return authCachedInfo{}, fmt.Errorf("could not deserialize token: %v", err) +// loadAuthInfo reads the token from the file and tries to refresh it if it's expired. +func (b *Broker) loadAuthInfo(ctx context.Context, session *sessionInfo, password string, useOldEncryptedToken bool) (loadedInfo authCachedInfo, err error) { + var jsonData []byte + if useOldEncryptedToken { + encryptedData, err := os.ReadFile(session.oldEncryptedTokenPath) + if err != nil { + return authCachedInfo{}, fmt.Errorf("could not read old encrypted token: %v", err) + } + jsonData, err = decrypt(encryptedData, []byte(password)) + if err != nil { + return authCachedInfo{}, fmt.Errorf("could not decrypt token: %v", err) + } + } else { + jsonData, err = os.ReadFile(session.tokenPath) + if err != nil { + return authCachedInfo{}, fmt.Errorf("could not read token: %v", err) + } } var cachedInfo authCachedInfo - if err := json.Unmarshal(deserialized, &cachedInfo); err != nil { + if err := json.Unmarshal(jsonData, &cachedInfo); err != nil { return authCachedInfo{}, fmt.Errorf("could not unmarshal token: %v", err) } @@ -748,7 +831,7 @@ func (b *Broker) fetchUserInfo(ctx context.Context, session *sessionInfo, t *aut // This means that home was not provided by the claims, so we need to set it to the broker default. if !filepath.IsAbs(userInfo.Home) { - userInfo.Home = filepath.Join(b.cfg.HomeBaseDir, userInfo.Home) + userInfo.Home = filepath.Join(b.cfg.homeBaseDir, userInfo.Home) } return userInfo, err @@ -776,3 +859,48 @@ func errorMessageForDisplay(err error, fallback string) errorMessage { } return errorMessage{Message: fallback} } + +func cleanupOldEncryptedToken(path string) { + exists, err := fileutils.FileExists(path) + if err != nil { + slog.Warn(fmt.Sprintf("Failed to check if old encrypted token exists %s: %v", path, err)) + } + if !exists { + return + } + + if err := os.Remove(path); err != nil { + slog.Warn(fmt.Sprintf("Failed to remove old encrypted token %s: %v", path, err)) + return + } + + // Also remove the parent directory and the parent's parent directory if they are empty. The directory structure was: + // $SNAP_DATA/cache/$ISSUER/$USERNAME.cache + // so we try to remove the $SNAP_DATA/cache/$ISSUER directory and the $SNAP_DATA/cache directory. + + // Check if the parent directory is empty. + empty, err := fileutils.IsDirEmpty(filepath.Dir(path)) + if err != nil { + slog.Warn(fmt.Sprintf("Failed to check if old encrypted token parent directory %s is empty: %v", filepath.Dir(path), err)) + return + } + if !empty { + return + } + if err := os.Remove(filepath.Dir(path)); err != nil { + slog.Warn(fmt.Sprintf("Failed to remove old encrypted token directory %s: %v", filepath.Dir(path), err)) + } + + // Check if the parent's parent directory is empty. + empty, err = fileutils.IsDirEmpty(filepath.Dir(filepath.Dir(path))) + if err != nil { + slog.Warn(fmt.Sprintf("Failed to check if old encrypted token parent directory %s is empty: %v", filepath.Dir(filepath.Dir(path)), err)) + return + } + if !empty { + return + } + if err := os.Remove(filepath.Dir(filepath.Dir(path))); err != nil { + slog.Warn(fmt.Sprintf("Failed to remove old encrypted token parent directory %s: %v", filepath.Dir(filepath.Dir(path)), err)) + } +} diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 505759f20b..9614cf3e15 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ubuntu/authd-oidc-brokers/internal/broker" "github.com/ubuntu/authd-oidc-brokers/internal/broker/authmodes" + "github.com/ubuntu/authd-oidc-brokers/internal/password" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "github.com/ubuntu/authd-oidc-brokers/internal/testutils" "golang.org/x/oauth2" @@ -26,9 +27,9 @@ func TestNew(t *testing.T) { t.Parallel() tests := map[string]struct { - issuer string - clientID string - cachePath string + issuer string + clientID string + dataDir string wantErr bool }{ @@ -37,7 +38,7 @@ func TestNew(t *testing.T) { "Error if issuer is not provided": {issuer: "-", wantErr: true}, "Error if clientID is not provided": {clientID: "-", wantErr: true}, - "Error if cacheDir is not provided": {cachePath: "-", wantErr: true}, + "Error if dataDir is not provided": {dataDir: "-", wantErr: true}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { @@ -56,18 +57,16 @@ func TestNew(t *testing.T) { tc.clientID = "test-client-id" } - if tc.cachePath == "-" { - tc.cachePath = "" + if tc.dataDir == "-" { + tc.dataDir = "" } else { - tc.cachePath = t.TempDir() + tc.dataDir = t.TempDir() } - bCfg := broker.Config{ - IssuerURL: tc.issuer, - ClientID: tc.clientID, - CachePath: tc.cachePath, - } - b, err := broker.New(bCfg) + bCfg := &broker.Config{DataDir: tc.dataDir} + bCfg.SetIssuerURL(tc.issuer) + bCfg.SetClientID(tc.clientID) + b, err := broker.New(*bCfg) if tc.wantErr { require.Error(t, err, "New should have returned an error") return @@ -111,7 +110,9 @@ func TestNewSession(t *testing.T) { provider, stopServer := testutils.StartMockProvider("", opts...) t.Cleanup(stopServer) - b := newBrokerForTests(t, broker.Config{IssuerURL: provider.URL}) + cfg := &broker.Config{} + cfg.SetIssuerURL(provider.URL) + b := newBrokerForTests(t, *cfg) id, _, err := b.NewSession("test-user", "lang", "auth") require.NoError(t, err, "NewSession should not have returned an error") @@ -227,7 +228,9 @@ func TestGetAuthenticationModes(t *testing.T) { provider, stopServer = testutils.StartMockProvider(address, opts...) t.Cleanup(stopServer) } - b := newBrokerForTests(t, broker.Config{IssuerURL: provider.URL}) + cfg := &broker.Config{} + cfg.SetIssuerURL(provider.URL) + b := newBrokerForTests(t, *cfg) sessionID, _ := newSessionForTests(t, b, "", tc.sessionMode) if tc.sessionID == "-" { sessionID = "" @@ -344,7 +347,9 @@ func TestSelectAuthenticationMode(t *testing.T) { sessionType = "passwd" } - b := newBrokerForTests(t, broker.Config{IssuerURL: provider.URL}) + cfg := &broker.Config{} + cfg.SetIssuerURL(provider.URL) + b := newBrokerForTests(t, *cfg) sessionID, _ := newSessionForTests(t, b, "", sessionType) if tc.tokenExists { @@ -406,7 +411,7 @@ func TestIsAuthenticated(t *testing.T) { preexistentToken string invalidAuthData bool dontWaitForFirstCall bool - readOnlyCacheDir bool + readOnlyDataDir bool }{ "Successfully authenticate user with qrcode and newpassword": {firstChallenge: "-", wantSecondCall: true}, "Successfully authenticate user with link code and newpassword": {firstMode: authmodes.Device, firstChallenge: "-", wantSecondCall: true}, @@ -441,7 +446,7 @@ func TestIsAuthenticated(t *testing.T) { "Error when authentication data is invalid": {invalidAuthData: true}, "Error when challenge can not be decrypted": {firstMode: authmodes.Password, badFirstKey: true}, "Error when provided wrong challenge": {firstMode: authmodes.Password, preexistentToken: "valid", firstChallenge: "wrongpassword"}, - "Error when can not cache token": {firstChallenge: "-", wantSecondCall: true, readOnlyCacheDir: true}, + "Error when can not cache token": {firstChallenge: "-", wantSecondCall: true, readOnlyDataDir: true}, "Error when IsAuthenticated is ongoing for session": {dontWaitForFirstCall: true, wantSecondCall: true}, "Error when mode is password and token does not exist": {firstMode: authmodes.Password}, @@ -520,9 +525,9 @@ func TestIsAuthenticated(t *testing.T) { } outDir := t.TempDir() - cacheDir := filepath.Join(outDir, "cache") + dataDir := filepath.Join(outDir, "data") - err := os.Mkdir(cacheDir, 0700) + err := os.Mkdir(dataDir, 0700) require.NoError(t, err, "Setup: Mkdir should not have returned an error") provider := defaultProvider @@ -536,23 +541,27 @@ func TestIsAuthenticated(t *testing.T) { provider = p } - b := newBrokerForTests(t, broker.Config{CachePath: cacheDir, IssuerURL: provider.URL}) + cfg := &broker.Config{DataDir: dataDir} + cfg.SetIssuerURL(provider.URL) + b := newBrokerForTests(t, *cfg) sessionID, key := newSessionForTests(t, b, tc.username, tc.sessionMode) if tc.preexistentToken != "" { tok := generateCachedInfo(t, tc.preexistentToken, provider.URL) - err := b.CacheAuthInfo(sessionID, tok, correctPassword) + err := b.CacheAuthInfo(sessionID, tok) require.NoError(t, err, "Setup: SaveToken should not have returned an error") + err = password.HashAndStorePassword(correctPassword, b.PasswordFilepathForSession(sessionID)) + require.NoError(t, err, "Setup: HashAndStorePassword should not have returned an error") } - var readOnlyCacheCleanup, readOnlyTokenCleanup func() - if tc.readOnlyCacheDir { + var readOnlyDataCleanup, readOnlyTokenCleanup func() + if tc.readOnlyDataDir { if tc.preexistentToken != "" { readOnlyTokenCleanup = testutils.MakeReadOnly(t, b.TokenPathForSession(sessionID)) t.Cleanup(readOnlyTokenCleanup) } - readOnlyCacheCleanup = testutils.MakeReadOnly(t, b.CachePath()) - t.Cleanup(readOnlyCacheCleanup) + readOnlyDataCleanup = testutils.MakeReadOnly(t, b.DataDir()) + t.Cleanup(readOnlyDataCleanup) } switch tc.firstChallenge { @@ -639,8 +648,8 @@ func TestIsAuthenticated(t *testing.T) { <-firstCallDone // We need to restore some permissions in order to save the golden files. - if tc.readOnlyCacheDir { - readOnlyCacheCleanup() + if tc.readOnlyDataDir { + readOnlyDataCleanup() if tc.preexistentToken != "" { readOnlyTokenCleanup() } @@ -651,15 +660,21 @@ func TestIsAuthenticated(t *testing.T) { err := os.WriteFile(b.TokenPathForSession(sessionID), []byte("Definitely an encrypted token"), 0600) require.NoError(t, err, "Teardown: Failed to write generic token file") } + passwordPath := b.PasswordFilepathForSession(sessionID) + if _, err := os.Stat(passwordPath); err == nil { + err := os.WriteFile(passwordPath, []byte("Definitely a hashed password"), 0600) + require.NoError(t, err, "Teardown: Failed to write generic password file") + } // Ensure that the directory structure is generic to avoid golden file conflicts if _, err := os.Stat(filepath.Dir(b.TokenPathForSession(sessionID))); err == nil { toReplace := strings.ReplaceAll(strings.TrimPrefix(provider.URL, "http://"), ":", "_") - providerCache := filepath.Dir(b.TokenPathForSession(sessionID)) - newProviderCache := strings.ReplaceAll(providerCache, toReplace, "provider_url") - err := os.Rename(providerCache, newProviderCache) + tokenDir := filepath.Dir(filepath.Dir(b.TokenPathForSession(sessionID))) + newTokenDir := strings.ReplaceAll(tokenDir, toReplace, "provider_url") + err := os.Rename(tokenDir, newTokenDir) if err != nil { - require.ErrorIs(t, err, os.ErrNotExist, "Teardown: Failed to rename cache directory") + require.ErrorIs(t, err, os.ErrNotExist, "Teardown: Failed to rename token directory") + t.Logf("Failed to rename token directory: %v", err) } } @@ -683,10 +698,12 @@ func TestConcurrentIsAuthenticated(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { outDir := t.TempDir() - cacheDir := filepath.Join(outDir, "cache") - err := os.Mkdir(cacheDir, 0700) + dataDir := filepath.Join(outDir, "data") + err := os.Mkdir(dataDir, 0700) require.NoError(t, err, "Setup: Mkdir should not have returned an error") - b := newBrokerForTests(t, broker.Config{CachePath: cacheDir, IssuerURL: defaultProvider.URL}) + cfg := &broker.Config{DataDir: dataDir} + cfg.SetIssuerURL(defaultProvider.URL) + b := newBrokerForTests(t, *cfg) if tc.firstUser == "" { tc.firstUser = "user-timeout-0" @@ -697,13 +714,17 @@ func TestConcurrentIsAuthenticated(t *testing.T) { firstSession, firstKey := newSessionForTests(t, b, tc.firstUser, "") tok := generateCachedInfo(t, tc.firstUser, defaultProvider.URL) - err = b.CacheAuthInfo(firstSession, tok, authmodes.Password) + err = b.CacheAuthInfo(firstSession, tok) require.NoError(t, err, "Setup: SaveToken should not have returned an error") + err = password.HashAndStorePassword("password", b.PasswordFilepathForSession(firstSession)) + require.NoError(t, err, "Setup: HashAndStorePassword should not have returned an error") secondSession, secondKey := newSessionForTests(t, b, tc.secondUser, "") tok = generateCachedInfo(t, tc.secondUser, defaultProvider.URL) - err = b.CacheAuthInfo(secondSession, tok, authmodes.Password) + err = b.CacheAuthInfo(secondSession, tok) require.NoError(t, err, "Setup: SaveToken should not have returned an error") + err = password.HashAndStorePassword("password", b.PasswordFilepathForSession(secondSession)) + require.NoError(t, err, "Setup: HashAndStorePassword should not have returned an error") firstCallDone := make(chan struct{}) go func() { @@ -760,16 +781,22 @@ func TestConcurrentIsAuthenticated(t *testing.T) { err := os.WriteFile(b.TokenPathForSession(sessionID), []byte("Definitely an encrypted token"), 0600) require.NoError(t, err, "Teardown: Failed to write generic token file") } + passwordPath := b.PasswordFilepathForSession(sessionID) + if _, err := os.Stat(passwordPath); err == nil { + err := os.WriteFile(passwordPath, []byte("Definitely a hashed password"), 0600) + require.NoError(t, err, "Teardown: Failed to write generic password file") + } } // Ensure that the directory structure is generic to avoid golden file conflicts - if _, err := os.Stat(filepath.Dir(b.TokenPathForSession(firstSession))); err == nil { + issuerDataDir := filepath.Dir(b.UserDataDirForSession(firstSession)) + if _, err := os.Stat(issuerDataDir); err == nil { toReplace := strings.ReplaceAll(strings.TrimPrefix(defaultProvider.URL, "http://"), ":", "_") - providerCache := filepath.Dir(b.TokenPathForSession(firstSession)) - newProviderCache := strings.ReplaceAll(providerCache, toReplace, "provider_url") - err := os.Rename(providerCache, newProviderCache) + newIssuerDataDir := strings.ReplaceAll(issuerDataDir, toReplace, "provider_url") + err := os.Rename(issuerDataDir, newIssuerDataDir) if err != nil { - require.ErrorIs(t, err, os.ErrNotExist, "Teardown: Failed to rename cache directory") + require.ErrorIs(t, err, os.ErrNotExist, "Teardown: Failed to rename issuer data directory") + t.Logf("Failed to rename issuer data directory: %v", err) } } testutils.CompareTreesWithFiltering(t, outDir, testutils.GoldenPath(t), testutils.Update()) @@ -808,12 +835,12 @@ func TestFetchUserInfo(t *testing.T) { homeDirPath = "" } - brokerCfg := broker.Config{ - IssuerURL: defaultProvider.URL, - ClientID: "test-client-id", - CachePath: t.TempDir(), - HomeBaseDir: homeDirPath, - } + dataDir := t.TempDir() + clientID := "test-client-id" + brokerCfg := &broker.Config{DataDir: dataDir} + brokerCfg.SetIssuerURL(defaultProvider.URL) + brokerCfg.SetHomeBaseDir(homeDirPath) + brokerCfg.SetClientID(clientID) mockInfoer := &testutils.MockProviderInfoer{ GroupsErr: tc.wantGroupErr, @@ -826,7 +853,7 @@ func TestFetchUserInfo(t *testing.T) { mockInfoer.Groups = []info.Group{} } - b, err := broker.New(brokerCfg, broker.WithCustomProviderInfo(mockInfoer)) + b, err := broker.New(*brokerCfg, broker.WithCustomProviderInfo(mockInfoer)) require.NoError(t, err, "Setup: New should not have returned an error") if tc.username == "" { @@ -863,7 +890,9 @@ func TestCancelIsAuthenticated(t *testing.T) { provider, cleanup := testutils.StartMockProvider("", testutils.WithHandler("/token", testutils.HangingHandler(3*time.Second))) t.Cleanup(cleanup) - b := newBrokerForTests(t, broker.Config{IssuerURL: provider.URL}) + cfg := &broker.Config{} + cfg.SetIssuerURL(provider.URL) + b := newBrokerForTests(t, *cfg) sessionID, _ := newSessionForTests(t, b, "", "") updateAuthModes(t, b, sessionID, authmodes.DeviceQr) @@ -885,7 +914,9 @@ func TestCancelIsAuthenticated(t *testing.T) { func TestEndSession(t *testing.T) { t.Parallel() - b := newBrokerForTests(t, broker.Config{IssuerURL: defaultProvider.URL}) + cfg := &broker.Config{} + cfg.SetIssuerURL(defaultProvider.URL) + b := newBrokerForTests(t, *cfg) sessionID, _ := newSessionForTests(t, b, "", "") @@ -939,7 +970,11 @@ func TestUserPreCheck(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { t.Parallel() - b := newBrokerForTests(t, broker.Config{IssuerURL: defaultProvider.URL, AllowedSSHSuffixes: tc.allowedSuffixes, HomeBaseDir: tc.homePrefix}) + cfg := &broker.Config{} + cfg.SetIssuerURL(defaultProvider.URL) + cfg.SetHomeBaseDir(tc.homePrefix) + cfg.SetAllowedSSHSuffixes(tc.allowedSuffixes) + b := newBrokerForTests(t, *cfg) got, err := b.UserPreCheck(tc.username) if tc.wantErr { diff --git a/internal/broker/config.go b/internal/broker/config.go new file mode 100644 index 0000000000..6cf224cb99 --- /dev/null +++ b/internal/broker/config.go @@ -0,0 +1,62 @@ +package broker + +import ( + "errors" + "fmt" + "strings" + + "gopkg.in/ini.v1" +) + +// Configuration sections and keys. +const ( + // oidcSection is the section name in the config file for the OIDC specific configuration. + oidcSection = "oidc" + // issuerKey is the key in the config file for the issuer. + issuerKey = "issuer" + // clientIDKey is the key in the config file for the client ID. + clientIDKey = "client_id" + + // usersSection is the section name in the config file for the users and broker specific configuration. + usersSection = "users" + // homeDirKey is the key in the config file for the home directory prefix. + homeDirKey = "home_base_dir" + // SSHSuffixKey is the key in the config file for the SSH allowed suffixes. + sshSuffixesKey = "ssh_allowed_suffixes" +) + +// parseConfigFile parses the config file and returns a map with the configuration keys and values. +func parseConfigFile(cfgPath string) (userConfig, error) { + cfg := userConfig{} + + iniCfg, err := ini.Load(cfgPath) + if err != nil { + return cfg, err + } + + // Check if any of the keys still contain the placeholders. + for _, section := range iniCfg.Sections() { + for _, key := range section.Keys() { + if strings.Contains(key.Value(), "<") && strings.Contains(key.Value(), ">") { + err = errors.Join(err, fmt.Errorf("found invalid character in section %q, key %q", section.Name(), key.Name())) + } + } + } + if err != nil { + return cfg, fmt.Errorf("config file has invalid values, did you edit the file %q?\n%w", cfgPath, err) + } + + oidc := iniCfg.Section(oidcSection) + if oidc != nil { + cfg.issuerURL = oidc.Key(issuerKey).String() + cfg.clientID = oidc.Key(clientIDKey).String() + } + + users := iniCfg.Section(usersSection) + if users != nil { + cfg.homeBaseDir = users.Key(homeDirKey).String() + cfg.allowedSSHSuffixes = strings.Split(users.Key(sshSuffixesKey).String(), ",") + } + + return cfg, nil +} diff --git a/cmd/authd-oidc/daemon/internal_test.go b/internal/broker/config_test.go similarity index 65% rename from cmd/authd-oidc/daemon/internal_test.go rename to internal/broker/config_test.go index a5ae962358..fb6411d086 100644 --- a/cmd/authd-oidc/daemon/internal_test.go +++ b/internal/broker/config_test.go @@ -1,9 +1,13 @@ -package daemon +package broker import ( + "fmt" "os" "path/filepath" + "reflect" + "strings" "testing" + "unsafe" "github.com/stretchr/testify/require" "github.com/ubuntu/authd-oidc-brokers/internal/testutils" @@ -77,15 +81,32 @@ func TestParseConfig(t *testing.T) { require.NoError(t, err, "Setup: Failed to make config file unreadable") } - got, err := parseConfig(confPath) + cfg, err := parseConfigFile(confPath) if tc.wantErr { require.Error(t, err) return } require.NoError(t, err) - want := testutils.LoadWithUpdateFromGoldenYAML(t, got) - require.EqualValues(t, want, got) + outDir := t.TempDir() + // Write the names and values of all fields in the config to a file. We can't use the json or yaml + // packages because they can't access unexported fields. + var fields []string + val := reflect.ValueOf(&cfg).Elem() + typ := reflect.TypeOf(&cfg).Elem() + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + fieldValue := val.Field(i) + if field.PkgPath != "" { + //nolint: gosec // We are using unsafe to access unexported fields for testing purposes + fieldValue = reflect.NewAt(fieldValue.Type(), unsafe.Pointer(fieldValue.UnsafeAddr())).Elem() + } + fields = append(fields, fmt.Sprintf("%s=%v", field.Name, fieldValue)) + } + err = os.WriteFile(filepath.Join(outDir, "config.txt"), []byte(strings.Join(fields, "\n")), 0600) + require.NoError(t, err) + + testutils.CompareTreesWithFiltering(t, outDir, testutils.GoldenPath(t), testutils.Update()) }) } } diff --git a/internal/broker/encrypt.go b/internal/broker/encrypt.go index 0e4755129b..b7e11f0d9e 100644 --- a/internal/broker/encrypt.go +++ b/internal/broker/encrypt.go @@ -3,7 +3,6 @@ package broker import ( "crypto/aes" "crypto/cipher" - "crypto/rand" "crypto/rsa" "crypto/sha512" "encoding/base64" @@ -16,36 +15,6 @@ import ( const saltLen = 32 -func encrypt(raw, key []byte) ([]byte, error) { - salt := make([]byte, saltLen) - if _, err := rand.Read(salt); err != nil { - return nil, err - } - - derivedKey, err := scrypt.Key(key, salt, 32768, 8, 1, 32) - if err != nil { - return nil, err - } - - block, err := aes.NewCipher(derivedKey) - if err != nil { - return nil, err - } - - gcm, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - - nonce := make([]byte, gcm.NonceSize()) - if _, err := rand.Read(nonce); err != nil { - return nil, err - } - - ciphered := gcm.Seal(nonce, nonce, raw, nil) - return append(ciphered, salt...), nil -} - func decrypt(ciphered, key []byte) ([]byte, error) { salt, data := ciphered[len(ciphered)-saltLen:], ciphered[:len(ciphered)-saltLen] diff --git a/internal/broker/export_test.go b/internal/broker/export_test.go index e81adafcb8..601cd3b3f3 100644 --- a/internal/broker/export_test.go +++ b/internal/broker/export_test.go @@ -9,6 +9,30 @@ import ( "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" ) +func (cfg *Config) SetClientID(clientID string) { + cfg.clientID = clientID +} + +func (cfg *Config) SetIssuerURL(issuerURL string) { + cfg.issuerURL = issuerURL +} + +func (cfg *Config) SetHomeBaseDir(homeBaseDir string) { + cfg.homeBaseDir = homeBaseDir +} + +func (cfg *Config) SetAllowedSSHSuffixes(allowedSSHSuffixes []string) { + cfg.allowedSSHSuffixes = allowedSSHSuffixes +} + +func (cfg *Config) ClientID() string { + return cfg.clientID +} + +func (cfg *Config) IssuerURL() string { + return cfg.issuerURL +} + // TokenPathForSession returns the path to the token file for the given session. func (b *Broker) TokenPathForSession(sessionID string) string { b.currentSessionsMu.Lock() @@ -19,12 +43,38 @@ func (b *Broker) TokenPathForSession(sessionID string) string { return "" } - return session.cachePath + return session.tokenPath +} + +// PasswordFilepathForSession returns the path to the password file for the given session. +func (b *Broker) PasswordFilepathForSession(sessionID string) string { + b.currentSessionsMu.Lock() + defer b.currentSessionsMu.Unlock() + + session, ok := b.currentSessions[sessionID] + if !ok { + return "" + } + + return session.passwordPath +} + +// UserDataDirForSession returns the path to the user data directory for the given session. +func (b *Broker) UserDataDirForSession(sessionID string) string { + b.currentSessionsMu.Lock() + defer b.currentSessionsMu.Unlock() + + session, ok := b.currentSessions[sessionID] + if !ok { + return "" + } + + return session.userDataDir } -// CachePath returns the path to the cache directory for tests. -func (b *Broker) CachePath() string { - return b.cfg.CachePath +// DataDir returns the path to the data directory for tests. +func (b *Broker) DataDir() string { + return b.cfg.DataDir } // UpdateSessionAuthStep updates the current auth step for the given session. @@ -69,24 +119,23 @@ func (b *Broker) SetAvailableMode(sessionID, mode string) error { type AuthCachedInfo = authCachedInfo // CacheAuthInfo exposes the broker's cacheAuthInfo method for tests. -func (b *Broker) CacheAuthInfo(sessionID string, token *authCachedInfo, password string) error { +func (b *Broker) CacheAuthInfo(sessionID string, token *authCachedInfo) error { s, err := b.getSession(sessionID) if err != nil { return err } if token == nil { - return writeTrashToken(s.cachePath, password) + return writeTrashToken(s.tokenPath) } - return b.cacheAuthInfo(&s, *token, password) + return b.cacheAuthInfo(&s, *token) } -func writeTrashToken(path, challenge string) error { - content, err := encrypt([]byte("This is a trash token that is not valid for authentication"), []byte(challenge)) - if err != nil { - return err - } +func writeTrashToken(path string) error { + var err error + content := []byte("This is a trash token that is not valid for authentication") + // Create issuer specific cache directory if it doesn't exist. if err = os.MkdirAll(filepath.Dir(path), 0700); err != nil { return fmt.Errorf("could not create token directory: %v", err) diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index 9fcabfc91a..f2f50925f7 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -20,17 +20,17 @@ import ( // newBrokerForTests is a helper function to create a new broker for tests with the specified configuration. // -// Note that the IssuerURL is required in the configuration. +// Note that the issuerURL is required in the configuration. func newBrokerForTests(t *testing.T, cfg broker.Config) (b *broker.Broker) { t.Helper() - require.NotEmpty(t, cfg.IssuerURL, "Setup: issuerURL must not be empty") + require.NotEmpty(t, cfg.IssuerURL(), "Setup: issuerURL must not be empty") - if cfg.CachePath == "" { - cfg.CachePath = t.TempDir() + if cfg.DataDir == "" { + cfg.DataDir = t.TempDir() } - if cfg.ClientID == "" { - cfg.ClientID = "test-client-id" + if cfg.ClientID() == "" { + cfg.SetClientID("test-client-id") } b, err := broker.New( diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user-timeout-0/password b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user-timeout-0/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user-timeout-0/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/cache/provider_url/user-timeout-0.cache b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user-timeout-0/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/cache/provider_url/user-timeout-0.cache rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user-timeout-0/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user-timeout-1/password b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user-timeout-1/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user-timeout-1/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/cache/provider_url/user-timeout-1.cache b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user-timeout-1/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/cache/provider_url/user-timeout-1.cache rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user-timeout-1/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user-timeout-1/password b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user-timeout-1/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user-timeout-1/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/cache/provider_url/user-timeout-1.cache b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user-timeout-1/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/cache/provider_url/user-timeout-1.cache rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user-timeout-1/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user-timeout-3/password b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user-timeout-3/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user-timeout-3/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/cache/provider_url/user-timeout-3.cache b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user-timeout-3/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/cache/provider_url/user-timeout-3.cache rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user-timeout-3/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user-timeout-2/password b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user-timeout-2/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user-timeout-2/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/cache/provider_url/user-timeout-2.cache b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user-timeout-2/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/cache/provider_url/user-timeout-2.cache rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user-timeout-2/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user-timeout-3/password b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user-timeout-3/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user-timeout-3/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/cache/provider_url/user-timeout-3.cache b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user-timeout-3/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/cache/provider_url/user-timeout-3.cache rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user-timeout-3/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/cache/provider_url/test-user@email.com.cache rename to internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/cache/provider_url/test-user@email.com.cache rename to internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/cache/provider_url/test-user@email.com.cache rename to internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/cache/provider_url/test-user@email.com.cache rename to internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/cache/provider_url/test-user@email.com.cache rename to internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/cache/provider_url/test-user@email.com.cache rename to internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_authentication_data_is_invalid/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_authentication_data_is_invalid/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_authentication_data_is_invalid/cache/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_authentication_data_is_invalid/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/cache/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/second_call index 00cb9a752e..7e0ccb7170 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/second_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/second_call @@ -1,3 +1,3 @@ access: denied -data: '{"message":"authentication failure: could not cache user info"}' +data: '{"message":"authentication failure: could not store password"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_challenge_can_not_be_decrypted/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_challenge_can_not_be_decrypted/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_challenge_can_not_be_decrypted/cache/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_challenge_can_not_be_decrypted/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/cache/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/cache/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token/cache/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/cache/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_link_expires/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_link_expires/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_link_expires/cache/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_link_expires/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_response_is_invalid/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_response_is_invalid/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_response_is_invalid/cache/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_response_is_invalid/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/cache/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/cache/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_token_is_not_set/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_token_is_not_set/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_token_is_not_set/cache/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_token_is_not_set/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/cache/provider_url/test-user@email.com.cache rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/cache/provider_url/test-user@email.com.cache rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/cache/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call index 69d73127bc..8e7a31d8b8 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call @@ -1,3 +1,3 @@ -access: retry -data: '{"message":"authentication failure: could not load cached info"}' +access: denied +data: '{"message":"authentication failure: password file does not exist"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/cache/provider_url/test-user@email.com.cache rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/cache/provider_url/test-user@email.com.cache rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/cache/provider_url/test-user@email.com.cache rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token/cache/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/cache/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_link_expires/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_link_expires/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_link_expires/cache/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_link_expires/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_response_is_invalid/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_response_is_invalid/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_response_is_invalid/cache/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_response_is_invalid/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/cache/provider_url/test-user@email.com.cache rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/first_call index 69d73127bc..c096089b92 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/first_call @@ -1,3 +1,3 @@ access: retry -data: '{"message":"authentication failure: could not load cached info"}' +data: '{"message":"authentication failure: incorrect password"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/cache/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/cache/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/cache/provider_url/test-user@email.com.cache rename to internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/cache/provider_url/test-user@email.com.cache rename to internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/cache/provider_url/test-user@email.com.cache b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/cache/provider_url/test-user@email.com.cache rename to internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter/config.txt b/internal/broker/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter/config.txt new file mode 100644 index 0000000000..29d43c2f42 --- /dev/null +++ b/internal/broker/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter/config.txt @@ -0,0 +1,4 @@ +clientID= +homeBaseDir= +allowedSSHSuffixes=[] \ No newline at end of file diff --git a/internal/broker/testdata/TestParseConfig/golden/successfully_parse_config_file/config.txt b/internal/broker/testdata/TestParseConfig/golden/successfully_parse_config_file/config.txt new file mode 100644 index 0000000000..9e322c42ee --- /dev/null +++ b/internal/broker/testdata/TestParseConfig/golden/successfully_parse_config_file/config.txt @@ -0,0 +1,4 @@ +clientID=client_id +issuerURL=https://issuer.url.com +homeBaseDir= +allowedSSHSuffixes=[] \ No newline at end of file diff --git a/internal/broker/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values/config.txt b/internal/broker/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values/config.txt new file mode 100644 index 0000000000..0fac62eeb2 --- /dev/null +++ b/internal/broker/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values/config.txt @@ -0,0 +1,4 @@ +clientID=client_id +issuerURL=https://issuer.url.com +homeBaseDir=/home +allowedSSHSuffixes=[] \ No newline at end of file diff --git a/internal/dbusservice/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter b/internal/dbusservice/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter deleted file mode 100644 index 17a42fa4e9..0000000000 --- a/internal/dbusservice/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter +++ /dev/null @@ -1,4 +0,0 @@ -DEFAULT: {} -oidc: - client_id: diff --git a/internal/dbusservice/testdata/TestParseConfig/golden/successfully_parse_config_file b/internal/dbusservice/testdata/TestParseConfig/golden/successfully_parse_config_file deleted file mode 100644 index 622dbad7f1..0000000000 --- a/internal/dbusservice/testdata/TestParseConfig/golden/successfully_parse_config_file +++ /dev/null @@ -1,4 +0,0 @@ -DEFAULT: {} -oidc: - client_id: client_id - issuer: https://issuer.url.com diff --git a/internal/dbusservice/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values b/internal/dbusservice/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values deleted file mode 100644 index 53fe96579b..0000000000 --- a/internal/dbusservice/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values +++ /dev/null @@ -1,7 +0,0 @@ -DEFAULT: {} -oidc: - client_id: client_id - issuer: https://issuer.url.com -users: - allowed_ssh_suffixes: '@issuer.url.com' - home_base_dir: /home diff --git a/internal/fileutils/fileutils.go b/internal/fileutils/fileutils.go new file mode 100644 index 0000000000..584aeef45c --- /dev/null +++ b/internal/fileutils/fileutils.go @@ -0,0 +1,45 @@ +// Package fileutils provides utility functions for file operations. +package fileutils + +import ( + "errors" + "io" + "os" +) + +// FileExists checks if a file exists at the given path. +func FileExists(path string) (bool, error) { + _, err := os.Stat(path) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return false, err + } + return !errors.Is(err, os.ErrNotExist), nil +} + +// IsDirEmpty checks if the specified directory is empty. +func IsDirEmpty(path string) (bool, error) { + f, err := os.Open(path) + if err != nil { + return false, err + } + defer f.Close() + + _, err = f.Readdirnames(1) + if errors.Is(err, io.EOF) { + return true, nil + } + return false, err +} + +// Touch creates an empty file at the given path, if it doesn't already exist. +func Touch(path string) error { + file, err := os.OpenFile(path, os.O_RDONLY|os.O_CREATE, 0o600) + if err != nil && !errors.Is(err, os.ErrExist) { + return err + } + err = file.Close() + if err != nil { + return err + } + return nil +} diff --git a/internal/fileutils/fileutils_test.go b/internal/fileutils/fileutils_test.go new file mode 100644 index 0000000000..d1e54dbf49 --- /dev/null +++ b/internal/fileutils/fileutils_test.go @@ -0,0 +1,156 @@ +package fileutils_test + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + "github.com/ubuntu/authd-oidc-brokers/internal/fileutils" +) + +func TestFileExists(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + name string + fileExists bool + parentDirIsFile bool + + wantExists bool + wantError bool + }{ + "Returns true when file exists": {fileExists: true, wantExists: true}, + "Returns false when file does not exist": {fileExists: false, wantExists: false}, + "Returns false when parent directory does not exist": {fileExists: false, wantExists: false}, + + "Error when parent directory is a file": {parentDirIsFile: true, wantError: true}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + tempDir := t.TempDir() + path := filepath.Join(tempDir, "file") + if tc.fileExists { + err := fileutils.Touch(path) + require.NoError(t, err, "Touch should not return an error") + } + if tc.parentDirIsFile { + path = filepath.Join(tempDir, "file", "file") + err := fileutils.Touch(filepath.Join(tempDir, "file")) + require.NoError(t, err, "Touch should not return an error") + } + + exists, err := fileutils.FileExists(path) + if tc.wantError { + require.Error(t, err, "FileExists should return an error") + } else { + require.NoError(t, err, "FileExists should not return an error") + } + require.Equal(t, tc.wantExists, exists, "FileExists should return the expected result") + }) + } +} + +func TestIsDirEmpty(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + isEmpty bool + isFile bool + doesNotExist bool + + wantEmpty bool + wantError bool + }{ + "Returns true when directory is empty": {isEmpty: true, wantEmpty: true}, + "Returns false when directory is not empty": {wantEmpty: false}, + + "Error when directory does not exist": {doesNotExist: true, wantError: true}, + "Error when directory is a file": {isFile: true, wantError: true}, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + tempDir := t.TempDir() + path := filepath.Join(tempDir, "dir") + + if !tc.doesNotExist { + err := os.Mkdir(path, 0o700) + require.NoError(t, err, "Mkdir should not return an error") + } + + if !tc.isEmpty && !tc.doesNotExist && !tc.isFile { + err := fileutils.Touch(filepath.Join(tempDir, "dir", "file")) + require.NoError(t, err, "Touch should not return an error") + } + if tc.isFile { + path = filepath.Join(tempDir, "file") + err := fileutils.Touch(path) + require.NoError(t, err, "Touch should not return an error") + } + + empty, err := fileutils.IsDirEmpty(path) + if tc.wantError { + require.Error(t, err, "IsDirEmpty should return an error") + } else { + require.NoError(t, err, "IsDirEmpty should not return an error") + } + require.Equal(t, tc.wantEmpty, empty, "IsDirEmpty should return the expected result") + }) + } +} + +func TestTouch(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + name string + fileExists bool + fileIsDir bool + parentDoesNotExist bool + + wantError bool + }{ + "Creates file when it does not exist": {fileExists: false}, + "Does not return error when file already exists": {fileExists: true}, + + "Returns error when file is a directory": {fileIsDir: true, wantError: true}, + "Returns error when parent directory does not exist": {parentDoesNotExist: true, wantError: true}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + tempDir := t.TempDir() + path := filepath.Join(tempDir, "file") + + if tc.fileExists && !tc.fileIsDir { + err := fileutils.Touch(path) + require.NoError(t, err, "Touch should not return an error") + } + + if tc.fileIsDir { + path = filepath.Join(tempDir, "dir") + err := os.Mkdir(path, 0o700) + require.NoError(t, err, "Mkdir should not return an error") + } + + if tc.parentDoesNotExist { + path = filepath.Join(tempDir, "dir", "file") + } + + err := fileutils.Touch(path) + if tc.wantError { + require.Error(t, err, "Touch should return an error") + return + } + + require.NoError(t, err, "Touch should not return an error") + }) + } +} diff --git a/internal/password/password.go b/internal/password/password.go new file mode 100644 index 0000000000..28245241e0 --- /dev/null +++ b/internal/password/password.go @@ -0,0 +1,58 @@ +// Package password provides functions for creating and using the hashed password file. +package password + +import ( + "crypto/rand" + "encoding/base64" + "fmt" + "os" + "path/filepath" + "slices" + + "golang.org/x/crypto/argon2" +) + +// HashAndStorePassword hashes the password and stores it in the data directory. +func HashAndStorePassword(password, path string) error { + // Ensure that the password file's parent directory exists. + if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { + return fmt.Errorf("could not create password parent directory: %w", err) + } + + salt := make([]byte, 16) + if _, err := rand.Read(salt); err != nil { + return fmt.Errorf("could not generate salt: %w", err) + } + + hash := hashPassword(password, salt) + s := base64.StdEncoding.EncodeToString(append(salt, hash...)) + if err := os.WriteFile(path, []byte(s), 0o600); err != nil { + return fmt.Errorf("could not store password: %w", err) + } + + return nil +} + +// CheckPassword checks if the provided password matches the hash stored in the password file. +func CheckPassword(password, path string) (bool, error) { + data, err := os.ReadFile(path) + if err != nil { + return false, fmt.Errorf("could not read password file: %w", err) + } + + decoded, err := base64.StdEncoding.DecodeString(string(data)) + if err != nil { + return false, fmt.Errorf("could not decode password: %w", err) + } + + salt, hash := decoded[:16], decoded[16:] + if !slices.Equal(hash, hashPassword(password, salt)) { + return false, nil + } + + return true, nil +} + +func hashPassword(password string, salt []byte) []byte { + return argon2.IDKey([]byte(password), salt, 1, 64*1024, 4, 32) +} diff --git a/internal/password/password_test.go b/internal/password/password_test.go new file mode 100644 index 0000000000..9632098f68 --- /dev/null +++ b/internal/password/password_test.go @@ -0,0 +1,106 @@ +package password_test + +import ( + "encoding/base64" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + "github.com/ubuntu/authd-oidc-brokers/internal/fileutils" + "github.com/ubuntu/authd-oidc-brokers/internal/password" +) + +func TestHashAndStorePassword(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + password string + path string + pathExists bool + parentDirExists bool + + wantErr bool + }{ + "Success when password file and parent dir don't exist yet": {password: "test123"}, + "Success when parent directory already exists": {password: "test123", parentDirExists: true}, + "Success when password file already exists": {password: "test123", pathExists: true}, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + if tc.pathExists { + // The parent directory must also exist for the file to exist. + tc.parentDirExists = true + } + + parentDir := t.TempDir() + if !tc.parentDirExists { + err := os.Remove(parentDir) + require.NoError(t, err, "Removing parent directory failed") + } + path := filepath.Join(parentDir, "password") + + if tc.pathExists { + err := fileutils.Touch(path) + require.NoError(t, err, "Creating empty password file failed") + } + + err := password.HashAndStorePassword(tc.password, path) + if err != nil { + t.Fatalf("HashAndStorePassword() failed: %v", err) + } + }) + } +} + +func TestCheckPassword(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + password string + pathToRead string + writeGarbage bool + + wantMatch bool + expectedError error + }{ + "Success when password matches": {password: "test123", wantMatch: true}, + "No match when password doesn't match": {password: "not-test123", wantMatch: false}, + + "Error when password file doesn't exist": {password: "test123", pathToRead: "nonexistent", expectedError: os.ErrNotExist}, + "Error when password file contains garbage": {password: "test123", writeGarbage: true, expectedError: base64.CorruptInputError(0)}, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + parentDir := t.TempDir() + path := filepath.Join(parentDir, "password") + + if tc.pathToRead == "" { + tc.pathToRead = path + } + + err := password.HashAndStorePassword("test123", path) + require.NoError(t, err, "HashAndStorePassword() failed") + + if tc.writeGarbage { + err := os.WriteFile(path, []byte{0x00}, 0o600) + require.NoError(t, err, "Writing garbage to password file failed") + } + + match, err := password.CheckPassword(tc.password, tc.pathToRead) + if tc.expectedError != nil { + require.ErrorIs(t, err, tc.expectedError, "CheckPassword() failed") + } else { + require.NoError(t, err, "CheckPassword() failed") + } + + require.Equal(t, tc.wantMatch, match, "CheckPassword() returned unexpected result") + }) + } +} From 893b48acca2f30d28fc72fd252d783d5f3772c50 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 17 Sep 2024 17:39:02 +0200 Subject: [PATCH 0192/1670] refactor: Make broker tests a bit less confusing Instead of making the behavior of the ProviderInfoer mock depend on the username, we now set a field when we want to insert a delay into the GetUserInfo call. --- internal/broker/broker_test.go | 42 ++++++++----------- internal/broker/helper_test.go | 30 ++++++------- .../{user-timeout-0 => user1}/password | 0 .../{user-timeout-0 => user1}/token.json | 0 .../{user-timeout-1 => user2}/password | 0 .../{user-timeout-1 => user2}/token.json | 0 .../first_auth | 2 +- .../second_auth | 2 +- .../{user-timeout-1 => user1}/password | 0 .../{user-timeout-1 => user1}/token.json | 0 .../{user-timeout-3 => user2}/password | 0 .../{user-timeout-3 => user2}/token.json | 0 .../first_auth | 2 +- .../second_auth | 2 +- .../{user-timeout-2 => user1}/password | 0 .../{user-timeout-2 => user1}/token.json | 0 .../{user-timeout-3 => user2}/password | 0 .../{user-timeout-3 => user2}/token.json | 0 .../first_auth | 2 +- .../second_auth | 2 +- internal/testutils/provider.go | 33 +++++++++------ 21 files changed, 56 insertions(+), 61 deletions(-) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/{user-timeout-0 => user1}/password (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/{user-timeout-0 => user1}/token.json (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/{user-timeout-1 => user2}/password (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/{user-timeout-1 => user2}/token.json (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/{user-timeout-1 => user1}/password (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/{user-timeout-1 => user1}/token.json (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/{user-timeout-3 => user2}/password (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/{user-timeout-3 => user2}/token.json (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/{user-timeout-2 => user1}/password (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/{user-timeout-2 => user1}/token.json (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/{user-timeout-3 => user2}/password (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/{user-timeout-3 => user2}/token.json (100%) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 9614cf3e15..fc733c922d 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -112,7 +112,7 @@ func TestNewSession(t *testing.T) { t.Cleanup(stopServer) cfg := &broker.Config{} cfg.SetIssuerURL(provider.URL) - b := newBrokerForTests(t, *cfg) + b := newBrokerForTests(t, *cfg, nil) id, _, err := b.NewSession("test-user", "lang", "auth") require.NoError(t, err, "NewSession should not have returned an error") @@ -230,7 +230,7 @@ func TestGetAuthenticationModes(t *testing.T) { } cfg := &broker.Config{} cfg.SetIssuerURL(provider.URL) - b := newBrokerForTests(t, *cfg) + b := newBrokerForTests(t, *cfg, nil) sessionID, _ := newSessionForTests(t, b, "", tc.sessionMode) if tc.sessionID == "-" { sessionID = "" @@ -349,7 +349,7 @@ func TestSelectAuthenticationMode(t *testing.T) { cfg := &broker.Config{} cfg.SetIssuerURL(provider.URL) - b := newBrokerForTests(t, *cfg) + b := newBrokerForTests(t, *cfg, nil) sessionID, _ := newSessionForTests(t, b, "", sessionType) if tc.tokenExists { @@ -543,7 +543,7 @@ func TestIsAuthenticated(t *testing.T) { cfg := &broker.Config{DataDir: dataDir} cfg.SetIssuerURL(provider.URL) - b := newBrokerForTests(t, *cfg) + b := newBrokerForTests(t, *cfg, nil) sessionID, key := newSessionForTests(t, b, tc.username, tc.sessionMode) if tc.preexistentToken != "" { @@ -686,14 +686,14 @@ func TestIsAuthenticated(t *testing.T) { // Due to ordering restrictions, this test can not be run in parallel, otherwise the routines would not be ordered as expected. func TestConcurrentIsAuthenticated(t *testing.T) { tests := map[string]struct { - firstUser string - secondUser string + firstCallDelay int + secondCallDelay int timeBetween time.Duration }{ - "First auth starts and finishes before second": {timeBetween: 2 * time.Second}, - "First auth starts first but second finishes first": {firstUser: "user-timeout-3", timeBetween: time.Second}, - "First auth starts first then second starts and first finishes": {firstUser: "user-timeout-2", secondUser: "user-timeout-3", timeBetween: time.Second}, + "First auth starts and finishes before second": {secondCallDelay: 1, timeBetween: 2 * time.Second}, + "First auth starts first but second finishes first": {firstCallDelay: 3, timeBetween: time.Second}, + "First auth starts first then second starts and first finishes": {firstCallDelay: 2, secondCallDelay: 3, timeBetween: time.Second}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { @@ -703,24 +703,18 @@ func TestConcurrentIsAuthenticated(t *testing.T) { require.NoError(t, err, "Setup: Mkdir should not have returned an error") cfg := &broker.Config{DataDir: dataDir} cfg.SetIssuerURL(defaultProvider.URL) - b := newBrokerForTests(t, *cfg) + mockInfoer := &testutils.MockProviderInfoer{FirstCallDelay: tc.firstCallDelay, SecondCallDelay: tc.secondCallDelay} + b := newBrokerForTests(t, *cfg, mockInfoer) - if tc.firstUser == "" { - tc.firstUser = "user-timeout-0" - } - if tc.secondUser == "" { - tc.secondUser = "user-timeout-1" - } - - firstSession, firstKey := newSessionForTests(t, b, tc.firstUser, "") - tok := generateCachedInfo(t, tc.firstUser, defaultProvider.URL) + firstSession, firstKey := newSessionForTests(t, b, "user1", "") + tok := generateCachedInfo(t, "", defaultProvider.URL) err = b.CacheAuthInfo(firstSession, tok) require.NoError(t, err, "Setup: SaveToken should not have returned an error") err = password.HashAndStorePassword("password", b.PasswordFilepathForSession(firstSession)) require.NoError(t, err, "Setup: HashAndStorePassword should not have returned an error") - secondSession, secondKey := newSessionForTests(t, b, tc.secondUser, "") - tok = generateCachedInfo(t, tc.secondUser, defaultProvider.URL) + secondSession, secondKey := newSessionForTests(t, b, "user2", "") + tok = generateCachedInfo(t, "", defaultProvider.URL) err = b.CacheAuthInfo(secondSession, tok) require.NoError(t, err, "Setup: SaveToken should not have returned an error") err = password.HashAndStorePassword("password", b.PasswordFilepathForSession(secondSession)) @@ -892,7 +886,7 @@ func TestCancelIsAuthenticated(t *testing.T) { cfg := &broker.Config{} cfg.SetIssuerURL(provider.URL) - b := newBrokerForTests(t, *cfg) + b := newBrokerForTests(t, *cfg, nil) sessionID, _ := newSessionForTests(t, b, "", "") updateAuthModes(t, b, sessionID, authmodes.DeviceQr) @@ -916,7 +910,7 @@ func TestEndSession(t *testing.T) { cfg := &broker.Config{} cfg.SetIssuerURL(defaultProvider.URL) - b := newBrokerForTests(t, *cfg) + b := newBrokerForTests(t, *cfg, nil) sessionID, _ := newSessionForTests(t, b, "", "") @@ -974,7 +968,7 @@ func TestUserPreCheck(t *testing.T) { cfg.SetIssuerURL(defaultProvider.URL) cfg.SetHomeBaseDir(tc.homePrefix) cfg.SetAllowedSSHSuffixes(tc.allowedSuffixes) - b := newBrokerForTests(t, *cfg) + b := newBrokerForTests(t, *cfg, nil) got, err := b.UserPreCheck(tc.username) if tc.wantErr { diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index f2f50925f7..c24ec68c4f 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -6,7 +6,6 @@ import ( "crypto/sha512" "crypto/x509" "encoding/base64" - "strings" "testing" "time" @@ -21,11 +20,21 @@ import ( // newBrokerForTests is a helper function to create a new broker for tests with the specified configuration. // // Note that the issuerURL is required in the configuration. -func newBrokerForTests(t *testing.T, cfg broker.Config) (b *broker.Broker) { +func newBrokerForTests(t *testing.T, cfg broker.Config, providerInfoer *testutils.MockProviderInfoer) (b *broker.Broker) { t.Helper() require.NotEmpty(t, cfg.IssuerURL(), "Setup: issuerURL must not be empty") + if providerInfoer == nil { + providerInfoer = &testutils.MockProviderInfoer{} + } + if providerInfoer.Groups == nil { + providerInfoer.Groups = []info.Group{ + {Name: "remote-group", UGID: "12345"}, + {Name: "linux-local-group", UGID: ""}, + } + } + if cfg.DataDir == "" { cfg.DataDir = t.TempDir() } @@ -35,12 +44,7 @@ func newBrokerForTests(t *testing.T, cfg broker.Config) (b *broker.Broker) { b, err := broker.New( cfg, - broker.WithCustomProviderInfo(&testutils.MockProviderInfoer{ - Groups: []info.Group{ - {Name: "remote-group", UGID: "12345"}, - {Name: "linux-local-group", UGID: ""}, - }, - }), + broker.WithCustomProviderInfo(providerInfoer), ) require.NoError(t, err, "Setup: New should not have returned an error") return b @@ -136,11 +140,6 @@ func generateCachedInfo(t *testing.T, preexistentToken, issuer string) *broker.A username = "test-user@email.com" } - // This is to handle delay cases where we need to control the authentication ordering. - if strings.HasPrefix(preexistentToken, "user-timeout-") { - username = preexistentToken - } - idToken := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{ "iss": issuer, "sub": "saved-user-id", @@ -171,11 +170,6 @@ func generateCachedInfo(t *testing.T, preexistentToken, issuer string) *broker.A }, } - // This is to force the broker to query the provider for the user info. - if strings.HasPrefix(preexistentToken, "user-timeout-") { - tok.UserInfo = info.User{} - } - if preexistentToken == "invalid-id" { encodedToken = ".invalid." tok.UserInfo = info.User{} diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user-timeout-0/password b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user1/password similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user-timeout-0/password rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user1/password diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user-timeout-0/token.json b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user1/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user-timeout-0/token.json rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user1/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user-timeout-1/password b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user2/password similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user-timeout-1/password rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user2/password diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user-timeout-1/token.json b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user2/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user-timeout-1/token.json rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user2/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/first_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/first_auth index bd6241b176..2928c3a331 100644 --- a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/first_auth +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/first_auth @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"user-timeout-0","uuid":"saved-user-id","dir":"/home/user-timeout-0","shell":"/usr/bin/bash","gecos":"user-timeout-0","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"saved-remote-group","ugid":"12345"},{"name":"saved-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/second_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/second_auth index 1fee6db9d3..2928c3a331 100644 --- a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/second_auth +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/second_auth @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"user-timeout-1","uuid":"saved-user-id","dir":"/home/user-timeout-1","shell":"/usr/bin/bash","gecos":"user-timeout-1","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"saved-remote-group","ugid":"12345"},{"name":"saved-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user-timeout-1/password b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user1/password similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user-timeout-1/password rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user1/password diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user-timeout-1/token.json b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user1/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user-timeout-1/token.json rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user1/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user-timeout-3/password b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user2/password similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user-timeout-3/password rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user2/password diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user-timeout-3/token.json b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user2/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user-timeout-3/token.json rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user2/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/first_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/first_auth index d4235f545a..2928c3a331 100644 --- a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/first_auth +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/first_auth @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"user-timeout-3","uuid":"saved-user-id","dir":"/home/user-timeout-3","shell":"/usr/bin/bash","gecos":"user-timeout-3","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"saved-remote-group","ugid":"12345"},{"name":"saved-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/second_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/second_auth index 1fee6db9d3..2928c3a331 100644 --- a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/second_auth +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/second_auth @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"user-timeout-1","uuid":"saved-user-id","dir":"/home/user-timeout-1","shell":"/usr/bin/bash","gecos":"user-timeout-1","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"saved-remote-group","ugid":"12345"},{"name":"saved-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user-timeout-2/password b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1/password similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user-timeout-2/password rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1/password diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user-timeout-2/token.json b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user-timeout-2/token.json rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user-timeout-3/password b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2/password similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user-timeout-3/password rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2/password diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user-timeout-3/token.json b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user-timeout-3/token.json rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/first_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/first_auth index e70dc9da68..2928c3a331 100644 --- a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/first_auth +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/first_auth @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"user-timeout-2","uuid":"saved-user-id","dir":"/home/user-timeout-2","shell":"/usr/bin/bash","gecos":"user-timeout-2","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"saved-remote-group","ugid":"12345"},{"name":"saved-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/second_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/second_auth index d4235f545a..2928c3a331 100644 --- a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/second_auth +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/second_auth @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"user-timeout-3","uuid":"saved-user-id","dir":"/home/user-timeout-3","shell":"/usr/bin/bash","gecos":"user-timeout-3","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"saved-remote-group","ugid":"12345"},{"name":"saved-local-group","ugid":""}]}}' err: diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index 98537913c1..a9f27d8110 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -13,8 +13,8 @@ import ( "net/http" "net/http/httptest" "slices" - "strconv" "strings" + "sync" "time" "github.com/coreos/go-oidc/v3/oidc" @@ -295,10 +295,15 @@ func ExpiryDeviceAuthHandler() ProviderHandler { // MockProviderInfoer is a mock that implements the ProviderInfoer interface. type MockProviderInfoer struct { noprovider.NoProvider - Scopes []string - Options []oauth2.AuthCodeOption - Groups []info.Group - GroupsErr bool + Scopes []string + Options []oauth2.AuthCodeOption + Groups []info.Group + GroupsErr bool + FirstCallDelay int + SecondCallDelay int + + numCalls int + numCallsLock sync.Mutex } // CheckTokenScopes checks if the token has the required scopes. @@ -349,14 +354,16 @@ func (p *MockProviderInfoer) GetUserInfo(ctx context.Context, accessToken *oauth return info.User{}, err } - // This is a special case for testing purposes. If the username starts with "user-timeout-", we will delay the - // return for a while to control the authentication order for multiple users. - if strings.HasPrefix(userClaims.Email, "user-timeout") { - d, err := strconv.Atoi(strings.TrimPrefix(userClaims.Email, "user-timeout-")) - if err != nil { - return info.User{}, err - } - time.Sleep(time.Duration(d) * time.Second) + p.numCallsLock.Lock() + numCalls := p.numCalls + p.numCalls++ + p.numCallsLock.Unlock() + + if numCalls == 0 && p.FirstCallDelay > 0 { + time.Sleep(time.Duration(p.FirstCallDelay) * time.Second) + } + if numCalls == 1 && p.SecondCallDelay > 0 { + time.Sleep(time.Duration(p.SecondCallDelay) * time.Second) } return info.NewUser( From deab5ce0c61133235854e0525051cd0834d9c8e2 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 17 Sep 2024 18:22:14 +0200 Subject: [PATCH 0193/1670] refactor: Make the tests even less confusing Instead of relying on the invalid token causing `fetchUserInfo` to fail, we now make the `GetUserInfo` call fail in the ProviderInfoer mock. --- internal/broker/broker_test.go | 14 ++++++++------ .../provider_url/test-user@email.com/password | 0 .../provider_url/test-user@email.com/token.json | 0 .../first_call | 0 internal/testutils/provider.go | 17 +++++++++++------ 5 files changed, 19 insertions(+), 12 deletions(-) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_password_and_can_not_fetch_user_info => error_when_token_is_expired_and_can_not_fetch_user_info}/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_password_and_can_not_fetch_user_info => error_when_token_is_expired_and_can_not_fetch_user_info}/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_password_and_can_not_fetch_user_info => error_when_token_is_expired_and_can_not_fetch_user_info}/first_call (100%) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index fc733c922d..5df8164b60 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -397,10 +397,11 @@ func TestIsAuthenticated(t *testing.T) { sessionMode string username string - firstMode string - firstChallenge string - firstAuthInfo map[string]any - badFirstKey bool + firstMode string + firstChallenge string + firstAuthInfo map[string]any + badFirstKey bool + getUserInfoFails bool customHandlers map[string]testutils.ProviderHandler address string @@ -464,7 +465,7 @@ func TestIsAuthenticated(t *testing.T) { "/token": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, }, - "Error when mode is password and can not fetch user info": {firstMode: authmodes.Password, preexistentToken: "invalid-id"}, + "Error when token is expired and can not fetch user info": {firstMode: authmodes.Password, preexistentToken: "expired", getUserInfoFails: true}, "Error when mode is qrcode and response is invalid": {firstAuthInfo: map[string]any{"response": "not a valid response"}}, "Error when mode is qrcode and link expires": { @@ -543,7 +544,8 @@ func TestIsAuthenticated(t *testing.T) { cfg := &broker.Config{DataDir: dataDir} cfg.SetIssuerURL(provider.URL) - b := newBrokerForTests(t, *cfg, nil) + mockInfoer := &testutils.MockProviderInfoer{GetUserInfoFails: tc.getUserInfoFails} + b := newBrokerForTests(t, *cfg, mockInfoer) sessionID, key := newSessionForTests(t, b, tc.username, tc.sessionMode) if tc.preexistentToken != "" { diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_token_is_expired_and_can_not_fetch_user_info/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_token_is_expired_and_can_not_fetch_user_info/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_token_is_expired_and_can_not_fetch_user_info/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_token_is_expired_and_can_not_fetch_user_info/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_token_is_expired_and_can_not_fetch_user_info/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_can_not_fetch_user_info/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_token_is_expired_and_can_not_fetch_user_info/first_call diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index a9f27d8110..5c9bdb0a8e 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -295,12 +295,13 @@ func ExpiryDeviceAuthHandler() ProviderHandler { // MockProviderInfoer is a mock that implements the ProviderInfoer interface. type MockProviderInfoer struct { noprovider.NoProvider - Scopes []string - Options []oauth2.AuthCodeOption - Groups []info.Group - GroupsErr bool - FirstCallDelay int - SecondCallDelay int + Scopes []string + Options []oauth2.AuthCodeOption + Groups []info.Group + GroupsErr bool + FirstCallDelay int + SecondCallDelay int + GetUserInfoFails bool numCalls int numCallsLock sync.Mutex @@ -344,6 +345,10 @@ func (p *MockProviderInfoer) AuthOptions() []oauth2.AuthCodeOption { // GetUserInfo is a no-op when no specific provider is in use. func (p *MockProviderInfoer) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken) (info.User, error) { + if p.GetUserInfoFails { + return info.User{}, errors.New("error requested in the mock") + } + userClaims, err := p.userClaims(idToken) if err != nil { return info.User{}, err From f56f2e8d7da08e720eb6ef470972880a1c241562 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 10 Sep 2024 11:45:17 +0200 Subject: [PATCH 0194/1670] refactor: Extract token package --- cmd/authd-oidc/daemon/daemon.go | 2 +- internal/broker/broker.go | 195 ++++-------------- internal/broker/broker_test.go | 17 +- internal/broker/encrypt.go | 32 --- internal/broker/export_test.go | 36 +--- internal/broker/helper_test.go | 54 +++-- .../provider_url/test-user@email.com/password | 0 .../test-user@email.com/token.json | 0 .../first_call | 0 .../first_call | 4 +- .../first_call | 2 +- .../first_call | 4 +- .../first_call | 4 +- .../first_call | 4 +- internal/token/export_test.go | 5 + internal/token/migration.go | 148 +++++++++++++ internal/token/migration_test.go | 161 +++++++++++++++ internal/token/token.go | 70 +++++++ internal/token/token_test.go | 128 ++++++++++++ 19 files changed, 616 insertions(+), 250 deletions(-) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_token_is_expired_and_can_not_fetch_user_info => error_when_existing_token_has_no_user_info_and_fetching_user_info_fails}/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_token_is_expired_and_can_not_fetch_user_info => error_when_existing_token_has_no_user_info_and_fetching_user_info_fails}/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_token_is_expired_and_can_not_fetch_user_info => error_when_existing_token_has_no_user_info_and_fetching_user_info_fails}/first_call (100%) create mode 100644 internal/token/export_test.go create mode 100644 internal/token/migration.go create mode 100644 internal/token/migration_test.go create mode 100644 internal/token/token.go create mode 100644 internal/token/token_test.go diff --git a/cmd/authd-oidc/daemon/daemon.go b/cmd/authd-oidc/daemon/daemon.go index 966a0a9526..42850374f3 100644 --- a/cmd/authd-oidc/daemon/daemon.go +++ b/cmd/authd-oidc/daemon/daemon.go @@ -53,7 +53,6 @@ func New(name string) *App { // Command parsing has been successful. Returns to not print usage anymore. a.rootCmd.SilenceUsage = true - // Set config defaults // Before version 0.2, we used to store the tokens encrypted // in a different directory. For backward compatibility, we // try to use the encrypted tokens from the old directory @@ -66,6 +65,7 @@ func New(name string) *App { dataDir = snapData configDir = snapData } + // Set config defaults a.config = daemonConfig{ Paths: systemPaths{ BrokerConf: filepath.Join(configDir, "broker.conf"), diff --git a/internal/broker/broker.go b/internal/broker/broker.go index e8eaf94cce..26c6c9f24f 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -11,7 +11,6 @@ import ( "errors" "fmt" "log/slog" - "os" "path/filepath" "slices" "strings" @@ -27,6 +26,7 @@ import ( "github.com/ubuntu/authd-oidc-brokers/internal/providers" providerErrors "github.com/ubuntu/authd-oidc-brokers/internal/providers/errors" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" + "github.com/ubuntu/authd-oidc-brokers/internal/token" "github.com/ubuntu/decorate" "golang.org/x/oauth2" ) @@ -480,7 +480,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo return AuthRetry, errorMessage{Message: "could not decode challenge"} } - var authInfo authCachedInfo + var authInfo token.AuthCachedInfo switch session.selectedMode { case authmodes.Device, authmodes.DeviceQr: response, ok := session.authInfo["response"].(*oauth2.DeviceAuthResponse) @@ -510,7 +510,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo return AuthDenied, errorMessage{Message: "could not get ID token"} } - authInfo = b.newAuthCachedInfo(t, rawIDToken) + authInfo = token.NewAuthCachedInfo(t, rawIDToken, b.providerInfo) authInfo.UserInfo, err = b.fetchUserInfo(ctx, session, &authInfo) if err != nil { slog.Error(err.Error()) @@ -521,51 +521,53 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo return AuthNext, nil case authmodes.Password: - var useOldEncryptedToken bool - exists, err := fileutils.FileExists(session.passwordPath) + useOldEncryptedToken, err := token.UseOldEncryptedToken(session.tokenPath, session.passwordPath, session.oldEncryptedTokenPath) if err != nil { slog.Error(err.Error()) return AuthDenied, errorMessage{Message: "could not check password file"} } - if !exists { - // For backwards compatibility, we also check the old encrypted token path. - exists, err = fileutils.FileExists(session.oldEncryptedTokenPath) + + if useOldEncryptedToken { + authInfo, err = token.LoadOldEncryptedAuthInfo(session.oldEncryptedTokenPath, challenge) if err != nil { slog.Error(err.Error()) - return AuthDenied, errorMessage{Message: "could not check old encrypted token path"} - } - if !exists { - return AuthDenied, errorMessage{Message: "password file does not exist"} + return AuthDenied, errorMessage{Message: "could not load encrypted token"} } - useOldEncryptedToken = true - } - if !useOldEncryptedToken { + // We were able to decrypt the old token with the password, so we can now hash and store the password in the + // new format. + if err = password.HashAndStorePassword(challenge, session.passwordPath); err != nil { + slog.Error(err.Error()) + return AuthDenied, errorMessage{Message: "could not store password"} + } + } else { ok, err := password.CheckPassword(challenge, session.passwordPath) if err != nil { slog.Error(err.Error()) - return AuthRetry, errorMessage{Message: "could not check password"} + return AuthDenied, errorMessage{Message: "could not check password"} } if !ok { return AuthRetry, errorMessage{Message: "incorrect password"} } - } - authInfo, err = b.loadAuthInfo(ctx, session, challenge, useOldEncryptedToken) - if err != nil { - slog.Error(err.Error()) - return AuthRetry, errorMessage{Message: "could not load cached info"} + authInfo, err = token.LoadAuthInfo(session.tokenPath) + if err != nil { + slog.Error(err.Error()) + return AuthDenied, errorMessage{Message: "could not load stored token"} + } } - if useOldEncryptedToken { - // We were able to decrypt the old token with the password, so we can now hash and store the password in the - // new format. - if err = password.HashAndStorePassword(challenge, session.passwordPath); err != nil { + // Refresh the token if it has expired and we're online. Ideally, we should always refresh it when we're online, + // but the oauth2.TokenSource implementation only refreshes the token when it's expired. + if !authInfo.Token.Valid() && !session.isOffline { + authInfo, err = b.refreshToken(ctx, session.authCfg.oauth, authInfo) + if err != nil { slog.Error(err.Error()) - return AuthDenied, errorMessage{Message: "could not store password"} + return AuthDenied, errorMessage{Message: "could not refresh token"} } } + // Try to refresh the user info userInfo, err := b.fetchUserInfo(ctx, session, &authInfo) if err != nil && authInfo.UserInfo.Name == "" { // We don't have a valid user info, so we can't proceed. @@ -591,7 +593,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo var ok bool // This mode must always come after a authentication mode, so it has to have an auth_info. - authInfo, ok = session.authInfo["auth_info"].(authCachedInfo) + authInfo, ok = session.authInfo["auth_info"].(token.AuthCachedInfo) if !ok { slog.Error("could not get required information") return AuthDenied, errorMessage{Message: "could not get required information"} @@ -607,14 +609,14 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo return AuthGranted, userInfoMessage{UserInfo: authInfo.UserInfo} } - if err := b.cacheAuthInfo(session, authInfo); err != nil { + if err := token.CacheAuthInfo(session.tokenPath, authInfo); err != nil { slog.Error(err.Error()) return AuthDenied, errorMessage{Message: "could not cache user info"} } // At this point we successfully stored the hashed password and a new token, so we can now safely remove any old // encrypted token. - cleanupOldEncryptedToken(session.oldEncryptedTokenPath) + token.CleanupOldEncryptedToken(session.oldEncryptedTokenPath) return AuthGranted, userInfoMessage{UserInfo: authInfo.UserInfo} } @@ -723,94 +725,28 @@ func (b *Broker) updateSession(sessionID string, session sessionInfo) error { return nil } -// authCachedInfo represents the token that will be saved on disk for offline authentication. -type authCachedInfo struct { - Token *oauth2.Token - ExtraFields map[string]interface{} - RawIDToken string - UserInfo info.User -} - -func (b *Broker) newAuthCachedInfo(t *oauth2.Token, idToken string) authCachedInfo { - return authCachedInfo{ - Token: t, - RawIDToken: idToken, - ExtraFields: b.providerInfo.GetExtraFields(t), - } -} - -// cacheAuthInfo saves the token to the file. -func (b *Broker) cacheAuthInfo(session *sessionInfo, authInfo authCachedInfo) (err error) { - jsonData, err := json.Marshal(authInfo) - if err != nil { - return fmt.Errorf("could not marshal token: %v", err) - } - - // Create issuer specific cache directory if it doesn't exist. - if err = os.MkdirAll(filepath.Dir(session.tokenPath), 0700); err != nil { - return fmt.Errorf("could not create token directory: %v", err) - } - - if err = os.WriteFile(session.tokenPath, jsonData, 0600); err != nil { - return fmt.Errorf("could not save token: %v", err) - } - - return nil -} - -// loadAuthInfo reads the token from the file and tries to refresh it if it's expired. -func (b *Broker) loadAuthInfo(ctx context.Context, session *sessionInfo, password string, useOldEncryptedToken bool) (loadedInfo authCachedInfo, err error) { - var jsonData []byte - if useOldEncryptedToken { - encryptedData, err := os.ReadFile(session.oldEncryptedTokenPath) - if err != nil { - return authCachedInfo{}, fmt.Errorf("could not read old encrypted token: %v", err) - } - jsonData, err = decrypt(encryptedData, []byte(password)) - if err != nil { - return authCachedInfo{}, fmt.Errorf("could not decrypt token: %v", err) - } - } else { - jsonData, err = os.ReadFile(session.tokenPath) - if err != nil { - return authCachedInfo{}, fmt.Errorf("could not read token: %v", err) - } - } - - var cachedInfo authCachedInfo - if err := json.Unmarshal(jsonData, &cachedInfo); err != nil { - return authCachedInfo{}, fmt.Errorf("could not unmarshal token: %v", err) - } - - // Set the extra fields of the token. - if cachedInfo.ExtraFields != nil { - cachedInfo.Token = cachedInfo.Token.WithExtra(cachedInfo.ExtraFields) - } - - // If the token is still valid, we return it. Ideally, we would refresh it online, but the TokenSource API also uses - // this logic to decide whether the token needs refreshing, so we should run it early to control the returned values. - if cachedInfo.Token.Valid() || session.isOffline { - return cachedInfo, nil - } - - // Tries to refresh the access token. If the service is unavailable, we allow authentication. +// refreshToken refreshes the OAuth2 token and returns the updated AuthCachedInfo. +func (b *Broker) refreshToken(ctx context.Context, oauth2Config oauth2.Config, oldToken token.AuthCachedInfo) (token.AuthCachedInfo, error) { timeoutCtx, cancel := context.WithTimeout(ctx, maxRequestDuration) defer cancel() - tok, err := session.authCfg.oauth.TokenSource(timeoutCtx, cachedInfo.Token).Token() + oauthToken, err := oauth2Config.TokenSource(timeoutCtx, oldToken.Token).Token() if err != nil { - return authCachedInfo{}, fmt.Errorf("could not refresh token: %v", err) + return token.AuthCachedInfo{}, err } - // If the ID token was refreshed, we overwrite the cached one. - refreshedIDToken, ok := tok.Extra("id_token").(string) + // Update the raw ID token + rawIDToken, ok := oauthToken.Extra("id_token").(string) if !ok { - refreshedIDToken = cachedInfo.RawIDToken + slog.Debug("refreshed token does not contain an ID token, keeping the old one") + rawIDToken = oldToken.RawIDToken } - return b.newAuthCachedInfo(tok, refreshedIDToken), nil + t := token.NewAuthCachedInfo(oauthToken, rawIDToken, b.providerInfo) + t.UserInfo = oldToken.UserInfo + return t, nil } -func (b *Broker) fetchUserInfo(ctx context.Context, session *sessionInfo, t *authCachedInfo) (userInfo info.User, err error) { +func (b *Broker) fetchUserInfo(ctx context.Context, session *sessionInfo, t *token.AuthCachedInfo) (userInfo info.User, err error) { if session.isOffline { return info.User{}, errors.New("session is in offline mode") } @@ -859,48 +795,3 @@ func errorMessageForDisplay(err error, fallback string) errorMessage { } return errorMessage{Message: fallback} } - -func cleanupOldEncryptedToken(path string) { - exists, err := fileutils.FileExists(path) - if err != nil { - slog.Warn(fmt.Sprintf("Failed to check if old encrypted token exists %s: %v", path, err)) - } - if !exists { - return - } - - if err := os.Remove(path); err != nil { - slog.Warn(fmt.Sprintf("Failed to remove old encrypted token %s: %v", path, err)) - return - } - - // Also remove the parent directory and the parent's parent directory if they are empty. The directory structure was: - // $SNAP_DATA/cache/$ISSUER/$USERNAME.cache - // so we try to remove the $SNAP_DATA/cache/$ISSUER directory and the $SNAP_DATA/cache directory. - - // Check if the parent directory is empty. - empty, err := fileutils.IsDirEmpty(filepath.Dir(path)) - if err != nil { - slog.Warn(fmt.Sprintf("Failed to check if old encrypted token parent directory %s is empty: %v", filepath.Dir(path), err)) - return - } - if !empty { - return - } - if err := os.Remove(filepath.Dir(path)); err != nil { - slog.Warn(fmt.Sprintf("Failed to remove old encrypted token directory %s: %v", filepath.Dir(path), err)) - } - - // Check if the parent's parent directory is empty. - empty, err = fileutils.IsDirEmpty(filepath.Dir(filepath.Dir(path))) - if err != nil { - slog.Warn(fmt.Sprintf("Failed to check if old encrypted token parent directory %s is empty: %v", filepath.Dir(filepath.Dir(path)), err)) - return - } - if !empty { - return - } - if err := os.Remove(filepath.Dir(filepath.Dir(path))); err != nil { - slog.Warn(fmt.Sprintf("Failed to remove old encrypted token parent directory %s: %v", filepath.Dir(filepath.Dir(path)), err)) - } -} diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 5df8164b60..d9a71e1ddf 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -17,6 +17,7 @@ import ( "github.com/ubuntu/authd-oidc-brokers/internal/password" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "github.com/ubuntu/authd-oidc-brokers/internal/testutils" + "github.com/ubuntu/authd-oidc-brokers/internal/token" "golang.org/x/oauth2" "gopkg.in/yaml.v3" ) @@ -465,7 +466,7 @@ func TestIsAuthenticated(t *testing.T) { "/token": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, }, - "Error when token is expired and can not fetch user info": {firstMode: authmodes.Password, preexistentToken: "expired", getUserInfoFails: true}, + "Error when existing token has no user info and fetching user info fails": {firstMode: authmodes.Password, preexistentToken: "no-user-info", getUserInfoFails: true}, "Error when mode is qrcode and response is invalid": {firstAuthInfo: map[string]any{"response": "not a valid response"}}, "Error when mode is qrcode and link expires": { @@ -549,9 +550,7 @@ func TestIsAuthenticated(t *testing.T) { sessionID, key := newSessionForTests(t, b, tc.username, tc.sessionMode) if tc.preexistentToken != "" { - tok := generateCachedInfo(t, tc.preexistentToken, provider.URL) - err := b.CacheAuthInfo(sessionID, tok) - require.NoError(t, err, "Setup: SaveToken should not have returned an error") + generateAndStoreCachedInfo(t, tc.preexistentToken, provider.URL, b.TokenPathForSession(sessionID)) err = password.HashAndStorePassword(correctPassword, b.PasswordFilepathForSession(sessionID)) require.NoError(t, err, "Setup: HashAndStorePassword should not have returned an error") } @@ -709,16 +708,12 @@ func TestConcurrentIsAuthenticated(t *testing.T) { b := newBrokerForTests(t, *cfg, mockInfoer) firstSession, firstKey := newSessionForTests(t, b, "user1", "") - tok := generateCachedInfo(t, "", defaultProvider.URL) - err = b.CacheAuthInfo(firstSession, tok) - require.NoError(t, err, "Setup: SaveToken should not have returned an error") + generateAndStoreCachedInfo(t, "", defaultProvider.URL, b.TokenPathForSession(firstSession)) err = password.HashAndStorePassword("password", b.PasswordFilepathForSession(firstSession)) require.NoError(t, err, "Setup: HashAndStorePassword should not have returned an error") secondSession, secondKey := newSessionForTests(t, b, "user2", "") - tok = generateCachedInfo(t, "", defaultProvider.URL) - err = b.CacheAuthInfo(secondSession, tok) - require.NoError(t, err, "Setup: SaveToken should not have returned an error") + generateAndStoreCachedInfo(t, "", defaultProvider.URL, b.TokenPathForSession(secondSession)) err = password.HashAndStorePassword("password", b.PasswordFilepathForSession(secondSession)) require.NoError(t, err, "Setup: HashAndStorePassword should not have returned an error") @@ -864,7 +859,7 @@ func TestFetchUserInfo(t *testing.T) { cachedInfo := generateCachedInfo(t, tc.userToken, defaultProvider.URL) if cachedInfo == nil { - cachedInfo = &broker.AuthCachedInfo{} + cachedInfo = &token.AuthCachedInfo{} } got, err := b.FetchUserInfo(sessionID, cachedInfo) diff --git a/internal/broker/encrypt.go b/internal/broker/encrypt.go index b7e11f0d9e..56eb8b4bd2 100644 --- a/internal/broker/encrypt.go +++ b/internal/broker/encrypt.go @@ -1,46 +1,14 @@ package broker import ( - "crypto/aes" - "crypto/cipher" "crypto/rsa" "crypto/sha512" "encoding/base64" "errors" "fmt" "log/slog" - - "golang.org/x/crypto/scrypt" ) -const saltLen = 32 - -func decrypt(ciphered, key []byte) ([]byte, error) { - salt, data := ciphered[len(ciphered)-saltLen:], ciphered[:len(ciphered)-saltLen] - - derivedKey, err := scrypt.Key(key, salt, 32768, 8, 1, 32) - if err != nil { - return nil, err - } - - block, err := aes.NewCipher(derivedKey) - if err != nil { - return nil, err - } - - gcm, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - - decrypted, err := gcm.Open(nil, data[:gcm.NonceSize()], data[gcm.NonceSize():], nil) - if err != nil { - return nil, err - } - - return decrypted, nil -} - // decodeRawChallenge extract the base64 challenge and try to decrypt it with the private key. func decodeRawChallenge(priv *rsa.PrivateKey, rawChallenge string) (decoded string, err error) { defer func() { diff --git a/internal/broker/export_test.go b/internal/broker/export_test.go index 601cd3b3f3..6bfc49a24d 100644 --- a/internal/broker/export_test.go +++ b/internal/broker/export_test.go @@ -2,11 +2,9 @@ package broker import ( "context" - "fmt" - "os" - "path/filepath" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" + tokenPkg "github.com/ubuntu/authd-oidc-brokers/internal/token" ) func (cfg *Config) SetClientID(clientID string) { @@ -116,42 +114,14 @@ func (b *Broker) SetAvailableMode(sessionID, mode string) error { return b.updateSession(sessionID, s) } -type AuthCachedInfo = authCachedInfo - -// CacheAuthInfo exposes the broker's cacheAuthInfo method for tests. -func (b *Broker) CacheAuthInfo(sessionID string, token *authCachedInfo) error { - s, err := b.getSession(sessionID) - if err != nil { - return err - } - - if token == nil { - return writeTrashToken(s.tokenPath) - } - - return b.cacheAuthInfo(&s, *token) -} - -func writeTrashToken(path string) error { - var err error - content := []byte("This is a trash token that is not valid for authentication") - - // Create issuer specific cache directory if it doesn't exist. - if err = os.MkdirAll(filepath.Dir(path), 0700); err != nil { - return fmt.Errorf("could not create token directory: %v", err) - } - - return os.WriteFile(path, content, 0600) -} - // FetchUserInfo exposes the broker's fetchUserInfo method for tests. -func (b *Broker) FetchUserInfo(sessionID string, cachedInfo *authCachedInfo) (info.User, error) { +func (b *Broker) FetchUserInfo(sessionID string, token *tokenPkg.AuthCachedInfo) (info.User, error) { s, err := b.getSession(sessionID) if err != nil { return info.User{}, err } - uInfo, err := b.fetchUserInfo(context.TODO(), &s, cachedInfo) + uInfo, err := b.fetchUserInfo(context.TODO(), &s, token) if err != nil { return info.User{}, err } diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index c24ec68c4f..5a05a5d800 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -6,6 +6,8 @@ import ( "crypto/sha512" "crypto/x509" "encoding/base64" + "os" + "path/filepath" "testing" "time" @@ -14,6 +16,7 @@ import ( "github.com/ubuntu/authd-oidc-brokers/internal/broker" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "github.com/ubuntu/authd-oidc-brokers/internal/testutils" + "github.com/ubuntu/authd-oidc-brokers/internal/token" "golang.org/x/oauth2" ) @@ -100,7 +103,7 @@ func updateAuthModes(t *testing.T, b *broker.Broker, sessionID, selectedMode str require.NoError(t, err, "Setup: SelectAuthenticationMode should not have returned an error") } -var testTokens = map[string]broker.AuthCachedInfo{ +var testTokens = map[string]token.AuthCachedInfo{ "valid": { Token: &oauth2.Token{ AccessToken: "accesstoken", @@ -123,7 +126,19 @@ var testTokens = map[string]broker.AuthCachedInfo{ }, } -func generateCachedInfo(t *testing.T, preexistentToken, issuer string) *broker.AuthCachedInfo { +func generateAndStoreCachedInfo(t *testing.T, preexistentToken, issuer, path string) { + t.Helper() + + tok := generateCachedInfo(t, preexistentToken, issuer) + if tok == nil { + writeTrashToken(t, path) + return + } + err := token.CacheAuthInfo(path, *tok) + require.NoError(t, err, "Setup: storing token should not have failed") +} + +func generateCachedInfo(t *testing.T, preexistentToken, issuer string) *token.AuthCachedInfo { t.Helper() if preexistentToken == "invalid" { @@ -158,16 +173,18 @@ func generateCachedInfo(t *testing.T, preexistentToken, issuer string) *broker.A tok = testTokens["valid"] } - tok.UserInfo = info.User{ - Name: username, - UUID: "saved-user-id", - Home: "/home/" + username, - Gecos: username, - Shell: "/usr/bin/bash", - Groups: []info.Group{ - {Name: "saved-remote-group", UGID: "12345"}, - {Name: "saved-local-group", UGID: ""}, - }, + if preexistentToken != "no-user-info" { + tok.UserInfo = info.User{ + Name: username, + UUID: "saved-user-id", + Home: "/home/" + username, + Gecos: username, + Shell: "/usr/bin/bash", + Groups: []info.Group{ + {Name: "saved-remote-group", UGID: "12345"}, + {Name: "saved-local-group", UGID: ""}, + }, + } } if preexistentToken == "invalid-id" { @@ -180,3 +197,16 @@ func generateCachedInfo(t *testing.T, preexistentToken, issuer string) *broker.A return &tok } + +func writeTrashToken(t *testing.T, path string) { + t.Helper() + + content := []byte("This is a trash token that is not valid for authentication") + + // Create issuer specific cache directory if it doesn't exist. + err := os.MkdirAll(filepath.Dir(path), 0700) + require.NoError(t, err, "Setup: creating token directory should not have failed") + + err = os.WriteFile(path, content, 0600) + require.NoError(t, err, "Setup: writing trash token should not have failed") +} diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_token_is_expired_and_can_not_fetch_user_info/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_token_is_expired_and_can_not_fetch_user_info/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_token_is_expired_and_can_not_fetch_user_info/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_token_is_expired_and_can_not_fetch_user_info/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_token_is_expired_and_can_not_fetch_user_info/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_token_is_expired_and_can_not_fetch_user_info/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/first_call index 69d73127bc..2bc8be9a07 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/first_call @@ -1,3 +1,3 @@ -access: retry -data: '{"message":"authentication failure: could not load cached info"}' +access: denied +data: '{"message":"authentication failure: could not refresh token"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call index 8e7a31d8b8..98c9e5d2d9 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call @@ -1,3 +1,3 @@ access: denied -data: '{"message":"authentication failure: password file does not exist"}' +data: '{"message":"authentication failure: could not check password file"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/first_call index 69d73127bc..9e5b4078ec 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/first_call @@ -1,3 +1,3 @@ -access: retry -data: '{"message":"authentication failure: could not load cached info"}' +access: denied +data: '{"message":"authentication failure: could not load stored token"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/first_call index 69d73127bc..2bc8be9a07 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/first_call @@ -1,3 +1,3 @@ -access: retry -data: '{"message":"authentication failure: could not load cached info"}' +access: denied +data: '{"message":"authentication failure: could not refresh token"}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/first_call index 69d73127bc..2bc8be9a07 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/first_call @@ -1,3 +1,3 @@ -access: retry -data: '{"message":"authentication failure: could not load cached info"}' +access: denied +data: '{"message":"authentication failure: could not refresh token"}' err: diff --git a/internal/token/export_test.go b/internal/token/export_test.go new file mode 100644 index 0000000000..6f26c85418 --- /dev/null +++ b/internal/token/export_test.go @@ -0,0 +1,5 @@ +package token + +func Saltlen() int { + return saltLen +} diff --git a/internal/token/migration.go b/internal/token/migration.go new file mode 100644 index 0000000000..04fc0b3641 --- /dev/null +++ b/internal/token/migration.go @@ -0,0 +1,148 @@ +package token + +import ( + "crypto/aes" + "crypto/cipher" + "encoding/json" + "fmt" + "log/slog" + "os" + "path/filepath" + + "github.com/ubuntu/authd-oidc-brokers/internal/fileutils" + "golang.org/x/crypto/scrypt" +) + +const saltLen = 32 + +// UseOldEncryptedToken checks if the password file or the old encrypted token file exists. It returns false if the +// password file exists, true if only the old encrypted token file exists, and an error if neither exists. +func UseOldEncryptedToken(passwordPath, tokenPath, oldEncryptedTokenPath string) (bool, error) { + passwordExists, err := fileutils.FileExists(passwordPath) + if err != nil { + return false, fmt.Errorf("could not check password file: %w", err) + } + tokenExists, err := fileutils.FileExists(tokenPath) + if err != nil { + return false, fmt.Errorf("could not check token file: %w", err) + } + if passwordExists && tokenExists { + return false, nil + } + + oldEncryptedTokenExists, err := fileutils.FileExists(oldEncryptedTokenPath) + if err != nil { + return false, fmt.Errorf("could not check old encrypted token file: %w", err) + } + if !oldEncryptedTokenExists { + if !passwordExists { + // We mention the password file in the error message instead of the old encrypted token file, because the latter + // is only used for backward compatibility, so if it doesn't exist, the missing password file is the real issue. + return false, fmt.Errorf("password file %q does not exist", passwordPath) + } + // We only get here if the password file exists and the token file does not exist. + return false, fmt.Errorf("token file %q does not exist", tokenPath) + } + + return true, nil +} + +// LoadOldEncryptedAuthInfo reads the token in the old encrypted format from the given path and decrypts it using the +// given password. It's used for backward compatibility. +func LoadOldEncryptedAuthInfo(path, password string) (AuthCachedInfo, error) { + encryptedData, err := os.ReadFile(path) + if err != nil { + return AuthCachedInfo{}, fmt.Errorf("could not read token: %v", err) + } + jsonData, err := decrypt(encryptedData, []byte(password)) + if err != nil { + return AuthCachedInfo{}, fmt.Errorf("could not decrypt token: %v", err) + } + + var cachedInfo AuthCachedInfo + if err := json.Unmarshal(jsonData, &cachedInfo); err != nil { + return AuthCachedInfo{}, fmt.Errorf("could not unmarshal token: %v", err) + } + + return cachedInfo, nil +} + +func decrypt(blob, key []byte) ([]byte, error) { + if len(blob) < saltLen { + return nil, fmt.Errorf("blob is too short to contain a valid salt") + } + + salt, data := blob[len(blob)-saltLen:], blob[:len(blob)-saltLen] + + derivedKey, err := scrypt.Key(key, salt, 32768, 8, 1, 32) + if err != nil { + return nil, err + } + + block, err := aes.NewCipher(derivedKey) + if err != nil { + return nil, err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + if len(data) < gcm.NonceSize() { + return nil, fmt.Errorf("data is too short to contain a valid nonce") + } + + decrypted, err := gcm.Open(nil, data[:gcm.NonceSize()], data[gcm.NonceSize():], nil) + if err != nil { + return nil, err + } + + return decrypted, nil +} + +// CleanupOldEncryptedToken removes the old encrypted token file at the given path and its parent directories if they are empty. +func CleanupOldEncryptedToken(path string) { + exists, err := fileutils.FileExists(path) + if err != nil { + slog.Warn(fmt.Sprintf("Failed to check if old encrypted token exists %s: %v", path, err)) + } + if !exists { + return + } + + if err := os.Remove(path); err != nil { + slog.Warn(fmt.Sprintf("Failed to remove old encrypted token %s: %v", path, err)) + return + } + + // Also remove the parent directory and the parent's parent directory if they are empty. The directory structure was: + // $SNAP_DATA/cache/$ISSUER/$USERNAME.cache + // so we try to remove the $SNAP_DATA/cache/$ISSUER directory and the $SNAP_DATA/cache directory. + + // Check if the parent directory is empty. + empty, err := fileutils.IsDirEmpty(filepath.Dir(path)) + if err != nil { + slog.Warn(fmt.Sprintf("Failed to check if old encrypted token parent directory %s is empty: %v", filepath.Dir(path), err)) + return + } + if !empty { + return + } + if err := os.Remove(filepath.Dir(path)); err != nil { + slog.Warn(fmt.Sprintf("Failed to remove old encrypted token directory %s: %v", filepath.Dir(path), err)) + } + + // Check if the parent's parent directory is empty. + empty, err = fileutils.IsDirEmpty(filepath.Dir(filepath.Dir(path))) + if err != nil { + slog.Warn(fmt.Sprintf("Failed to check if old encrypted token parent directory %s is empty: %v", filepath.Dir(filepath.Dir(path)), err)) + return + } + if !empty { + return + } + if err := os.Remove(filepath.Dir(filepath.Dir(path))); err != nil { + slog.Warn(fmt.Sprintf("Failed to remove old encrypted token parent directory %s: %v", filepath.Dir(filepath.Dir(path)), err)) + } +} diff --git a/internal/token/migration_test.go b/internal/token/migration_test.go new file mode 100644 index 0000000000..b81f388390 --- /dev/null +++ b/internal/token/migration_test.go @@ -0,0 +1,161 @@ +package token_test + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/json" + "fmt" + "io" + "os" + "testing" + + "github.com/stretchr/testify/require" + "github.com/ubuntu/authd-oidc-brokers/internal/token" + "golang.org/x/crypto/scrypt" +) + +func TestUseOldEncryptedToken(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + passwordFileExists bool + newTokenFileExists bool + oldEncryptedTokenFileExists bool + + wantRet bool + wantError bool + }{ + "Success when both the password file and the new token file exist": {passwordFileExists: true, newTokenFileExists: true, wantRet: false}, + "Success when old encrypted token file exists": {oldEncryptedTokenFileExists: true, wantRet: true, wantError: false}, + + "Error if only the password file exists": {passwordFileExists: true, wantRet: false, wantError: true}, + "Error if only the new token file exists": {newTokenFileExists: true, wantRet: false, wantError: true}, + "Error if neither the password file nor the old encrypted token file exists": {wantError: true}, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + passwordPath := t.TempDir() + "/password" + tokenPath := t.TempDir() + "/token" + oldEncryptedTokenPath := t.TempDir() + "/oldtoken" + + if tc.passwordFileExists { + err := os.WriteFile(passwordPath, []byte("password"), 0600) + require.NoError(t, err, "WriteFile should not return an error") + } + if tc.newTokenFileExists { + err := os.WriteFile(tokenPath, []byte("token"), 0600) + require.NoError(t, err, "WriteFile should not return an error") + } + if tc.oldEncryptedTokenFileExists { + err := os.WriteFile(oldEncryptedTokenPath, []byte("encryptedtoken"), 0600) + require.NoError(t, err, "WriteFile should not return an error") + } + + got, err := token.UseOldEncryptedToken(passwordPath, tokenPath, oldEncryptedTokenPath) + if tc.wantError { + require.Error(t, err, "UseOldEncryptedToken should return an error") + return + } + require.NoError(t, err, "UseOldEncryptedToken should not return an error") + require.Equal(t, tc.wantRet, got, "UseOldEncryptedToken should return the expected value") + }) + } +} + +func TestLoadOldEncryptedAuthInfo(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + noOldToken bool + invalidData bool + incorrectPassword bool + + wantToken token.AuthCachedInfo + wantError bool + }{ + "Successfully load old encrypted token": {wantToken: testToken, wantError: false}, + "Error when file does not exist": {noOldToken: true, wantError: true}, + "Error when file contains invalid data": {invalidData: true, wantError: true}, + "Error when password is incorrect": {incorrectPassword: true, wantError: true}, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + tokenPath := t.TempDir() + "/oldtoken" + + enteredPassword := "password" + oldPassword := enteredPassword + if tc.incorrectPassword { + oldPassword = "wrongpassword" + } + + if !tc.noOldToken { + jsonData, err := json.Marshal(testToken) + require.NoError(t, err, "Marshal should not return an error") + encrypted, err := encrypt(jsonData, []byte(oldPassword)) + require.NoError(t, err, "encrypt should not return an error") + err = os.WriteFile(tokenPath, encrypted, 0600) + require.NoError(t, err, "WriteFile should not return an error") + } + + if tc.invalidData { + err := os.WriteFile(tokenPath, []byte("invalid data"), 0600) + require.NoError(t, err, "WriteFile should not return an error") + } + + got, err := token.LoadOldEncryptedAuthInfo(tokenPath, enteredPassword) + if tc.wantError { + require.Error(t, err, "LoadOldEncryptedAuthInfo should return an error") + return + } + require.NoError(t, err, "LoadOldEncryptedAuthInfo should not return an error") + require.Equal(t, tc.wantToken, got, "LoadOldEncryptedAuthInfo should return the expected value") + }) + } +} + +func encrypt(data, key []byte) ([]byte, error) { + // Generate a random salt + salt := make([]byte, token.Saltlen()) + if _, err := io.ReadFull(rand.Reader, salt); err != nil { + return nil, fmt.Errorf("could not generate salt: %v", err) + } + + // Derive a key from the password using the salt + derivedKey, err := scrypt.Key(key, salt, 32768, 8, 1, 32) + if err != nil { + return nil, fmt.Errorf("could not derive key: %v", err) + } + + // Create a new AES cipher block + block, err := aes.NewCipher(derivedKey) + if err != nil { + return nil, fmt.Errorf("could not create cipher block: %v", err) + } + + // Create a GCM cipher + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, fmt.Errorf("could not create GCM cipher: %v", err) + } + + // Generate a random nonce + nonce := make([]byte, gcm.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return nil, fmt.Errorf("could not generate nonce: %v", err) + } + + // Encrypt the data + ciphertext := gcm.Seal(nonce, nonce, data, nil) + + // Concatenate the nonce, encrypted data, and salt + result := append(ciphertext, salt...) + + return result, nil +} diff --git a/internal/token/token.go b/internal/token/token.go new file mode 100644 index 0000000000..f12810a6de --- /dev/null +++ b/internal/token/token.go @@ -0,0 +1,70 @@ +// Package token provides functions to save and load tokens from disk. +package token + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + + "github.com/ubuntu/authd-oidc-brokers/internal/providers" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" + "golang.org/x/oauth2" +) + +// AuthCachedInfo represents the token that will be saved on disk for offline authentication. +type AuthCachedInfo struct { + Token *oauth2.Token + ExtraFields map[string]interface{} + RawIDToken string + UserInfo info.User +} + +// NewAuthCachedInfo creates a new AuthCachedInfo. It sets the provided token and rawIDToken and the provider-specific +// extra fields which should be stored persistently. +func NewAuthCachedInfo(token *oauth2.Token, rawIDToken string, provider providers.ProviderInfoer) AuthCachedInfo { + return AuthCachedInfo{ + Token: token, + RawIDToken: rawIDToken, + ExtraFields: provider.GetExtraFields(token), + } +} + +// CacheAuthInfo saves the token to the given path. +func CacheAuthInfo(path string, token AuthCachedInfo) (err error) { + jsonData, err := json.Marshal(token) + if err != nil { + return fmt.Errorf("could not marshal token: %v", err) + } + + // Create issuer specific cache directory if it doesn't exist. + if err = os.MkdirAll(filepath.Dir(path), 0700); err != nil { + return fmt.Errorf("could not create token directory: %v", err) + } + + if err = os.WriteFile(path, jsonData, 0600); err != nil { + return fmt.Errorf("could not save token: %v", err) + } + + return nil +} + +// LoadAuthInfo reads the token from the given path. +func LoadAuthInfo(path string) (AuthCachedInfo, error) { + jsonData, err := os.ReadFile(path) + if err != nil { + return AuthCachedInfo{}, fmt.Errorf("could not read token: %v", err) + } + + var cachedInfo AuthCachedInfo + if err := json.Unmarshal(jsonData, &cachedInfo); err != nil { + return AuthCachedInfo{}, fmt.Errorf("could not unmarshal token: %v", err) + } + + // Set the extra fields of the token. + if cachedInfo.ExtraFields != nil { + cachedInfo.Token = cachedInfo.Token.WithExtra(cachedInfo.ExtraFields) + } + + return cachedInfo, nil +} diff --git a/internal/token/token_test.go b/internal/token/token_test.go new file mode 100644 index 0000000000..9c796bb168 --- /dev/null +++ b/internal/token/token_test.go @@ -0,0 +1,128 @@ +package token_test + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" + "github.com/ubuntu/authd-oidc-brokers/internal/token" + "golang.org/x/oauth2" +) + +var testToken = token.AuthCachedInfo{ + Token: &oauth2.Token{ + AccessToken: "accesstoken", + RefreshToken: "refreshtoken", + }, + RawIDToken: "rawidtoken", + UserInfo: info.User{ + Name: "foo", + UUID: "saved-user-id", + Home: "/home/foo", + Gecos: "foo", + Shell: "/usr/bin/bash", + Groups: []info.Group{ + {Name: "saved-remote-group", UGID: "12345"}, + {Name: "saved-local-group", UGID: ""}, + }, + }, +} + +func TestCacheAuthInfo(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + existingParentDir bool + existingFile bool + fileIsDir bool + parentIsFile bool + + wantError bool + }{ + "Successfully store token with non-existing parent directory": {}, + "Successfully store token with existing parent directory": {existingParentDir: true}, + "Successfully store token with existing file": {existingParentDir: true, existingFile: true}, + + "Error when file exists and is a directory": {existingParentDir: true, existingFile: true, fileIsDir: true, wantError: true}, + "Error when parent directory is a file": {existingParentDir: true, parentIsFile: true, wantError: true}, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + tokenPath := filepath.Join(t.TempDir(), "parent", "token.json") + + if tc.existingParentDir && !tc.parentIsFile { + err := os.MkdirAll(filepath.Dir(tokenPath), 0700) + require.NoError(t, err, "MkdirAll should not return an error") + } + if tc.existingFile && !tc.fileIsDir { + err := os.WriteFile(tokenPath, []byte("existing file"), 0600) + require.NoError(t, err, "WriteFile should not return an error") + } + if tc.fileIsDir { + err := os.MkdirAll(tokenPath, 0700) + require.NoError(t, err, "MkdirAll should not return an error") + } + if tc.parentIsFile { + parentPath := filepath.Dir(tokenPath) + err := os.WriteFile(parentPath, []byte("existing file"), 0600) + require.NoError(t, err, "WriteFile should not return an error") + } + + err := token.CacheAuthInfo(tokenPath, testToken) + if tc.wantError { + require.Error(t, err, "CacheAuthInfo should return an error") + return + } + require.NoError(t, err, "CacheAuthInfo should not return an error") + }) + } +} + +func TestLoadAuthInfo(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + expectedRet token.AuthCachedInfo + fileExists bool + invalidJSON bool + + wantError bool + }{ + "Successfully load token from existing file": {fileExists: true, expectedRet: testToken}, + "Error when file does not exist": {wantError: true}, + "Error when file contains invalid JSON": {fileExists: true, invalidJSON: true, wantError: true}, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + tokenPath := filepath.Join(t.TempDir(), "parent", "token.json") + if tc.fileExists { + err := os.MkdirAll(filepath.Dir(tokenPath), 0700) + require.NoError(t, err, "MkdirAll should not return an error") + + if tc.invalidJSON { + err = os.WriteFile(tokenPath, []byte("invalid json"), 0600) + require.NoError(t, err, "WriteFile should not return an error") + } else { + err = token.CacheAuthInfo(tokenPath, testToken) + require.NoError(t, err, "CacheAuthInfo should not return an error") + } + } + + got, err := token.LoadAuthInfo(tokenPath) + if tc.wantError { + require.Error(t, err, "LoadAuthInfo should return an error") + return + } + require.NoError(t, err, "LoadAuthInfo should not return an error") + require.Equal(t, tc.expectedRet, got, "LoadAuthInfo should return the expected value") + }) + } +} From dd613c55208d853915d7f1b42e7d9d92ea424586 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 12 Sep 2024 16:27:35 +0200 Subject: [PATCH 0195/1670] Disable the thelper linter It's not useful to always call t.Helper() in all functions which receive a `t *testing.T` argument. For example, when using a `setup()` function in test cases, we don't want that setup function to be skipped when printing the file and line information in a log message, because that function is only called exactly once by exactly one test case, so no information is lost. Disabling the linter for each `setup()` function would require adding a lot of `//nolint` directives which clutter our code. Lets completely disable the thelper linter instead. --- .golangci.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.golangci.yaml b/.golangci.yaml index dba5860e16..c1d1a934f3 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -19,7 +19,6 @@ linters: - nakedret - nolintlint - revive - - thelper - tparallel - unconvert - unparam From d3cc4645de44eb24923186f1ae1a7c844638fa6e Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 13 Sep 2024 11:16:54 +0200 Subject: [PATCH 0196/1670] Use different group names in different tests To make it easier to figure out where in the code a group name comes from. --- internal/broker/broker_test.go | 4 ++-- internal/broker/helper_test.go | 4 ++-- ...sfully_fetch_user_info_with_default_home_when_not_provided | 4 ++-- .../golden/successfully_fetch_user_info_with_groups | 4 ++-- .../second_call | 2 +- .../second_call | 2 +- .../first_call | 2 +- .../authenticating_with_qrcode_reacquires_token/second_call | 2 +- .../second_call | 2 +- .../successfully_authenticate_user_with_password/first_call | 2 +- .../second_call | 2 +- internal/token/token_test.go | 3 +-- 12 files changed, 16 insertions(+), 17 deletions(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index d9a71e1ddf..2c15fc6548 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -836,8 +836,8 @@ func TestFetchUserInfo(t *testing.T) { mockInfoer := &testutils.MockProviderInfoer{ GroupsErr: tc.wantGroupErr, Groups: []info.Group{ - {Name: "remote-group", UGID: "12345"}, - {Name: "linux-local-group", UGID: ""}, + {Name: "test-fetch-user-info-remote-group", UGID: "12345"}, + {Name: "linux-test-fetch-user-info-local-group", UGID: ""}, }, } if tc.emptyGroups { diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index 5a05a5d800..7f3dd5da9d 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -33,8 +33,8 @@ func newBrokerForTests(t *testing.T, cfg broker.Config, providerInfoer *testutil } if providerInfoer.Groups == nil { providerInfoer.Groups = []info.Group{ - {Name: "remote-group", UGID: "12345"}, - {Name: "linux-local-group", UGID: ""}, + {Name: "new-broker-for-tests-remote-group", UGID: "12345"}, + {Name: "linux-new-broker-for-tests-local-group", UGID: ""}, } } diff --git a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_default_home_when_not_provided b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_default_home_when_not_provided index 010bd89ef1..78d05dda77 100644 --- a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_default_home_when_not_provided +++ b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_default_home_when_not_provided @@ -4,7 +4,7 @@ home: /home/test-user@email.com shell: /usr/bin/bash gecos: test-user@email.com groups: - - name: remote-group + - name: test-fetch-user-info-remote-group ugid: "12345" - - name: linux-local-group + - name: linux-test-fetch-user-info-local-group ugid: "" diff --git a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_groups b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_groups index 4c8fefb44a..606bf2442f 100644 --- a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_groups +++ b/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_groups @@ -4,7 +4,7 @@ home: /home/userInfoTests/test-user@email.com shell: /usr/bin/bash gecos: test-user@email.com groups: - - name: remote-group + - name: test-fetch-user-info-remote-group ugid: "12345" - - name: linux-local-group + - name: linux-test-fetch-user-info-local-group ugid: "" diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/second_call index 3c4c96bc15..12ca5144ad 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/second_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/second_call @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/second_call index 3c4c96bc15..12ca5144ad 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/second_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/second_call @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/first_call index 3c4c96bc15..12ca5144ad 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/first_call @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/second_call index 3c4c96bc15..12ca5144ad 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/second_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/second_call @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/second_call index 3c4c96bc15..12ca5144ad 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/second_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/second_call @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call index cc046e18ae..20738888ae 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/second_call index 3c4c96bc15..12ca5144ad 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/second_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/second_call @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-group","ugid":"12345"},{"name":"linux-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' err: diff --git a/internal/token/token_test.go b/internal/token/token_test.go index 9c796bb168..7d1fd1f56a 100644 --- a/internal/token/token_test.go +++ b/internal/token/token_test.go @@ -24,8 +24,7 @@ var testToken = token.AuthCachedInfo{ Gecos: "foo", Shell: "/usr/bin/bash", Groups: []info.Group{ - {Name: "saved-remote-group", UGID: "12345"}, - {Name: "saved-local-group", UGID: ""}, + {Name: "token-test-group", UGID: "12345"}, }, }, } From 8037435f55da4d2ab2e7d032e99b276b428ff217 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 9 Oct 2024 21:08:54 +0200 Subject: [PATCH 0197/1670] refactor: Use fewer magic string constants in tests ... and instead use an options struct for generating the preexistent token. --- internal/broker/broker_test.go | 67 +++++++-------- internal/broker/helper_test.go | 85 +++++++++---------- .../first_auth | 2 +- .../second_auth | 2 +- .../first_auth | 2 +- .../second_auth | 2 +- .../first_auth | 2 +- .../second_auth | 2 +- .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json | 1 + .../first_call | 3 + .../second_call | 3 + 12 files changed, 87 insertions(+), 85 deletions(-) create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/data/provider_url/test-user@email.com/password create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/data/provider_url/test-user@email.com/token.json create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/second_call diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 2c15fc6548..c55bc2d0df 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -410,28 +410,26 @@ func TestIsAuthenticated(t *testing.T) { wantSecondCall bool secondChallenge string - preexistentToken string + token *tokenOptions invalidAuthData bool dontWaitForFirstCall bool readOnlyDataDir bool }{ - "Successfully authenticate user with qrcode and newpassword": {firstChallenge: "-", wantSecondCall: true}, - "Successfully authenticate user with link code and newpassword": {firstMode: authmodes.Device, firstChallenge: "-", wantSecondCall: true}, - "Successfully authenticate user with password": {firstMode: authmodes.Password, preexistentToken: "valid"}, + "Successfully authenticate user with QRCode+newpassword": {firstChallenge: "-", wantSecondCall: true}, + "Successfully authenticate user with password": {firstMode: authmodes.Password, token: &tokenOptions{}}, - "Authenticating with qrcode reacquires token": {firstChallenge: "-", wantSecondCall: true, preexistentToken: "valid"}, - "Authenticating with link code reacquires token": {firstMode: authmodes.Device, firstChallenge: "-", wantSecondCall: true, preexistentToken: "valid"}, - "Authenticating with password refreshes expired token": {firstMode: authmodes.Password, preexistentToken: "expired"}, + "Authenticating with qrcode reacquires token": {firstChallenge: "-", wantSecondCall: true, token: &tokenOptions{}}, + "Authenticating with password refreshes expired token": {firstMode: authmodes.Password, token: &tokenOptions{expired: true}}, "Authenticating with password still allowed if server is unreachable": { - firstMode: authmodes.Password, - preexistentToken: "valid", + firstMode: authmodes.Password, + token: &tokenOptions{}, customHandlers: map[string]testutils.ProviderHandler{ "/.well-known/openid-configuration": testutils.UnavailableHandler(), }, }, "Authenticating with password still allowed if token is expired and server is unreachable": { - firstMode: authmodes.Password, - preexistentToken: "expired", + firstMode: authmodes.Password, + token: &tokenOptions{expired: true}, customHandlers: map[string]testutils.ProviderHandler{ "/.well-known/openid-configuration": testutils.UnavailableHandler(), }, @@ -447,26 +445,26 @@ func TestIsAuthenticated(t *testing.T) { "Error when authentication data is invalid": {invalidAuthData: true}, "Error when challenge can not be decrypted": {firstMode: authmodes.Password, badFirstKey: true}, - "Error when provided wrong challenge": {firstMode: authmodes.Password, preexistentToken: "valid", firstChallenge: "wrongpassword"}, + "Error when provided wrong challenge": {firstMode: authmodes.Password, token: &tokenOptions{}, firstChallenge: "wrongpassword"}, "Error when can not cache token": {firstChallenge: "-", wantSecondCall: true, readOnlyDataDir: true}, "Error when IsAuthenticated is ongoing for session": {dontWaitForFirstCall: true, wantSecondCall: true}, "Error when mode is password and token does not exist": {firstMode: authmodes.Password}, "Error when mode is password but server returns error": { - firstMode: authmodes.Password, - preexistentToken: "expired", + firstMode: authmodes.Password, + token: &tokenOptions{expired: true}, customHandlers: map[string]testutils.ProviderHandler{ "/token": testutils.BadRequestHandler(), }, }, - "Error when mode is password and token is invalid": {firstMode: authmodes.Password, preexistentToken: "invalid"}, - "Error when mode is password and cached token can't be refreshed": {firstMode: authmodes.Password, preexistentToken: "no-refresh"}, - "Error when mode is password and token refresh times out": {firstMode: authmodes.Password, preexistentToken: "expired", + "Error when mode is password and token is invalid": {firstMode: authmodes.Password, token: &tokenOptions{invalid: true}}, + "Error when mode is password and cached token can't be refreshed": {firstMode: authmodes.Password, token: &tokenOptions{expired: true, noRefreshToken: true}}, + "Error when mode is password and token refresh times out": {firstMode: authmodes.Password, token: &tokenOptions{expired: true}, customHandlers: map[string]testutils.ProviderHandler{ "/token": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, }, - "Error when existing token has no user info and fetching user info fails": {firstMode: authmodes.Password, preexistentToken: "no-user-info", getUserInfoFails: true}, + "Error when existing token has no user info and fetching user info fails": {firstMode: authmodes.Password, token: &tokenOptions{noUserInfo: true}, getUserInfoFails: true}, "Error when mode is qrcode and response is invalid": {firstAuthInfo: map[string]any{"response": "not a valid response"}}, "Error when mode is qrcode and link expires": { @@ -549,15 +547,16 @@ func TestIsAuthenticated(t *testing.T) { b := newBrokerForTests(t, *cfg, mockInfoer) sessionID, key := newSessionForTests(t, b, tc.username, tc.sessionMode) - if tc.preexistentToken != "" { - generateAndStoreCachedInfo(t, tc.preexistentToken, provider.URL, b.TokenPathForSession(sessionID)) + if tc.token != nil { + tc.token.issuer = provider.URL + generateAndStoreCachedInfo(t, *tc.token, b.TokenPathForSession(sessionID)) err = password.HashAndStorePassword(correctPassword, b.PasswordFilepathForSession(sessionID)) require.NoError(t, err, "Setup: HashAndStorePassword should not have returned an error") } var readOnlyDataCleanup, readOnlyTokenCleanup func() if tc.readOnlyDataDir { - if tc.preexistentToken != "" { + if tc.token != nil { readOnlyTokenCleanup = testutils.MakeReadOnly(t, b.TokenPathForSession(sessionID)) t.Cleanup(readOnlyTokenCleanup) } @@ -651,7 +650,7 @@ func TestIsAuthenticated(t *testing.T) { // We need to restore some permissions in order to save the golden files. if tc.readOnlyDataDir { readOnlyDataCleanup() - if tc.preexistentToken != "" { + if tc.token != nil { readOnlyTokenCleanup() } } @@ -708,12 +707,14 @@ func TestConcurrentIsAuthenticated(t *testing.T) { b := newBrokerForTests(t, *cfg, mockInfoer) firstSession, firstKey := newSessionForTests(t, b, "user1", "") - generateAndStoreCachedInfo(t, "", defaultProvider.URL, b.TokenPathForSession(firstSession)) + firstToken := tokenOptions{username: "user1", issuer: defaultProvider.URL} + generateAndStoreCachedInfo(t, firstToken, b.TokenPathForSession(firstSession)) err = password.HashAndStorePassword("password", b.PasswordFilepathForSession(firstSession)) require.NoError(t, err, "Setup: HashAndStorePassword should not have returned an error") secondSession, secondKey := newSessionForTests(t, b, "user2", "") - generateAndStoreCachedInfo(t, "", defaultProvider.URL, b.TokenPathForSession(secondSession)) + secondToken := tokenOptions{username: "user2", issuer: defaultProvider.URL} + generateAndStoreCachedInfo(t, secondToken, b.TokenPathForSession(secondSession)) err = password.HashAndStorePassword("password", b.PasswordFilepathForSession(secondSession)) require.NoError(t, err, "Setup: HashAndStorePassword should not have returned an error") @@ -799,8 +800,8 @@ func TestFetchUserInfo(t *testing.T) { t.Parallel() tests := map[string]struct { - username string - userToken string + username string + token tokenOptions emptyHomeDir bool emptyGroups bool @@ -811,10 +812,10 @@ func TestFetchUserInfo(t *testing.T) { "Successfully fetch user info without groups": {emptyGroups: true}, "Successfully fetch user info with default home when not provided": {emptyHomeDir: true}, - "Error when token can not be validated": {userToken: "invalid", wantErr: true}, - "Error when ID token claims are invalid": {userToken: "invalid-id", wantErr: true}, - "Error when username is not configured": {userToken: "no-name", wantErr: true}, - "Error when username is different than the requested one": {userToken: "other-name", wantErr: true}, + "Error when token can not be validated": {token: tokenOptions{invalid: true}, wantErr: true}, + "Error when ID token claims are invalid": {token: tokenOptions{invalidClaims: true}, wantErr: true}, + "Error when username is not configured": {token: tokenOptions{username: "-"}, wantErr: true}, + "Error when username is different than the requested one": {token: tokenOptions{username: "other-user@email.com"}, wantErr: true}, "Error when getting user groups": {wantGroupErr: true, wantErr: true}, } for name, tc := range tests { @@ -850,14 +851,12 @@ func TestFetchUserInfo(t *testing.T) { if tc.username == "" { tc.username = "test-user@email.com" } - if tc.userToken == "" { - tc.userToken = "valid" - } + tc.token.issuer = defaultProvider.URL sessionID, _, err := b.NewSession(tc.username, "lang", "auth") require.NoError(t, err, "Setup: Failed to create session for the tests") - cachedInfo := generateCachedInfo(t, tc.userToken, defaultProvider.URL) + cachedInfo := generateCachedInfo(t, tc.token) if cachedInfo == nil { cachedInfo = &token.AuthCachedInfo{} } diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index 7f3dd5da9d..c30ad1e0c1 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -103,33 +103,10 @@ func updateAuthModes(t *testing.T, b *broker.Broker, sessionID, selectedMode str require.NoError(t, err, "Setup: SelectAuthenticationMode should not have returned an error") } -var testTokens = map[string]token.AuthCachedInfo{ - "valid": { - Token: &oauth2.Token{ - AccessToken: "accesstoken", - RefreshToken: "refreshtoken", - Expiry: time.Now().Add(1000 * time.Hour), - }, - }, - "expired": { - Token: &oauth2.Token{ - AccessToken: "accesstoken", - RefreshToken: "refreshtoken", - Expiry: time.Now().Add(-1000 * time.Hour), - }, - }, - "no-refresh": { - Token: &oauth2.Token{ - AccessToken: "accesstoken", - Expiry: time.Now().Add(-1000 * time.Hour), - }, - }, -} - -func generateAndStoreCachedInfo(t *testing.T, preexistentToken, issuer, path string) { +func generateAndStoreCachedInfo(t *testing.T, options tokenOptions, path string) { t.Helper() - tok := generateCachedInfo(t, preexistentToken, issuer) + tok := generateCachedInfo(t, options) if tok == nil { writeTrashToken(t, path) return @@ -138,47 +115,65 @@ func generateAndStoreCachedInfo(t *testing.T, preexistentToken, issuer, path str require.NoError(t, err, "Setup: storing token should not have failed") } -func generateCachedInfo(t *testing.T, preexistentToken, issuer string) *token.AuthCachedInfo { +type tokenOptions struct { + username string + issuer string + + expired bool + noRefreshToken bool + invalid bool + invalidClaims bool + noUserInfo bool +} + +func generateCachedInfo(t *testing.T, options tokenOptions) *token.AuthCachedInfo { t.Helper() - if preexistentToken == "invalid" { + if options.invalid { return nil } - var username string - switch preexistentToken { - case "no-name": - username = "" - case "other-name": - username = "other-user@email.com" - default: - username = "test-user@email.com" + if options.username == "" { + options.username = "test-user@email.com" + } + if options.username == "-" { + options.username = "" } idToken := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{ - "iss": issuer, + "iss": options.issuer, "sub": "saved-user-id", "aud": "test-client-id", "exp": 9999999999, "name": "test-user", "preferred_username": "test-user-preferred-username@email.com", - "email": username, + "email": options.username, "email_verified": true, }) encodedToken, err := idToken.SignedString(testutils.MockKey) require.NoError(t, err, "Setup: signing token should not have failed") - tok, ok := testTokens[preexistentToken] - if !ok { - tok = testTokens["valid"] + tok := token.AuthCachedInfo{ + Token: &oauth2.Token{ + AccessToken: "accesstoken", + RefreshToken: "refreshtoken", + Expiry: time.Now().Add(1000 * time.Hour), + }, + } + + if options.expired { + tok.Token.Expiry = time.Now().Add(-1000 * time.Hour) + } + if options.noRefreshToken { + tok.Token.RefreshToken = "" } - if preexistentToken != "no-user-info" { + if !options.noUserInfo { tok.UserInfo = info.User{ - Name: username, + Name: options.username, UUID: "saved-user-id", - Home: "/home/" + username, - Gecos: username, + Home: "/home/" + options.username, + Gecos: options.username, Shell: "/usr/bin/bash", Groups: []info.Group{ {Name: "saved-remote-group", UGID: "12345"}, @@ -187,7 +182,7 @@ func generateCachedInfo(t *testing.T, preexistentToken, issuer string) *token.Au } } - if preexistentToken == "invalid-id" { + if options.invalidClaims { encodedToken = ".invalid." tok.UserInfo = info.User{} } diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/first_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/first_auth index 2928c3a331..13dbd8761c 100644 --- a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/first_auth +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/first_auth @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"saved-remote-group","ugid":"12345"},{"name":"saved-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"user1","uuid":"saved-user-id","dir":"/home/user1","shell":"/usr/bin/bash","gecos":"user1","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/second_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/second_auth index 2928c3a331..c0b252ffdf 100644 --- a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/second_auth +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/second_auth @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"saved-remote-group","ugid":"12345"},{"name":"saved-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"user2","uuid":"saved-user-id","dir":"/home/user2","shell":"/usr/bin/bash","gecos":"user2","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/first_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/first_auth index 2928c3a331..13dbd8761c 100644 --- a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/first_auth +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/first_auth @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"saved-remote-group","ugid":"12345"},{"name":"saved-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"user1","uuid":"saved-user-id","dir":"/home/user1","shell":"/usr/bin/bash","gecos":"user1","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/second_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/second_auth index 2928c3a331..c0b252ffdf 100644 --- a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/second_auth +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/second_auth @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"saved-remote-group","ugid":"12345"},{"name":"saved-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"user2","uuid":"saved-user-id","dir":"/home/user2","shell":"/usr/bin/bash","gecos":"user2","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/first_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/first_auth index 2928c3a331..13dbd8761c 100644 --- a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/first_auth +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/first_auth @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"saved-remote-group","ugid":"12345"},{"name":"saved-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"user1","uuid":"saved-user-id","dir":"/home/user1","shell":"/usr/bin/bash","gecos":"user1","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/second_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/second_auth index 2928c3a331..c0b252ffdf 100644 --- a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/second_auth +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/second_auth @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"saved-remote-group","ugid":"12345"},{"name":"saved-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"user2","uuid":"saved-user-id","dir":"/home/user2","shell":"/usr/bin/bash","gecos":"user2","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/data/provider_url/test-user@email.com/token.json new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/data/provider_url/test-user@email.com/token.json @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/first_call new file mode 100644 index 0000000000..d0887a134f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/first_call @@ -0,0 +1,3 @@ +access: next +data: '{}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/second_call new file mode 100644 index 0000000000..12ca5144ad --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/second_call @@ -0,0 +1,3 @@ +access: granted +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' +err: From 69126ba2269224ca78cd9d5d95536c7b2bbeef1f Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 5 Nov 2024 17:01:51 +0100 Subject: [PATCH 0198/1670] Rename test The test is expected to fail because the token is expired (and refreshing the token fails), not because the mode is password. --- internal/broker/broker_test.go | 4 ++-- .../data/provider_url/test-user@email.com/password | 0 .../data/provider_url/test-user@email.com/token.json | 0 .../first_call | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_password_and_cached_token_can't_be_refreshed => error_when_token_is_expired_and_refreshing_token_fails}/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_password_and_cached_token_can't_be_refreshed => error_when_token_is_expired_and_refreshing_token_fails}/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_password_and_cached_token_can't_be_refreshed => error_when_token_is_expired_and_refreshing_token_fails}/first_call (100%) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index c55bc2d0df..fa3db4170d 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -457,8 +457,8 @@ func TestIsAuthenticated(t *testing.T) { "/token": testutils.BadRequestHandler(), }, }, - "Error when mode is password and token is invalid": {firstMode: authmodes.Password, token: &tokenOptions{invalid: true}}, - "Error when mode is password and cached token can't be refreshed": {firstMode: authmodes.Password, token: &tokenOptions{expired: true, noRefreshToken: true}}, + "Error when mode is password and token is invalid": {firstMode: authmodes.Password, token: &tokenOptions{invalid: true}}, + "Error when token is expired and refreshing token fails": {firstMode: authmodes.Password, token: &tokenOptions{expired: true, noRefreshToken: true}}, "Error when mode is password and token refresh times out": {firstMode: authmodes.Password, token: &tokenOptions{expired: true}, customHandlers: map[string]testutils.ProviderHandler{ "/token": testutils.HangingHandler(broker.MaxRequestDuration + 1), diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_token_is_expired_and_refreshing_token_fails/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_cached_token_can't_be_refreshed/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_token_is_expired_and_refreshing_token_fails/first_call From fde33c1afa005080e23b4fa3d9722472f0569d21 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 7 Nov 2024 19:28:13 +0100 Subject: [PATCH 0199/1670] Remove unused key "token" in firstAuthInfo map It's not used anywhere and as far as I can tell it never was. --- internal/broker/broker_test.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index fa3db4170d..cd8b9c9065 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -18,7 +18,6 @@ import ( "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "github.com/ubuntu/authd-oidc-brokers/internal/testutils" "github.com/ubuntu/authd-oidc-brokers/internal/token" - "golang.org/x/oauth2" "gopkg.in/yaml.v3" ) @@ -500,18 +499,13 @@ func TestIsAuthenticated(t *testing.T) { }, "Error when empty challenge is provided for local password": {firstChallenge: "-", wantSecondCall: true, secondChallenge: "-"}, "Error when mode is newpassword and token is not set": { - firstMode: authmodes.NewPassword, - firstAuthInfo: map[string]any{"token": nil}, + firstMode: authmodes.NewPassword, }, "Error when mode is newpassword and id token is not set": { - firstMode: authmodes.NewPassword, - firstAuthInfo: map[string]any{"token": &oauth2.Token{}}, + firstMode: authmodes.NewPassword, }, "Error when mode is newpassword and can not fetch user info": { firstMode: authmodes.NewPassword, - firstAuthInfo: map[string]any{ - "token": (&oauth2.Token{}).WithExtra(map[string]interface{}{"id_token": "invalid"}), - }, }, // This test case also tests that errors with double quotes are marshaled to JSON correctly. "Error when selected username does not match the provider one": {username: "not-matching", firstChallenge: "-"}, From 41661e6bbf9cc6af5fa5e55dd3d38aa8ff0af927 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 7 Nov 2024 19:32:32 +0100 Subject: [PATCH 0200/1670] Fix tests The previous commit left three test cases with exactly the same preconditions. This commit changes the preconditions so that the test cases now actually test what they are supposed to test. --- internal/broker/broker_test.go | 6 ++++-- internal/broker/helper_test.go | 7 +++++-- .../data/.empty | 0 .../first_call | 3 +++ .../data/provider_url/test-user@email.com/password | 1 + .../data/provider_url/test-user@email.com/token.json | 1 + 6 files changed, 14 insertions(+), 4 deletions(-) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_newpassword_and_id_token_is_not_set => error_when_mode_is_newpassword_and_fetching_user_info_fails}/data/.empty (100%) create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_fetching_user_info_fails/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/data/provider_url/test-user@email.com/password create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index cd8b9c9065..f238e3bb3d 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -503,9 +503,11 @@ func TestIsAuthenticated(t *testing.T) { }, "Error when mode is newpassword and id token is not set": { firstMode: authmodes.NewPassword, + token: &tokenOptions{noIDToken: true}, }, - "Error when mode is newpassword and can not fetch user info": { - firstMode: authmodes.NewPassword, + "Error when mode is newpassword and fetching user info fails": { + firstMode: authmodes.NewPassword, + getUserInfoFails: true, }, // This test case also tests that errors with double quotes are marshaled to JSON correctly. "Error when selected username does not match the provider one": {username: "not-matching", firstChallenge: "-"}, diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index c30ad1e0c1..7bb5d7d998 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -121,6 +121,7 @@ type tokenOptions struct { expired bool noRefreshToken bool + noIDToken bool invalid bool invalidClaims bool noUserInfo bool @@ -187,8 +188,10 @@ func generateCachedInfo(t *testing.T, options tokenOptions) *token.AuthCachedInf tok.UserInfo = info.User{} } - tok.Token = tok.Token.WithExtra(map[string]string{"id_token": encodedToken}) - tok.RawIDToken = encodedToken + if !options.noIDToken { + tok.Token = tok.Token.WithExtra(map[string]string{"id_token": encodedToken}) + tok.RawIDToken = encodedToken + } return &tok } diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/data/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_fetching_user_info_fails/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/data/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_fetching_user_info_fails/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_fetching_user_info_fails/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_fetching_user_info_fails/first_call new file mode 100644 index 0000000000..c47bb5dfc9 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_fetching_user_info_fails/first_call @@ -0,0 +1,3 @@ +access: denied +data: '{"message":"authentication failure: could not get required information"}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/data/provider_url/test-user@email.com/token.json new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/data/provider_url/test-user@email.com/token.json @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file From c189816515956a6d336eb042bf2a23262546bedb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 08:12:41 +0000 Subject: [PATCH 0201/1670] deps(go): bump golang.org/x/crypto from 0.28.0 to 0.29.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.28.0 to 0.29.0. - [Commits](https://github.com/golang/crypto/compare/v0.28.0...v0.29.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index e1160ded89..6edefe1adb 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/stretchr/testify v1.9.0 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 - golang.org/x/crypto v0.28.0 + golang.org/x/crypto v0.29.0 golang.org/x/oauth2 v0.23.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 @@ -64,7 +64,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/sync v0.9.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/text v0.20.0 // indirect ) diff --git a/go.sum b/go.sum index 1b082bd75d..790418609e 100644 --- a/go.sum +++ b/go.sum @@ -132,8 +132,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -146,8 +146,8 @@ golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -155,16 +155,16 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc 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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From b3e8eba69622fe09c21ffc952620fe81f015e165 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 08:24:46 +0000 Subject: [PATCH 0202/1670] deps(go-tools): bump github.com/golangci/golangci-lint in /tools Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.61.0 to 1.62.0. - [Release notes](https://github.com/golangci/golangci-lint/releases) - [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md) - [Commits](https://github.com/golangci/golangci-lint/compare/v1.61.0...v1.62.0) --- updated-dependencies: - dependency-name: github.com/golangci/golangci-lint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- tools/go.mod | 73 ++++++++++++------------ tools/go.sum | 153 ++++++++++++++++++++++++++------------------------- 2 files changed, 117 insertions(+), 109 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index f6aaf03291..81810c7483 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -2,34 +2,34 @@ module github.com/ubuntu/authd-oidc-brokers/tools go 1.23.0 -require github.com/golangci/golangci-lint v1.61.0 +require github.com/golangci/golangci-lint v1.62.0 require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 4d63.com/gochecknoglobals v0.2.1 // indirect github.com/4meepo/tagalign v1.3.4 // indirect - github.com/Abirdcfly/dupword v0.1.1 // indirect - github.com/Antonboom/errname v0.1.13 // indirect - github.com/Antonboom/nilnil v0.1.9 // indirect - github.com/Antonboom/testifylint v1.4.3 // indirect + github.com/Abirdcfly/dupword v0.1.3 // indirect + github.com/Antonboom/errname v1.0.0 // indirect + github.com/Antonboom/nilnil v1.0.0 // indirect + github.com/Antonboom/testifylint v1.5.0 // indirect github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect github.com/Crocmagnon/fatcontext v0.5.2 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect - github.com/alecthomas/go-check-sumtype v0.1.4 // indirect - github.com/alexkohler/nakedret/v2 v2.0.4 // indirect + github.com/alecthomas/go-check-sumtype v0.2.0 // indirect + github.com/alexkohler/nakedret/v2 v2.0.5 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect github.com/ashanbrown/forbidigo v1.6.0 // indirect github.com/ashanbrown/makezero v1.1.1 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bkielbasa/cyclop v1.2.1 // indirect + github.com/bkielbasa/cyclop v1.2.3 // indirect github.com/blizzy78/varnamelen v0.8.0 // indirect github.com/bombsimon/wsl/v4 v4.4.1 // indirect - github.com/breml/bidichk v0.2.7 // indirect - github.com/breml/errchkjson v0.3.6 // indirect + github.com/breml/bidichk v0.3.2 // indirect + github.com/breml/errchkjson v0.4.0 // indirect github.com/butuzov/ireturn v0.3.0 // indirect github.com/butuzov/mirror v1.2.0 // indirect github.com/catenacyber/perfsprint v0.7.1 // indirect @@ -37,19 +37,19 @@ require ( github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/charithe/durationcheck v0.0.10 // indirect github.com/chavacava/garif v0.1.0 // indirect - github.com/ckaznocha/intrange v0.2.0 // indirect + github.com/ckaznocha/intrange v0.2.1 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect github.com/daixiang0/gci v0.13.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect github.com/ettle/strcase v0.2.0 // indirect - github.com/fatih/color v1.17.0 // indirect + github.com/fatih/color v1.18.0 // indirect github.com/fatih/structtag v1.2.0 // indirect github.com/firefart/nonamedreturns v1.0.5 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/ghostiam/protogetter v0.3.6 // indirect - github.com/go-critic/go-critic v0.11.4 // indirect + github.com/ghostiam/protogetter v0.3.8 // indirect + github.com/go-critic/go-critic v0.11.5 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect github.com/go-toolsmith/astequal v1.2.0 // indirect @@ -57,12 +57,13 @@ require ( github.com/go-toolsmith/astp v1.1.0 // indirect github.com/go-toolsmith/strparse v1.1.0 // indirect github.com/go-toolsmith/typep v1.1.0 // indirect - github.com/go-viper/mapstructure/v2 v2.1.0 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect + github.com/golangci/go-printf-func-name v0.1.0 // indirect github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 // indirect github.com/golangci/misspell v0.6.0 // indirect github.com/golangci/modinfo v0.3.4 // indirect @@ -81,20 +82,18 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jgautheron/goconst v1.7.1 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect - github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect github.com/jjti/go-spancheck v0.6.2 // indirect github.com/julz/importas v0.1.0 // indirect github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect - github.com/kisielk/errcheck v1.7.0 // indirect + github.com/kisielk/errcheck v1.8.0 // indirect github.com/kkHAIKE/contextcheck v1.1.5 // indirect github.com/kulti/thelper v0.6.3 // indirect github.com/kunwardeep/paralleltest v1.0.10 // indirect github.com/kyoh86/exportloopref v0.1.11 // indirect - github.com/lasiar/canonicalheader v1.1.1 // indirect + github.com/lasiar/canonicalheader v1.1.2 // indirect github.com/ldez/gomoddirectives v0.2.4 // indirect github.com/ldez/tagliatelle v0.5.0 // indirect github.com/leonklingele/grouper v1.1.2 // indirect - github.com/lufeee/execinquery v1.2.1 // indirect github.com/macabu/inamedparam v0.1.3 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/maratori/testableexamples v1.0.0 // indirect @@ -102,16 +101,16 @@ require ( github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mgechev/revive v1.3.9 // indirect + github.com/mgechev/revive v1.5.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moricho/tparallel v0.3.2 // indirect github.com/nakabonne/nestif v0.3.1 // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect - github.com/nunnatsa/ginkgolinter v0.16.2 // indirect + github.com/nunnatsa/ginkgolinter v0.18.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect @@ -126,18 +125,21 @@ require ( github.com/quasilyte/gogrep v0.5.0 // indirect github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect + github.com/raeperd/recvcheck v0.1.2 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/ryancurrah/gomodguard v1.3.5 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.27.0 // indirect - github.com/securego/gosec/v2 v2.21.2 // indirect + github.com/securego/gosec/v2 v2.21.4 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect - github.com/sivchari/tenv v1.10.0 // indirect - github.com/sonatard/noctx v0.0.2 // indirect + github.com/sivchari/tenv v1.12.1 // indirect + github.com/sonatard/noctx v0.1.0 // indirect github.com/sourcegraph/go-diff v0.7.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.5.0 // indirect @@ -151,32 +153,33 @@ require ( github.com/stretchr/testify v1.9.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/tdakkota/asciicheck v0.2.0 // indirect - github.com/tetafro/godot v1.4.17 // indirect + github.com/tetafro/godot v1.4.18 // indirect github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect - github.com/timonwong/loggercheck v0.9.4 // indirect + github.com/timonwong/loggercheck v0.10.1 // indirect github.com/tomarrell/wrapcheck/v2 v2.9.0 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect github.com/ultraware/funlen v0.1.0 // indirect github.com/ultraware/whitespace v0.1.1 // indirect github.com/uudashr/gocognit v1.1.3 // indirect + github.com/uudashr/iface v1.2.0 // indirect github.com/xen0n/gosmopolitan v1.2.2 // indirect github.com/yagipy/maintidx v1.0.0 // indirect github.com/yeya24/promlinter v0.3.0 // indirect github.com/ykadowak/zerologlint v0.1.5 // indirect gitlab.com/bosi/decorder v0.4.2 // indirect - go-simpler.org/musttag v0.12.2 // indirect + go-simpler.org/musttag v0.13.0 // indirect go-simpler.org/sloglint v0.7.2 // indirect go.uber.org/atomic v1.7.0 // indirect - go.uber.org/automaxprocs v1.5.3 // indirect + go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect - golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect - golang.org/x/mod v0.21.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect + golang.org/x/exp/typeparams v0.0.0-20240909161429-701f63a606c0 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/sync v0.9.0 // indirect + golang.org/x/sys v0.27.0 // indirect golang.org/x/text v0.18.0 // indirect - golang.org/x/tools v0.24.0 // indirect + golang.org/x/tools v0.27.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/tools/go.sum b/tools/go.sum index de6bd9d31a..c8205a75d7 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -37,14 +37,14 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/4meepo/tagalign v1.3.4 h1:P51VcvBnf04YkHzjfclN6BbsopfJR5rxs1n+5zHt+w8= github.com/4meepo/tagalign v1.3.4/go.mod h1:M+pnkHH2vG8+qhE5bVc/zeP7HS/j910Fwa9TUSyZVI0= -github.com/Abirdcfly/dupword v0.1.1 h1:Bsxe0fIw6OwBtXMIncaTxCLHYO5BB+3mcsR5E8VXloY= -github.com/Abirdcfly/dupword v0.1.1/go.mod h1:B49AcJdTYYkpd4HjgAcutNGG9HZ2JWwKunH9Y2BA6sM= -github.com/Antonboom/errname v0.1.13 h1:JHICqsewj/fNckzrfVSe+T33svwQxmjC+1ntDsHOVvM= -github.com/Antonboom/errname v0.1.13/go.mod h1:uWyefRYRN54lBg6HseYCFhs6Qjcy41Y3Jl/dVhA87Ns= -github.com/Antonboom/nilnil v0.1.9 h1:eKFMejSxPSA9eLSensFmjW2XTgTwJMjZ8hUHtV4s/SQ= -github.com/Antonboom/nilnil v0.1.9/go.mod h1:iGe2rYwCq5/Me1khrysB4nwI7swQvjclR8/YRPl5ihQ= -github.com/Antonboom/testifylint v1.4.3 h1:ohMt6AHuHgttaQ1xb6SSnxCeK4/rnK7KKzbvs7DmEck= -github.com/Antonboom/testifylint v1.4.3/go.mod h1:+8Q9+AOLsz5ZiQiiYujJKs9mNz398+M6UgslP4qgJLA= +github.com/Abirdcfly/dupword v0.1.3 h1:9Pa1NuAsZvpFPi9Pqkd93I7LIYRURj+A//dFd5tgBeE= +github.com/Abirdcfly/dupword v0.1.3/go.mod h1:8VbB2t7e10KRNdwTVoxdBaxla6avbhGzb8sCTygUMhw= +github.com/Antonboom/errname v1.0.0 h1:oJOOWR07vS1kRusl6YRSlat7HFnb3mSfMl6sDMRoTBA= +github.com/Antonboom/errname v1.0.0/go.mod h1:gMOBFzK/vrTiXN9Oh+HFs+e6Ndl0eTFbtsRTSRdXyGI= +github.com/Antonboom/nilnil v1.0.0 h1:n+v+B12dsE5tbAqRODXmEKfZv9j2KcTBrp+LkoM4HZk= +github.com/Antonboom/nilnil v1.0.0/go.mod h1:fDJ1FSFoLN6yoG65ANb1WihItf6qt9PJVTn/s2IrcII= +github.com/Antonboom/testifylint v1.5.0 h1:dlUIsDMtCrZWUnvkaCz3quJCoIjaGi41GzjPBGkkJ8A= +github.com/Antonboom/testifylint v1.5.0/go.mod h1:wqaJbu0Blb5Wag2wv7Z5xt+CIV+eVLxtGZrlK13z3AE= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= @@ -61,8 +61,8 @@ github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJP github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ= github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= -github.com/alecthomas/go-check-sumtype v0.1.4 h1:WCvlB3l5Vq5dZQTFmodqL2g68uHiSwwlWcT5a2FGK0c= -github.com/alecthomas/go-check-sumtype v0.1.4/go.mod h1:WyYPfhfkdhyrdaligV6svFopZV8Lqdzn5pyVBaV6jhQ= +github.com/alecthomas/go-check-sumtype v0.2.0 h1:Bo+e4DFf3rs7ME9w/0SU/g6nmzJaphduP8Cjiz0gbwY= +github.com/alecthomas/go-check-sumtype v0.2.0/go.mod h1:WyYPfhfkdhyrdaligV6svFopZV8Lqdzn5pyVBaV6jhQ= github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -70,8 +70,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alexkohler/nakedret/v2 v2.0.4 h1:yZuKmjqGi0pSmjGpOC016LtPJysIL0WEUiaXW5SUnNg= -github.com/alexkohler/nakedret/v2 v2.0.4/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU= +github.com/alexkohler/nakedret/v2 v2.0.5 h1:fP5qLgtwbx9EJE8dGEERT02YwS8En4r9nnZ71RK+EVU= +github.com/alexkohler/nakedret/v2 v2.0.5/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU= github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= @@ -86,16 +86,16 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bkielbasa/cyclop v1.2.1 h1:AeF71HZDob1P2/pRm1so9cd1alZnrpyc4q2uP2l0gJY= -github.com/bkielbasa/cyclop v1.2.1/go.mod h1:K/dT/M0FPAiYjBgQGau7tz+3TMh4FWAEqlMhzFWCrgM= +github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w= +github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo= github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= github.com/bombsimon/wsl/v4 v4.4.1 h1:jfUaCkN+aUpobrMO24zwyAMwMAV5eSziCkOKEauOLdw= github.com/bombsimon/wsl/v4 v4.4.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo= -github.com/breml/bidichk v0.2.7 h1:dAkKQPLl/Qrk7hnP6P+E0xOodrq8Us7+U0o4UBOAlQY= -github.com/breml/bidichk v0.2.7/go.mod h1:YodjipAGI9fGcYM7II6wFvGhdMYsC5pHDlGzqvEW3tQ= -github.com/breml/errchkjson v0.3.6 h1:VLhVkqSBH96AvXEyclMR37rZslRrY2kcyq+31HCsVrA= -github.com/breml/errchkjson v0.3.6/go.mod h1:jhSDoFheAF2RSDOlCfhHO9KqhZgAYLyvHe7bRCX8f/U= +github.com/breml/bidichk v0.3.2 h1:xV4flJ9V5xWTqxL+/PMFF6dtJPvZLPsyixAoPe8BGJs= +github.com/breml/bidichk v0.3.2/go.mod h1:VzFLBxuYtT23z5+iVkamXO386OB+/sVwZOpIj6zXGos= +github.com/breml/errchkjson v0.4.0 h1:gftf6uWZMtIa/Is3XJgibewBm2ksAQSY/kABDNFTAdk= +github.com/breml/errchkjson v0.4.0/go.mod h1:AuBOSTHyLSaaAFlWsRSuRBIroCh3eh7ZHh5YeelDIk8= github.com/butuzov/ireturn v0.3.0 h1:hTjMqWw3y5JC3kpnC5vXmFJAWI/m31jaCYQqzkS6PL0= github.com/butuzov/ireturn v0.3.0/go.mod h1:A09nIiwiqzN/IoVo9ogpa0Hzi9fex1kd9PSD6edP5ZA= github.com/butuzov/mirror v1.2.0 h1:9YVK1qIjNspaqWutSv8gsge2e/Xpq1eqEkslEUHy5cs= @@ -115,8 +115,8 @@ github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+U github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/ckaznocha/intrange v0.2.0 h1:FykcZuJ8BD7oX93YbO1UY9oZtkRbp+1/kJcDjkefYLs= -github.com/ckaznocha/intrange v0.2.0/go.mod h1:r5I7nUlAAG56xmkOpw4XVr16BXhwYTUdcuRFeevn1oE= +github.com/ckaznocha/intrange v0.2.1 h1:M07spnNEQoALOJhwrImSrJLaxwuiQK+hA2DeajBlwYk= +github.com/ckaznocha/intrange v0.2.1/go.mod h1:7NEhVyf8fzZO5Ds7CRaqPEm52Ut83hsTiL5zbER/HYk= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -135,8 +135,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA= @@ -147,10 +147,10 @@ github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwV github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/ghostiam/protogetter v0.3.6 h1:R7qEWaSgFCsy20yYHNIJsU9ZOb8TziSRRxuAOTVKeOk= -github.com/ghostiam/protogetter v0.3.6/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw= -github.com/go-critic/go-critic v0.11.4 h1:O7kGOCx0NDIni4czrkRIXTnit0mkyKOCePh3My6OyEU= -github.com/go-critic/go-critic v0.11.4/go.mod h1:2QAdo4iuLik5S9YG0rT4wcZ8QxwHYkrr6/2MWAiv/vc= +github.com/ghostiam/protogetter v0.3.8 h1:LYcXbYvybUyTIxN2Mj9h6rHrDZBDwZloPoKctWrFyJY= +github.com/ghostiam/protogetter v0.3.8/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA= +github.com/go-critic/go-critic v0.11.5 h1:TkDTOn5v7EEngMxu8KbuFqFR43USaaH8XRJLz1jhVYA= +github.com/go-critic/go-critic v0.11.5/go.mod h1:wu6U7ny9PiaHaZHcvMDmdysMqvDem162Rh3zWTrqk8M= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -186,8 +186,8 @@ github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQi github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= -github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w= -github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U= github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= @@ -226,10 +226,12 @@ 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/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= +github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU= +github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s= github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 h1:/1322Qns6BtQxUZDTAT4SdcoxknUki7IAoK4SAXr8ME= github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9/go.mod h1:Oesb/0uFAyWoaw1U1qS5zyjCg5NP9C9iwjnI4tIsXEE= -github.com/golangci/golangci-lint v1.61.0 h1:VvbOLaRVWmyxCnUIMTbf1kDsaJbTzH20FAMXTAlQGu8= -github.com/golangci/golangci-lint v1.61.0/go.mod h1:e4lztIrJJgLPhWvFPDkhiMwEFRrWlmFbrZea3FsJyN8= +github.com/golangci/golangci-lint v1.62.0 h1:/G0g+bi1BhmGJqLdNQkKBWjcim8HjOPc4tsKuHDOhcI= +github.com/golangci/golangci-lint v1.62.0/go.mod h1:jtoOhQcKTz8B6dGNFyfQV3WZkQk+YvBDewDtNpiAJts= github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= github.com/golangci/modinfo v0.3.4 h1:oU5huX3fbxqQXdfspamej74DFX0kyGLkw1ppvXoJ8GA= @@ -301,8 +303,6 @@ github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5 github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= -github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af h1:KA9BjwUk7KlCh6S9EAGWBt1oExIUv9WyNCiRz5amv48= -github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= github.com/jjti/go-spancheck v0.6.2 h1:iYtoxqPMzHUPp7St+5yA8+cONdyXD3ug6KK15n7Pklk= github.com/jjti/go-spancheck v0.6.2/go.mod h1:+X7lvIrR5ZdUTkxFYqzJ0abr8Sb5LOo80uOhWNqIrYA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= @@ -318,8 +318,8 @@ github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= github.com/karamaru-alpha/copyloopvar v1.1.0 h1:x7gNyKcC2vRBO1H2Mks5u1VxQtYvFiym7fCjIP8RPos= github.com/karamaru-alpha/copyloopvar v1.1.0/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= -github.com/kisielk/errcheck v1.7.0 h1:+SbscKmWJ5mOK/bO1zS60F5I9WwZDWOfRsC4RwfwRV0= -github.com/kisielk/errcheck v1.7.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= +github.com/kisielk/errcheck v1.8.0 h1:ZX/URYa7ilESY19ik/vBmCn6zdGQLxACwjAcWbHlYlg= +github.com/kisielk/errcheck v1.8.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkHAIKE/contextcheck v1.1.5 h1:CdnJh63tcDe53vG+RebdpdXJTc9atMgGqdx8LXxiilg= github.com/kkHAIKE/contextcheck v1.1.5/go.mod h1:O930cpht4xb1YQpK+1+AgoM3mFsvxr7uyFptcnWTYUA= @@ -339,16 +339,14 @@ github.com/kunwardeep/paralleltest v1.0.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCT github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= github.com/kyoh86/exportloopref v0.1.11 h1:1Z0bcmTypkL3Q4k+IDHMWTcnCliEZcaPiIe0/ymEyhQ= github.com/kyoh86/exportloopref v0.1.11/go.mod h1:qkV4UF1zGl6EkF1ox8L5t9SwyeBAZ3qLMd6up458uqA= -github.com/lasiar/canonicalheader v1.1.1 h1:wC+dY9ZfiqiPwAexUApFush/csSPXeIi4QqyxXmng8I= -github.com/lasiar/canonicalheader v1.1.1/go.mod h1:cXkb3Dlk6XXy+8MVQnF23CYKWlyA7kfQhSw2CcZtZb0= +github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= +github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= github.com/ldez/gomoddirectives v0.2.4 h1:j3YjBIjEBbqZ0NKtBNzr8rtMHTOrLPeiwTkfUJZ3alg= github.com/ldez/gomoddirectives v0.2.4/go.mod h1:oWu9i62VcQDYp9EQ0ONTfqLNh+mDLWWDO+SO0qSQw5g= github.com/ldez/tagliatelle v0.5.0 h1:epgfuYt9v0CG3fms0pEgIMNPuFf/LpPIfjk4kyqSioo= github.com/ldez/tagliatelle v0.5.0/go.mod h1:rj1HmWiL1MiKQuOONhd09iySTEkUuE/8+5jtPYz9xa4= github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= -github.com/lufeee/execinquery v1.2.1 h1:hf0Ems4SHcUGBxpGN7Jz78z1ppVkP/837ZlETPCEtOM= -github.com/lufeee/execinquery v1.2.1/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM= github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk= github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= @@ -366,12 +364,13 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mgechev/revive v1.3.9 h1:18Y3R4a2USSBF+QZKFQwVkBROUda7uoBlkEuBD+YD1A= -github.com/mgechev/revive v1.3.9/go.mod h1:+uxEIr5UH0TjXWHTno3xh4u7eg6jDpXKzQccA9UGhHU= +github.com/mgechev/revive v1.5.0 h1:oaSmjA7rP8+HyoRuCgC531VHwnLH1AlJdjj+1AnQceQ= +github.com/mgechev/revive v1.5.0/go.mod h1:L6T3H8EoerRO86c7WuGpvohIUmiploGiyoYbtIWFmV8= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -391,8 +390,8 @@ github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhK github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/nunnatsa/ginkgolinter v0.16.2 h1:8iLqHIZvN4fTLDC0Ke9tbSZVcyVHoBs0HIbnVSxfHJk= -github.com/nunnatsa/ginkgolinter v0.16.2/go.mod h1:4tWRinDN1FeJgU+iJANW/kz7xKN5nYRAOfJDQUS9dOQ= +github.com/nunnatsa/ginkgolinter v0.18.0 h1:ZXO1wKhPg3A6LpbN5dMuqwhfOjN5c3ous8YdKOuqk9k= +github.com/nunnatsa/ginkgolinter v0.18.0/go.mod h1:vPrWafSULmjMGCMsfGA908if95VnHQNAahvSBOjTuWs= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= @@ -452,9 +451,14 @@ github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= +github.com/raeperd/recvcheck v0.1.2 h1:SjdquRsRXJc26eSonWIo8b7IMtKD3OAT2Lb5G3ZX1+4= +github.com/raeperd/recvcheck v0.1.2/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.3.5 h1:cShyguSwUEeC0jS7ylOiG/idnd1TpJ1LfHGpV3oJmPU= github.com/ryancurrah/gomodguard v1.3.5/go.mod h1:MXlEPQRxgfPQa62O8wzK3Ozbkv9Rkqr+wKjSxTdsNJE= @@ -468,8 +472,8 @@ github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tM github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.27.0 h1:t/3jZpSXtRPRf2xr0m63i32ZrusyurIGT9E5wAvXQnI= github.com/sashamelentyev/usestdlibvars v1.27.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= -github.com/securego/gosec/v2 v2.21.2 h1:deZp5zmYf3TWwU7A7cR2+SolbTpZ3HQiwFqnzQyEl3M= -github.com/securego/gosec/v2 v2.21.2/go.mod h1:au33kg78rNseF5PwPnTWhuYBFf534bvJRvOrgZ/bFzU= +github.com/securego/gosec/v2 v2.21.4 h1:Le8MSj0PDmOnHJgUATjD96PaXRvCpKC+DGJvwyy0Mlk= +github.com/securego/gosec/v2 v2.21.4/go.mod h1:Jtb/MwRQfRxCXyCm1rfM1BEiiiTfUOdyzzAhlr6lUTA= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= @@ -481,10 +485,10 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= -github.com/sivchari/tenv v1.10.0 h1:g/hzMA+dBCKqGXgW8AV/1xIWhAvDrx0zFKNR48NFMg0= -github.com/sivchari/tenv v1.10.0/go.mod h1:tdY24masnVoZFxYrHv/nD6Tc8FbkEtAQEEziXpyMgqY= -github.com/sonatard/noctx v0.0.2 h1:L7Dz4De2zDQhW8S0t+KUjY0MAQJd6SgVwhzNIc4ok00= -github.com/sonatard/noctx v0.0.2/go.mod h1:kzFz+CzWSjQ2OzIm46uJZoXuBpa2+0y3T36U18dWqIo= +github.com/sivchari/tenv v1.12.1 h1:+E0QzjktdnExv/wwsnnyk4oqZBUfuh89YMQT1cyuvSY= +github.com/sivchari/tenv v1.12.1/go.mod h1:1LjSOUCc25snIr5n3DtGGrENhX3LuWefcplwVGC24mw= +github.com/sonatard/noctx v0.1.0 h1:JjqOc2WN16ISWAjAk8M5ej0RfExEXtkEyExl2hLW+OM= +github.com/sonatard/noctx v0.1.0/go.mod h1:0RvBxqY8D4j9cTTTWE8ylt2vqj2EPI8fHmrxHdsaZ2c= github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= @@ -527,12 +531,12 @@ github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= -github.com/tetafro/godot v1.4.17 h1:pGzu+Ye7ZUEFx7LHU0dAKmCOXWsPjl7qA6iMGndsjPs= -github.com/tetafro/godot v1.4.17/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= +github.com/tetafro/godot v1.4.18 h1:ouX3XGiziKDypbpXqShBfnNLTSjR8r3/HVzrtJ+bHlI= +github.com/tetafro/godot v1.4.18/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 h1:quvGphlmUVU+nhpFa4gg4yJyTRJ13reZMDHrKwYw53M= github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966/go.mod h1:27bSVNWSBOHm+qRp1T9qzaIpsWEP6TbUnei/43HK+PQ= -github.com/timonwong/loggercheck v0.9.4 h1:HKKhqrjcVj8sxL7K77beXh0adEm6DLjV/QOGeMXEVi4= -github.com/timonwong/loggercheck v0.9.4/go.mod h1:caz4zlPcgvpEkXgVnAJGowHAMW2NwHaNlpS8xDbVhTg= +github.com/timonwong/loggercheck v0.10.1 h1:uVZYClxQFpw55eh+PIoqM7uAOHMrhVcDoWDery9R8Lg= +github.com/timonwong/loggercheck v0.10.1/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= github.com/tomarrell/wrapcheck/v2 v2.9.0 h1:801U2YCAjLhdN8zhZ/7tdjB3EnAoRlJHt/s+9hijLQ4= github.com/tomarrell/wrapcheck/v2 v2.9.0/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= @@ -543,6 +547,8 @@ github.com/ultraware/whitespace v0.1.1 h1:bTPOGejYFulW3PkcrqkeQwOd6NKOOXvmGD9bo/ github.com/ultraware/whitespace v0.1.1/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= github.com/uudashr/gocognit v1.1.3 h1:l+a111VcDbKfynh+airAy/DJQKaXh2m9vkoysMPSZyM= github.com/uudashr/gocognit v1.1.3/go.mod h1:aKH8/e8xbTRBwjbCkwZ8qt4l2EpKXl31KMHgSS+lZ2U= +github.com/uudashr/iface v1.2.0 h1:ECJjh5q/1Zmnv/2yFpWV6H3oMg5+Mo+vL0aqw9Gjazo= +github.com/uudashr/iface v1.2.0/go.mod h1:Ux/7d/rAF3owK4m53cTVXL4YoVHKNqnoOeQHn2xrlp0= github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= @@ -562,8 +568,8 @@ gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= -go-simpler.org/musttag v0.12.2 h1:J7lRc2ysXOq7eM8rwaTYnNrHd5JwjppzB6mScysB2Cs= -go-simpler.org/musttag v0.12.2/go.mod h1:uN1DVIasMTQKk6XSik7yrJoEysGtR2GRqvWnI9S7TYM= +go-simpler.org/musttag v0.13.0 h1:Q/YAW0AHvaoaIbsPj3bvEI5/QFP7w696IMUpnKXQfCE= +go-simpler.org/musttag v0.13.0/go.mod h1:FTzIGeK6OkKlUDVpj0iQUXZLUO1Js9+mvykDQy9C5yM= go-simpler.org/sloglint v0.7.2 h1:Wc9Em/Zeuu7JYpl+oKoYOsQSy2X560aVueCW/m6IijY= go-simpler.org/sloglint v0.7.2/go.mod h1:US+9C80ppl7VsThQclkM7BkCHQAzuz8kHLsW3ppuluo= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -573,8 +579,8 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= -go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= +go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= @@ -599,12 +605,12 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= -golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8= -golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20240909161429-701f63a606c0 h1:bVwtbF629Xlyxk6xLQq2TDYmqP0uiWaet5LwRebuY0k= +golang.org/x/exp/typeparams v0.0.0-20240909161429-701f63a606c0/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -633,8 +639,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -673,8 +679,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= 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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -694,8 +700,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -749,8 +755,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -789,7 +795,6 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -832,8 +837,8 @@ golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= +golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= +golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From fdc3e10869bc27770914c8504058eff5d3c9e14b Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 11 Nov 2024 14:48:36 +0100 Subject: [PATCH 0203/1670] Rename test case Make it more clear that this is about the token stored in the session struct, not the token on disk. --- internal/broker/broker_test.go | 4 +--- .../data/.empty | 0 .../first_call | 0 3 files changed, 1 insertion(+), 3 deletions(-) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_newpassword_and_token_is_not_set => error_when_mode_is_newpassword_and_session_has_no_token}/data/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_newpassword_and_token_is_not_set => error_when_mode_is_newpassword_and_session_has_no_token}/first_call (100%) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index f238e3bb3d..eea00d9a15 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -498,9 +498,7 @@ func TestIsAuthenticated(t *testing.T) { }, }, "Error when empty challenge is provided for local password": {firstChallenge: "-", wantSecondCall: true, secondChallenge: "-"}, - "Error when mode is newpassword and token is not set": { - firstMode: authmodes.NewPassword, - }, + "Error when mode is newpassword and session has no token": {firstMode: authmodes.NewPassword}, "Error when mode is newpassword and id token is not set": { firstMode: authmodes.NewPassword, token: &tokenOptions{noIDToken: true}, diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_token_is_not_set/data/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_session_has_no_token/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_token_is_not_set/data/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_session_has_no_token/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_token_is_not_set/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_session_has_no_token/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_token_is_not_set/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_session_has_no_token/first_call From 21c91baee8ce0450c0c6ae1dbe33a2f6ec072bed Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 11 Nov 2024 14:52:33 +0100 Subject: [PATCH 0204/1670] Remove obsolete golden files These test cases were renamed previously and we forgot to remove the golden files. --- ..._not_fail_if_values_contain_a_single_template_delimiter | 4 ---- .../TestParseConfig/golden/successfully_parse_config_file | 4 ---- .../successfully_parse_config_file_with_optional_values | 7 ------- .../data/provider_url/test-user@email.com/password | 1 - .../data/provider_url/test-user@email.com/token.json | 1 - .../first_call | 3 --- .../second_call | 3 --- .../data/.empty | 0 .../first_call | 3 --- .../data/provider_url/test-user@email.com/password | 1 - .../data/provider_url/test-user@email.com/token.json | 1 - .../first_call | 3 --- .../second_call | 3 --- .../data/provider_url/test-user@email.com/password | 1 - .../data/provider_url/test-user@email.com/token.json | 1 - .../first_call | 3 --- .../second_call | 3 --- 17 files changed, 42 deletions(-) delete mode 100644 cmd/authd-oidc/daemon/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter delete mode 100644 cmd/authd-oidc/daemon/testdata/TestParseConfig/golden/successfully_parse_config_file delete mode 100644 cmd/authd-oidc/daemon/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values delete mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/data/provider_url/test-user@email.com/password delete mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/data/provider_url/test-user@email.com/token.json delete mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/first_call delete mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/second_call delete mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/data/.empty delete mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/first_call delete mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/data/provider_url/test-user@email.com/password delete mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/data/provider_url/test-user@email.com/token.json delete mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/first_call delete mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/second_call delete mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/data/provider_url/test-user@email.com/password delete mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/data/provider_url/test-user@email.com/token.json delete mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/first_call delete mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/second_call diff --git a/cmd/authd-oidc/daemon/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter b/cmd/authd-oidc/daemon/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter deleted file mode 100644 index 17a42fa4e9..0000000000 --- a/cmd/authd-oidc/daemon/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter +++ /dev/null @@ -1,4 +0,0 @@ -DEFAULT: {} -oidc: - client_id: diff --git a/cmd/authd-oidc/daemon/testdata/TestParseConfig/golden/successfully_parse_config_file b/cmd/authd-oidc/daemon/testdata/TestParseConfig/golden/successfully_parse_config_file deleted file mode 100644 index 622dbad7f1..0000000000 --- a/cmd/authd-oidc/daemon/testdata/TestParseConfig/golden/successfully_parse_config_file +++ /dev/null @@ -1,4 +0,0 @@ -DEFAULT: {} -oidc: - client_id: client_id - issuer: https://issuer.url.com diff --git a/cmd/authd-oidc/daemon/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values b/cmd/authd-oidc/daemon/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values deleted file mode 100644 index 53fe96579b..0000000000 --- a/cmd/authd-oidc/daemon/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values +++ /dev/null @@ -1,7 +0,0 @@ -DEFAULT: {} -oidc: - client_id: client_id - issuer: https://issuer.url.com -users: - allowed_ssh_suffixes: '@issuer.url.com' - home_base_dir: /home diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/data/provider_url/test-user@email.com/password deleted file mode 100644 index 119947240f..0000000000 --- a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/data/provider_url/test-user@email.com/password +++ /dev/null @@ -1 +0,0 @@ -Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/data/provider_url/test-user@email.com/token.json deleted file mode 100644 index 80ab783820..0000000000 --- a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/data/provider_url/test-user@email.com/token.json +++ /dev/null @@ -1 +0,0 @@ -Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/first_call deleted file mode 100644 index d0887a134f..0000000000 --- a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/first_call +++ /dev/null @@ -1,3 +0,0 @@ -access: next -data: '{}' -err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/second_call deleted file mode 100644 index 12ca5144ad..0000000000 --- a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_link_code_reacquires_token/second_call +++ /dev/null @@ -1,3 +0,0 @@ -access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' -err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/data/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/data/.empty deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/first_call deleted file mode 100644 index c47bb5dfc9..0000000000 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_can_not_fetch_user_info/first_call +++ /dev/null @@ -1,3 +0,0 @@ -access: denied -data: '{"message":"authentication failure: could not get required information"}' -err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/data/provider_url/test-user@email.com/password deleted file mode 100644 index 119947240f..0000000000 --- a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/data/provider_url/test-user@email.com/password +++ /dev/null @@ -1 +0,0 @@ -Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/data/provider_url/test-user@email.com/token.json deleted file mode 100644 index 80ab783820..0000000000 --- a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/data/provider_url/test-user@email.com/token.json +++ /dev/null @@ -1 +0,0 @@ -Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/first_call deleted file mode 100644 index d0887a134f..0000000000 --- a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/first_call +++ /dev/null @@ -1,3 +0,0 @@ -access: next -data: '{}' -err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/second_call deleted file mode 100644 index 12ca5144ad..0000000000 --- a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_link_code_and_newpassword/second_call +++ /dev/null @@ -1,3 +0,0 @@ -access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' -err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/data/provider_url/test-user@email.com/password deleted file mode 100644 index 119947240f..0000000000 --- a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/data/provider_url/test-user@email.com/password +++ /dev/null @@ -1 +0,0 @@ -Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/data/provider_url/test-user@email.com/token.json deleted file mode 100644 index 80ab783820..0000000000 --- a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/data/provider_url/test-user@email.com/token.json +++ /dev/null @@ -1 +0,0 @@ -Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/first_call deleted file mode 100644 index d0887a134f..0000000000 --- a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/first_call +++ /dev/null @@ -1,3 +0,0 @@ -access: next -data: '{}' -err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/second_call deleted file mode 100644 index 12ca5144ad..0000000000 --- a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode_and_newpassword/second_call +++ /dev/null @@ -1,3 +0,0 @@ -access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' -err: From 21b2ba7063cec75a302efe9616a4d303b10e3f72 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 11 Nov 2024 14:54:19 +0100 Subject: [PATCH 0205/1670] Remove obsolete test cases The ID token is not relevant for the newpassword mode, and neither is fetching the user info. --- internal/broker/broker_test.go | 8 -------- .../data/.empty | 0 .../first_call | 3 --- .../data/provider_url/test-user@email.com/password | 1 - .../data/provider_url/test-user@email.com/token.json | 1 - .../first_call | 3 --- 6 files changed, 16 deletions(-) delete mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_fetching_user_info_fails/data/.empty delete mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_fetching_user_info_fails/first_call delete mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/data/provider_url/test-user@email.com/password delete mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/data/provider_url/test-user@email.com/token.json delete mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/first_call diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index eea00d9a15..254f839dc0 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -499,14 +499,6 @@ func TestIsAuthenticated(t *testing.T) { }, "Error when empty challenge is provided for local password": {firstChallenge: "-", wantSecondCall: true, secondChallenge: "-"}, "Error when mode is newpassword and session has no token": {firstMode: authmodes.NewPassword}, - "Error when mode is newpassword and id token is not set": { - firstMode: authmodes.NewPassword, - token: &tokenOptions{noIDToken: true}, - }, - "Error when mode is newpassword and fetching user info fails": { - firstMode: authmodes.NewPassword, - getUserInfoFails: true, - }, // This test case also tests that errors with double quotes are marshaled to JSON correctly. "Error when selected username does not match the provider one": {username: "not-matching", firstChallenge: "-"}, } diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_fetching_user_info_fails/data/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_fetching_user_info_fails/data/.empty deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_fetching_user_info_fails/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_fetching_user_info_fails/first_call deleted file mode 100644 index c47bb5dfc9..0000000000 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_fetching_user_info_fails/first_call +++ /dev/null @@ -1,3 +0,0 @@ -access: denied -data: '{"message":"authentication failure: could not get required information"}' -err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/data/provider_url/test-user@email.com/password deleted file mode 100644 index 119947240f..0000000000 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/data/provider_url/test-user@email.com/password +++ /dev/null @@ -1 +0,0 @@ -Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/data/provider_url/test-user@email.com/token.json deleted file mode 100644 index 80ab783820..0000000000 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/data/provider_url/test-user@email.com/token.json +++ /dev/null @@ -1 +0,0 @@ -Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/first_call deleted file mode 100644 index c47bb5dfc9..0000000000 --- a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_id_token_is_not_set/first_call +++ /dev/null @@ -1,3 +0,0 @@ -access: denied -data: '{"message":"authentication failure: could not get required information"}' -err: From 29df1f9fc9f9990ed0fc7f210c780c5da891ee11 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 8 Nov 2024 18:09:05 +0100 Subject: [PATCH 0206/1670] Update golden files via the TESTS_UPDATE_GOLDEN env var This allows us to update all golden files in one go, without having to install the update flag per test package and deal with errors that the flag is not supported in some packages. --- cmd/authd-oidc/daemon/daemon_test.go | 9 --------- internal/broker/broker_test.go | 4 ---- internal/testutils/golden.go | 19 ++++++++++++------- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/cmd/authd-oidc/daemon/daemon_test.go b/cmd/authd-oidc/daemon/daemon_test.go index 4698ed2282..4f7141a90c 100644 --- a/cmd/authd-oidc/daemon/daemon_test.go +++ b/cmd/authd-oidc/daemon/daemon_test.go @@ -2,7 +2,6 @@ package daemon_test import ( "bytes" - "flag" "fmt" "io" "net/http/httptest" @@ -408,13 +407,5 @@ func TestMain(m *testing.M) { defer cleanup() mockProvider = providerServer - testutils.InstallUpdateFlag() - flag.Parse() - // Remove the flag from the command line arguments if it is present, to avoid that it's being parsed by the cobra - // command in the tests. - if testutils.Update() { - os.Args = os.Args[:len(os.Args)-1] - } - m.Run() } diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 254f839dc0..f39bff1d2d 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -2,7 +2,6 @@ package broker_test import ( "encoding/json" - "flag" "fmt" "net/http/httptest" "os" @@ -966,9 +965,6 @@ func TestUserPreCheck(t *testing.T) { } func TestMain(m *testing.M) { - testutils.InstallUpdateFlag() - flag.Parse() - server, cleanup := testutils.StartMockProvider("") defer cleanup() diff --git a/internal/testutils/golden.go b/internal/testutils/golden.go index adb7e3b134..0fd93b8f13 100644 --- a/internal/testutils/golden.go +++ b/internal/testutils/golden.go @@ -3,7 +3,6 @@ package testutils import ( "bytes" "errors" - "flag" "io/fs" "os" "path/filepath" @@ -17,6 +16,18 @@ import ( var update bool +const ( + // UpdateGoldenFilesEnv is the environment variable used to indicate go test that + // the golden files should be overwritten with the current test results. + UpdateGoldenFilesEnv = `TESTS_UPDATE_GOLDEN` +) + +func init() { + if os.Getenv(UpdateGoldenFilesEnv) != "" { + update = true + } +} + type goldenOptions struct { goldenPath string } @@ -251,12 +262,6 @@ func addEmptyMarker(p string) error { return err } -// InstallUpdateFlag install an update flag referenced in this package. -// The flags need to be parsed before running the tests. -func InstallUpdateFlag() { - flag.BoolVar(&update, "update", false, "update golden files") -} - // Update returns true if the update flag was set, false otherwise. func Update() bool { return update From 12e2a6c37e285c53c42d41e351ac88e134d8178c Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 8 Nov 2024 18:11:00 +0100 Subject: [PATCH 0207/1670] Rename Update to UpdateEnabled More descriptive and the same name already used in the authd repo. --- internal/broker/broker_test.go | 4 ++-- internal/broker/config_test.go | 2 +- internal/testutils/golden.go | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index f39bff1d2d..c1849f8be2 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -663,7 +663,7 @@ func TestIsAuthenticated(t *testing.T) { } } - testutils.CompareTreesWithFiltering(t, outDir, testutils.GoldenPath(t), testutils.Update()) + testutils.CompareTreesWithFiltering(t, outDir, testutils.GoldenPath(t), testutils.UpdateEnabled()) }) } } @@ -776,7 +776,7 @@ func TestConcurrentIsAuthenticated(t *testing.T) { t.Logf("Failed to rename issuer data directory: %v", err) } } - testutils.CompareTreesWithFiltering(t, outDir, testutils.GoldenPath(t), testutils.Update()) + testutils.CompareTreesWithFiltering(t, outDir, testutils.GoldenPath(t), testutils.UpdateEnabled()) }) } } diff --git a/internal/broker/config_test.go b/internal/broker/config_test.go index fb6411d086..7190238721 100644 --- a/internal/broker/config_test.go +++ b/internal/broker/config_test.go @@ -106,7 +106,7 @@ func TestParseConfig(t *testing.T) { err = os.WriteFile(filepath.Join(outDir, "config.txt"), []byte(strings.Join(fields, "\n")), 0600) require.NoError(t, err) - testutils.CompareTreesWithFiltering(t, outDir, testutils.GoldenPath(t), testutils.Update()) + testutils.CompareTreesWithFiltering(t, outDir, testutils.GoldenPath(t), testutils.UpdateEnabled()) }) } } diff --git a/internal/testutils/golden.go b/internal/testutils/golden.go index 0fd93b8f13..b822a821d7 100644 --- a/internal/testutils/golden.go +++ b/internal/testutils/golden.go @@ -127,7 +127,7 @@ func GoldenPath(t *testing.T) string { func CompareTreesWithFiltering(t *testing.T, p, goldPath string, update bool) { t.Helper() - // Update golden file + // UpdateEnabled golden file if update { t.Logf("updating golden file %s", goldPath) require.NoError(t, os.RemoveAll(goldPath), "Cannot remove target golden directory") @@ -262,7 +262,7 @@ func addEmptyMarker(p string) error { return err } -// Update returns true if the update flag was set, false otherwise. -func Update() bool { +// UpdateEnabled returns true if the update flag was set, false otherwise. +func UpdateEnabled() bool { return update } From 14e9f3b6ccd43864ebc21399637b9dcb6c671a29 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 7 Nov 2024 23:04:24 +0100 Subject: [PATCH 0208/1670] Replace spaces in test names with underscores To make it easier to copy/paste them into a `go test -run` command. --- cmd/authd-oidc/daemon/daemon_test.go | 6 +- cmd/authd-oidc/main_test.go | 16 +- internal/broker/broker_test.go | 158 +++++++++--------- internal/broker/config_test.go | 12 +- internal/fileutils/fileutils_test.go | 24 +-- internal/password/password_test.go | 14 +- internal/providers/info/info_test.go | 8 +- .../providers/msentraid/msentraid_test.go | 20 +-- internal/token/migration_test.go | 18 +- internal/token/token_test.go | 16 +- 10 files changed, 146 insertions(+), 146 deletions(-) diff --git a/cmd/authd-oidc/daemon/daemon_test.go b/cmd/authd-oidc/daemon/daemon_test.go index 4698ed2282..218655461c 100644 --- a/cmd/authd-oidc/daemon/daemon_test.go +++ b/cmd/authd-oidc/daemon/daemon_test.go @@ -116,9 +116,9 @@ func TestAppRunFailsOnComponentsCreationAndQuit(t *testing.T) { dataDirBehavior int configBehavior int }{ - "Error on existing data dir being a file": {dataDirBehavior: dirIsFile}, - "Error on data dir missing parent directory": {dataDirBehavior: noParentDir}, - "Error on wrong permission on data dir": {dataDirBehavior: wrongPermission}, + "Error_on_existing_data_dir_being_a_file": {dataDirBehavior: dirIsFile}, + "Error_on_data_dir_missing_parent_directory": {dataDirBehavior: noParentDir}, + "Error_on_wrong_permission_on_data_dir": {dataDirBehavior: wrongPermission}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { diff --git a/cmd/authd-oidc/main_test.go b/cmd/authd-oidc/main_test.go index 8e79d93628..5c7ed40bf1 100644 --- a/cmd/authd-oidc/main_test.go +++ b/cmd/authd-oidc/main_test.go @@ -49,16 +49,16 @@ func TestRun(t *testing.T) { wantReturnCode int }{ - "Run and exit successfully": {}, - "Run and return error": {runError: true, wantReturnCode: 1}, - "Run and return usage error": {usageErrorReturn: true, runError: true, wantReturnCode: 2}, - "Run and usage error only does not fail": {usageErrorReturn: true, runError: false, wantReturnCode: 0}, + "Run_and_exit_successfully": {}, + "Run_and_return_error": {runError: true, wantReturnCode: 1}, + "Run_and_return_usage_error": {usageErrorReturn: true, runError: true, wantReturnCode: 2}, + "Run_and_usage_error_only_does_not_fail": {usageErrorReturn: true, runError: false, wantReturnCode: 0}, // Signals handling - "Send SIGINT exits": {sendSig: syscall.SIGINT}, - "Send SIGTERM exits": {sendSig: syscall.SIGTERM}, - "Send SIGHUP without exiting": {sendSig: syscall.SIGHUP}, - "Send SIGHUP with exit": {sendSig: syscall.SIGHUP, hupReturn: true}, + "Send_SIGINT_exits": {sendSig: syscall.SIGINT}, + "Send_SIGTERM_exits": {sendSig: syscall.SIGTERM}, + "Send_SIGHUP_without_exiting": {sendSig: syscall.SIGHUP}, + "Send_SIGHUP_with_exit": {sendSig: syscall.SIGHUP, hupReturn: true}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 254f839dc0..8675c9da27 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -33,12 +33,12 @@ func TestNew(t *testing.T) { wantErr bool }{ - "Successfully create new broker": {}, - "Successfully create new even if can not connect to provider": {issuer: "https://notavailable"}, + "Successfully_create_new_broker": {}, + "Successfully_create_new_even_if_can_not_connect_to_provider": {issuer: "https://notavailable"}, - "Error if issuer is not provided": {issuer: "-", wantErr: true}, - "Error if clientID is not provided": {clientID: "-", wantErr: true}, - "Error if dataDir is not provided": {dataDir: "-", wantErr: true}, + "Error_if_issuer_is_not_provided": {issuer: "-", wantErr: true}, + "Error_if_clientID_is_not_provided": {clientID: "-", wantErr: true}, + "Error_if_dataDir_is_not_provided": {dataDir: "-", wantErr: true}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { @@ -85,14 +85,14 @@ func TestNewSession(t *testing.T) { wantOffline bool }{ - "Successfully create new session": {}, - "Creates new session in offline mode if provider is not available": { + "Successfully_create_new_session": {}, + "Creates_new_session_in_offline_mode_if_provider_is_not_available": { customHandlers: map[string]testutils.ProviderHandler{ "/.well-known/openid-configuration": testutils.UnavailableHandler(), }, wantOffline: true, }, - "Creates new session in offline mode if provider connection times out": { + "Creates_new_session_in_offline_mode_if_provider_connection_times_out": { customHandlers: map[string]testutils.ProviderHandler{ "/.well-known/openid-configuration": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, @@ -177,28 +177,28 @@ func TestGetAuthenticationModes(t *testing.T) { wantErr bool }{ // Auth Session - "Get device_auth_qr if there is no token": {}, - "Get newpassword if already authenticated with device_auth_qr": {secondAuthStep: true}, - "Get password and device_auth_qr if token exists": {tokenExists: true}, + "Get_device_auth_qr_if_there_is_no_token": {}, + "Get_newpassword_if_already_authenticated_with_device_auth_qr": {secondAuthStep: true}, + "Get_password_and_device_auth_qr_if_token_exists": {tokenExists: true}, - "Get only password if token exists and provider is not available": {tokenExists: true, providerAddress: "127.0.0.1:31310", unavailableProvider: true}, - "Get only password if token exists and provider does not support device_auth_qr": {tokenExists: true, providerAddress: "127.0.0.1:31311", deviceAuthUnsupported: true}, + "Get_only_password_if_token_exists_and_provider_is_not_available": {tokenExists: true, providerAddress: "127.0.0.1:31310", unavailableProvider: true}, + "Get_only_password_if_token_exists_and_provider_does_not_support_device_auth_qr": {tokenExists: true, providerAddress: "127.0.0.1:31311", deviceAuthUnsupported: true}, // Passwd Session - "Get only password if token exists and session is passwd": {sessionMode: "passwd", tokenExists: true}, - "Get newpassword if already authenticated with password and session is passwd": {sessionMode: "passwd", tokenExists: true, secondAuthStep: true}, + "Get_only_password_if_token_exists_and_session_is_passwd": {sessionMode: "passwd", tokenExists: true}, + "Get_newpassword_if_already_authenticated_with_password_and_session_is_passwd": {sessionMode: "passwd", tokenExists: true, secondAuthStep: true}, - "Error if there is no session": {sessionID: "-", wantErr: true}, + "Error_if_there_is_no_session": {sessionID: "-", wantErr: true}, // General errors - "Error if no authentication mode is supported": {providerAddress: "127.0.0.1:31312", deviceAuthUnsupported: true, wantErr: true}, - "Error if expecting device_auth_qr but not supported": {supportedLayouts: []string{"qrcode-without-wait"}, wantErr: true}, - "Error if expecting device_auth but not supported": {supportedLayouts: []string{"qrcode-without-wait-and-qrcode"}, wantErr: true}, - "Error if expecting newpassword but not supported": {supportedLayouts: []string{"newpassword-without-entry"}, wantErr: true}, - "Error if expecting password but not supported": {supportedLayouts: []string{"form-without-entry"}, wantErr: true}, + "Error_if_no_authentication_mode_is_supported": {providerAddress: "127.0.0.1:31312", deviceAuthUnsupported: true, wantErr: true}, + "Error_if_expecting_device_auth_qr_but_not_supported": {supportedLayouts: []string{"qrcode-without-wait"}, wantErr: true}, + "Error_if_expecting_device_auth_but_not_supported": {supportedLayouts: []string{"qrcode-without-wait-and-qrcode"}, wantErr: true}, + "Error_if_expecting_newpassword_but_not_supported": {supportedLayouts: []string{"newpassword-without-entry"}, wantErr: true}, + "Error_if_expecting_password_but_not_supported": {supportedLayouts: []string{"form-without-entry"}, wantErr: true}, // Passwd session errors - "Error if session is passwd but token does not exist": {sessionMode: "passwd", wantErr: true}, + "Error_if_session_is_passwd_but_token_does_not_exist": {sessionMode: "passwd", wantErr: true}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { @@ -292,20 +292,20 @@ func TestSelectAuthenticationMode(t *testing.T) { wantErr bool }{ - "Successfully select password": {modeName: authmodes.Password, tokenExists: true}, - "Successfully select device_auth_qr": {modeName: authmodes.DeviceQr}, - "Successfully select device_auth": {supportedLayouts: supportedLayoutsWithoutQrCode, modeName: authmodes.Device}, - "Successfully select newpassword": {modeName: authmodes.NewPassword, secondAuthStep: true}, + "Successfully_select_password": {modeName: authmodes.Password, tokenExists: true}, + "Successfully_select_device_auth_qr": {modeName: authmodes.DeviceQr}, + "Successfully_select_device_auth": {supportedLayouts: supportedLayoutsWithoutQrCode, modeName: authmodes.Device}, + "Successfully_select_newpassword": {modeName: authmodes.NewPassword, secondAuthStep: true}, - "Selected newpassword shows correct label in passwd session": {modeName: authmodes.NewPassword, passwdSession: true, tokenExists: true, secondAuthStep: true}, + "Selected_newpassword_shows_correct_label_in_passwd_session": {modeName: authmodes.NewPassword, passwdSession: true, tokenExists: true, secondAuthStep: true}, - "Error when selecting invalid mode": {modeName: "invalid", wantErr: true}, - "Error when selecting device_auth_qr but provider is unavailable": {modeName: authmodes.DeviceQr, wantErr: true, + "Error_when_selecting_invalid_mode": {modeName: "invalid", wantErr: true}, + "Error_when_selecting_device_auth_qr_but_provider_is_unavailable": {modeName: authmodes.DeviceQr, wantErr: true, customHandlers: map[string]testutils.ProviderHandler{ "/device_auth": testutils.UnavailableHandler(), }, }, - "Error when selecting device_auth but provider is unavailable": { + "Error_when_selecting_device_auth_but_provider_is_unavailable": { supportedLayouts: supportedLayoutsWithoutQrCode, modeName: authmodes.Device, customHandlers: map[string]testutils.ProviderHandler{ @@ -313,12 +313,12 @@ func TestSelectAuthenticationMode(t *testing.T) { }, wantErr: true, }, - "Error when selecting device_auth_qr but request times out": {modeName: authmodes.DeviceQr, wantErr: true, + "Error_when_selecting_device_auth_qr_but_request_times_out": {modeName: authmodes.DeviceQr, wantErr: true, customHandlers: map[string]testutils.ProviderHandler{ "/device_auth": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, }, - "Error when selecting device_auth but request times out": { + "Error_when_selecting_device_auth_but_request_times_out": { supportedLayouts: supportedLayoutsWithoutQrCode, modeName: authmodes.Device, customHandlers: map[string]testutils.ProviderHandler{ @@ -414,26 +414,26 @@ func TestIsAuthenticated(t *testing.T) { dontWaitForFirstCall bool readOnlyDataDir bool }{ - "Successfully authenticate user with QRCode+newpassword": {firstChallenge: "-", wantSecondCall: true}, - "Successfully authenticate user with password": {firstMode: authmodes.Password, token: &tokenOptions{}}, + "Successfully_authenticate_user_with_QRCode+newpassword": {firstChallenge: "-", wantSecondCall: true}, + "Successfully_authenticate_user_with_password": {firstMode: authmodes.Password, token: &tokenOptions{}}, - "Authenticating with qrcode reacquires token": {firstChallenge: "-", wantSecondCall: true, token: &tokenOptions{}}, - "Authenticating with password refreshes expired token": {firstMode: authmodes.Password, token: &tokenOptions{expired: true}}, - "Authenticating with password still allowed if server is unreachable": { + "Authenticating_with_qrcode_reacquires_token": {firstChallenge: "-", wantSecondCall: true, token: &tokenOptions{}}, + "Authenticating_with_password_refreshes_expired_token": {firstMode: authmodes.Password, token: &tokenOptions{expired: true}}, + "Authenticating_with_password_still_allowed_if_server_is_unreachable": { firstMode: authmodes.Password, token: &tokenOptions{}, customHandlers: map[string]testutils.ProviderHandler{ "/.well-known/openid-configuration": testutils.UnavailableHandler(), }, }, - "Authenticating with password still allowed if token is expired and server is unreachable": { + "Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable": { firstMode: authmodes.Password, token: &tokenOptions{expired: true}, customHandlers: map[string]testutils.ProviderHandler{ "/.well-known/openid-configuration": testutils.UnavailableHandler(), }, }, - "Authenticating still allowed if token is missing scopes": { + "Authenticating_still_allowed_if_token_is_missing_scopes": { firstChallenge: "-", wantSecondCall: true, customHandlers: map[string]testutils.ProviderHandler{ @@ -442,65 +442,65 @@ func TestIsAuthenticated(t *testing.T) { address: "127.0.0.1:31313", }, - "Error when authentication data is invalid": {invalidAuthData: true}, - "Error when challenge can not be decrypted": {firstMode: authmodes.Password, badFirstKey: true}, - "Error when provided wrong challenge": {firstMode: authmodes.Password, token: &tokenOptions{}, firstChallenge: "wrongpassword"}, - "Error when can not cache token": {firstChallenge: "-", wantSecondCall: true, readOnlyDataDir: true}, - "Error when IsAuthenticated is ongoing for session": {dontWaitForFirstCall: true, wantSecondCall: true}, + "Error_when_authentication_data_is_invalid": {invalidAuthData: true}, + "Error_when_challenge_can_not_be_decrypted": {firstMode: authmodes.Password, badFirstKey: true}, + "Error_when_provided_wrong_challenge": {firstMode: authmodes.Password, token: &tokenOptions{}, firstChallenge: "wrongpassword"}, + "Error_when_can_not_cache_token": {firstChallenge: "-", wantSecondCall: true, readOnlyDataDir: true}, + "Error_when_IsAuthenticated_is_ongoing_for_session": {dontWaitForFirstCall: true, wantSecondCall: true}, - "Error when mode is password and token does not exist": {firstMode: authmodes.Password}, - "Error when mode is password but server returns error": { + "Error_when_mode_is_password_and_token_does_not_exist": {firstMode: authmodes.Password}, + "Error_when_mode_is_password_but_server_returns_error": { firstMode: authmodes.Password, token: &tokenOptions{expired: true}, customHandlers: map[string]testutils.ProviderHandler{ "/token": testutils.BadRequestHandler(), }, }, - "Error when mode is password and token is invalid": {firstMode: authmodes.Password, token: &tokenOptions{invalid: true}}, - "Error when token is expired and refreshing token fails": {firstMode: authmodes.Password, token: &tokenOptions{expired: true, noRefreshToken: true}}, - "Error when mode is password and token refresh times out": {firstMode: authmodes.Password, token: &tokenOptions{expired: true}, + "Error_when_mode_is_password_and_token_is_invalid": {firstMode: authmodes.Password, token: &tokenOptions{invalid: true}}, + "Error_when_token_is_expired_and_refreshing_token_fails": {firstMode: authmodes.Password, token: &tokenOptions{expired: true, noRefreshToken: true}}, + "Error_when_mode_is_password_and_token_refresh_times_out": {firstMode: authmodes.Password, token: &tokenOptions{expired: true}, customHandlers: map[string]testutils.ProviderHandler{ "/token": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, }, - "Error when existing token has no user info and fetching user info fails": {firstMode: authmodes.Password, token: &tokenOptions{noUserInfo: true}, getUserInfoFails: true}, + "Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails": {firstMode: authmodes.Password, token: &tokenOptions{noUserInfo: true}, getUserInfoFails: true}, - "Error when mode is qrcode and response is invalid": {firstAuthInfo: map[string]any{"response": "not a valid response"}}, - "Error when mode is qrcode and link expires": { + "Error_when_mode_is_qrcode_and_response_is_invalid": {firstAuthInfo: map[string]any{"response": "not a valid response"}}, + "Error_when_mode_is_qrcode_and_link_expires": { customHandlers: map[string]testutils.ProviderHandler{ "/device_auth": testutils.ExpiryDeviceAuthHandler(), }, }, - "Error when mode is qrcode and can not get token": { + "Error_when_mode_is_qrcode_and_can_not_get_token": { customHandlers: map[string]testutils.ProviderHandler{ "/token": testutils.UnavailableHandler(), }, }, - "Error when mode is qrcode and can not get token due to timeout": { + "Error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout": { customHandlers: map[string]testutils.ProviderHandler{ "/token": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, }, - "Error when mode is link code and response is invalid": {firstAuthInfo: map[string]any{"response": "not a valid response"}}, - "Error when mode is link code and link expires": { + "Error_when_mode_is_link_code_and_response_is_invalid": {firstAuthInfo: map[string]any{"response": "not a valid response"}}, + "Error_when_mode_is_link_code_and_link_expires": { customHandlers: map[string]testutils.ProviderHandler{ "/device_auth": testutils.ExpiryDeviceAuthHandler(), }, }, - "Error when mode is link code and can not get token": { + "Error_when_mode_is_link_code_and_can_not_get_token": { customHandlers: map[string]testutils.ProviderHandler{ "/token": testutils.UnavailableHandler(), }, }, - "Error when mode is link code and can not get token due to timeout": { + "Error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout": { customHandlers: map[string]testutils.ProviderHandler{ "/token": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, }, - "Error when empty challenge is provided for local password": {firstChallenge: "-", wantSecondCall: true, secondChallenge: "-"}, - "Error when mode is newpassword and session has no token": {firstMode: authmodes.NewPassword}, + "Error_when_empty_challenge_is_provided_for_local_password": {firstChallenge: "-", wantSecondCall: true, secondChallenge: "-"}, + "Error_when_mode_is_newpassword_and_session_has_no_token": {firstMode: authmodes.NewPassword}, // This test case also tests that errors with double quotes are marshaled to JSON correctly. - "Error when selected username does not match the provider one": {username: "not-matching", firstChallenge: "-"}, + "Error_when_selected_username_does_not_match_the_provider_one": {username: "not-matching", firstChallenge: "-"}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { @@ -677,9 +677,9 @@ func TestConcurrentIsAuthenticated(t *testing.T) { timeBetween time.Duration }{ - "First auth starts and finishes before second": {secondCallDelay: 1, timeBetween: 2 * time.Second}, - "First auth starts first but second finishes first": {firstCallDelay: 3, timeBetween: time.Second}, - "First auth starts first then second starts and first finishes": {firstCallDelay: 2, secondCallDelay: 3, timeBetween: time.Second}, + "First_auth_starts_and_finishes_before_second": {secondCallDelay: 1, timeBetween: 2 * time.Second}, + "First_auth_starts_first_but_second_finishes_first": {firstCallDelay: 3, timeBetween: time.Second}, + "First_auth_starts_first_then_second_starts_and_first_finishes": {firstCallDelay: 2, secondCallDelay: 3, timeBetween: time.Second}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { @@ -794,15 +794,15 @@ func TestFetchUserInfo(t *testing.T) { wantGroupErr bool wantErr bool }{ - "Successfully fetch user info with groups": {}, - "Successfully fetch user info without groups": {emptyGroups: true}, - "Successfully fetch user info with default home when not provided": {emptyHomeDir: true}, - - "Error when token can not be validated": {token: tokenOptions{invalid: true}, wantErr: true}, - "Error when ID token claims are invalid": {token: tokenOptions{invalidClaims: true}, wantErr: true}, - "Error when username is not configured": {token: tokenOptions{username: "-"}, wantErr: true}, - "Error when username is different than the requested one": {token: tokenOptions{username: "other-user@email.com"}, wantErr: true}, - "Error when getting user groups": {wantGroupErr: true, wantErr: true}, + "Successfully_fetch_user_info_with_groups": {}, + "Successfully_fetch_user_info_without_groups": {emptyGroups: true}, + "Successfully_fetch_user_info_with_default_home_when_not_provided": {emptyHomeDir: true}, + + "Error_when_token_can_not_be_validated": {token: tokenOptions{invalid: true}, wantErr: true}, + "Error_when_ID_token_claims_are_invalid": {token: tokenOptions{invalidClaims: true}, wantErr: true}, + "Error_when_username_is_not_configured": {token: tokenOptions{username: "-"}, wantErr: true}, + "Error_when_username_is_different_than_the_requested_one": {token: tokenOptions{username: "other-user@email.com"}, wantErr: true}, + "Error_when_getting_user_groups": {wantGroupErr: true, wantErr: true}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { @@ -915,30 +915,30 @@ func TestUserPreCheck(t *testing.T) { wantErr bool }{ - "Successfully allow username with matching allowed suffix": { + "Successfully_allow_username_with_matching_allowed_suffix": { username: "user@allowed", allowedSuffixes: []string{"@allowed"}}, - "Successfully allow username that matches at least one allowed suffix": { + "Successfully_allow_username_that_matches_at_least_one_allowed_suffix": { username: "user@allowed", allowedSuffixes: []string{"@other", "@something", "@allowed"}, }, - "Return userinfo with correct homedir after precheck": { + "Return_userinfo_with_correct_homedir_after_precheck": { username: "user@allowed", allowedSuffixes: []string{"@allowed"}, homePrefix: "/home/allowed/", }, - "Error when username does not match allowed suffix": { + "Error_when_username_does_not_match_allowed_suffix": { username: "user@notallowed", allowedSuffixes: []string{"@allowed"}, wantErr: true, }, - "Error when username does not match any of the allowed suffixes": { + "Error_when_username_does_not_match_any_of_the_allowed_suffixes": { username: "user@notallowed", allowedSuffixes: []string{"@other", "@something", "@allowed"}, wantErr: true, }, - "Error when no allowed suffixes are provided": { + "Error_when_no_allowed_suffixes_are_provided": { username: "user@allowed", wantErr: true, }, diff --git a/internal/broker/config_test.go b/internal/broker/config_test.go index fb6411d086..4ee8a401ce 100644 --- a/internal/broker/config_test.go +++ b/internal/broker/config_test.go @@ -51,14 +51,14 @@ func TestParseConfig(t *testing.T) { wantErr bool }{ - "Successfully parse config file": {}, - "Successfully parse config file with optional values": {configType: "valid+optional"}, + "Successfully_parse_config_file": {}, + "Successfully_parse_config_file_with_optional_values": {configType: "valid+optional"}, - "Do not fail if values contain a single template delimiter": {configType: "singles"}, + "Do_not_fail_if_values_contain_a_single_template_delimiter": {configType: "singles"}, - "Error if file does not exist": {configType: "inexistent", wantErr: true}, - "Error if file is unreadable": {configType: "unreadable", wantErr: true}, - "Error if file is not updated": {configType: "template", wantErr: true}, + "Error_if_file_does_not_exist": {configType: "inexistent", wantErr: true}, + "Error_if_file_is_unreadable": {configType: "unreadable", wantErr: true}, + "Error_if_file_is_not_updated": {configType: "template", wantErr: true}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { diff --git a/internal/fileutils/fileutils_test.go b/internal/fileutils/fileutils_test.go index d1e54dbf49..b288272f5d 100644 --- a/internal/fileutils/fileutils_test.go +++ b/internal/fileutils/fileutils_test.go @@ -20,11 +20,11 @@ func TestFileExists(t *testing.T) { wantExists bool wantError bool }{ - "Returns true when file exists": {fileExists: true, wantExists: true}, - "Returns false when file does not exist": {fileExists: false, wantExists: false}, - "Returns false when parent directory does not exist": {fileExists: false, wantExists: false}, + "Returns_true_when_file_exists": {fileExists: true, wantExists: true}, + "Returns_false_when_file_does_not_exist": {fileExists: false, wantExists: false}, + "Returns_false_when_parent_directory_does_not_exist": {fileExists: false, wantExists: false}, - "Error when parent directory is a file": {parentDirIsFile: true, wantError: true}, + "Error_when_parent_directory_is_a_file": {parentDirIsFile: true, wantError: true}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { @@ -64,11 +64,11 @@ func TestIsDirEmpty(t *testing.T) { wantEmpty bool wantError bool }{ - "Returns true when directory is empty": {isEmpty: true, wantEmpty: true}, - "Returns false when directory is not empty": {wantEmpty: false}, + "Returns_true_when_directory_is_empty": {isEmpty: true, wantEmpty: true}, + "Returns_false_when_directory_is_not_empty": {wantEmpty: false}, - "Error when directory does not exist": {doesNotExist: true, wantError: true}, - "Error when directory is a file": {isFile: true, wantError: true}, + "Error_when_directory_does_not_exist": {doesNotExist: true, wantError: true}, + "Error_when_directory_is_a_file": {isFile: true, wantError: true}, } for name, tc := range tests { @@ -115,11 +115,11 @@ func TestTouch(t *testing.T) { wantError bool }{ - "Creates file when it does not exist": {fileExists: false}, - "Does not return error when file already exists": {fileExists: true}, + "Creates_file_when_it_does_not_exist": {fileExists: false}, + "Does_not_return_error_when_file_already_exists": {fileExists: true}, - "Returns error when file is a directory": {fileIsDir: true, wantError: true}, - "Returns error when parent directory does not exist": {parentDoesNotExist: true, wantError: true}, + "Returns_error_when_file_is_a_directory": {fileIsDir: true, wantError: true}, + "Returns_error_when_parent_directory_does_not_exist": {parentDoesNotExist: true, wantError: true}, } for _, tc := range tests { diff --git a/internal/password/password_test.go b/internal/password/password_test.go index 9632098f68..02d92a9b6a 100644 --- a/internal/password/password_test.go +++ b/internal/password/password_test.go @@ -22,9 +22,9 @@ func TestHashAndStorePassword(t *testing.T) { wantErr bool }{ - "Success when password file and parent dir don't exist yet": {password: "test123"}, - "Success when parent directory already exists": {password: "test123", parentDirExists: true}, - "Success when password file already exists": {password: "test123", pathExists: true}, + "Success_when_password_file_and_parent_dir_don't_exist_yet": {password: "test123"}, + "Success_when_parent_directory_already_exists": {password: "test123", parentDirExists: true}, + "Success_when_password_file_already_exists": {password: "test123", pathExists: true}, } for name, tc := range tests { @@ -67,11 +67,11 @@ func TestCheckPassword(t *testing.T) { wantMatch bool expectedError error }{ - "Success when password matches": {password: "test123", wantMatch: true}, - "No match when password doesn't match": {password: "not-test123", wantMatch: false}, + "Success_when_password_matches": {password: "test123", wantMatch: true}, + "No_match_when_password_doesn't_match": {password: "not-test123", wantMatch: false}, - "Error when password file doesn't exist": {password: "test123", pathToRead: "nonexistent", expectedError: os.ErrNotExist}, - "Error when password file contains garbage": {password: "test123", writeGarbage: true, expectedError: base64.CorruptInputError(0)}, + "Error_when_password_file_doesn't_exist": {password: "test123", pathToRead: "nonexistent", expectedError: os.ErrNotExist}, + "Error_when_password_file_contains_garbage": {password: "test123", writeGarbage: true, expectedError: base64.CorruptInputError(0)}, } for name, tc := range tests { diff --git a/internal/providers/info/info_test.go b/internal/providers/info/info_test.go index 7a65bfe29e..564b4e4d1a 100644 --- a/internal/providers/info/info_test.go +++ b/internal/providers/info/info_test.go @@ -18,7 +18,7 @@ func TestNewUser(t *testing.T) { gecos string groups []info.Group }{ - "Create a new user": { + "Create_a_new_user": { name: "test-user", home: "/home/test-user", uuid: "some-uuid", @@ -28,7 +28,7 @@ func TestNewUser(t *testing.T) { }, // Default values - "Create a new user with default home": { + "Create_a_new_user_with_default_home": { name: "test-user", home: "", uuid: "some-uuid", @@ -36,7 +36,7 @@ func TestNewUser(t *testing.T) { gecos: "Test User", groups: []info.Group{{Name: "test-group", UGID: "12345"}}, }, - "Create a new user with default shell": { + "Create_a_new_user_with_default_shell": { name: "test-user", home: "/home/test-user", uuid: "some-uuid", @@ -44,7 +44,7 @@ func TestNewUser(t *testing.T) { gecos: "Test User", groups: []info.Group{{Name: "test-group", UGID: "12345"}}, }, - "Create a new user with default gecos": {name: "test-user", + "Create_a_new_user_with_default_gecos": {name: "test-user", home: "/home/test-user", uuid: "some-uuid", shell: "/usr/bin/zsh", diff --git a/internal/providers/msentraid/msentraid_test.go b/internal/providers/msentraid/msentraid_test.go index 5295e7ba74..ce3a12ee4a 100644 --- a/internal/providers/msentraid/msentraid_test.go +++ b/internal/providers/msentraid/msentraid_test.go @@ -23,12 +23,12 @@ func TestCheckTokenScopes(t *testing.T) { wantErr bool }{ - "Success when checking all scopes are present": {scopes: msentraid.AllExpectedScopes()}, - "Success even if getting more scopes than requested": {scopes: msentraid.AllExpectedScopes() + " extra-scope"}, + "Success_when_checking_all_scopes_are_present": {scopes: msentraid.AllExpectedScopes()}, + "Success_even_if_getting_more_scopes_than_requested": {scopes: msentraid.AllExpectedScopes() + " extra-scope"}, - "Error with missing scopes": {scopes: "profile email", wantErr: true}, - "Error without extra scope field": {noExtraScopeField: true, wantErr: true}, - "Error with empty scopes": {scopes: "", wantErr: true}, + "Error_with_missing_scopes": {scopes: "profile email", wantErr: true}, + "Error_without_extra_scope_field": {noExtraScopeField: true, wantErr: true}, + "Error_with_empty_scopes": {scopes: "", wantErr: true}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { @@ -61,14 +61,14 @@ func TestVerifyUsername(t *testing.T) { wantErr bool }{ - "Success when usernames are the same": {requestedUsername: "foo-bar@example", authenticatedUser: "foo-bar@example"}, - "Success when usernames differ in case": {requestedUsername: "foo-bar@example", authenticatedUser: "Foo-Bar@example"}, + "Success_when_usernames_are_the_same": {requestedUsername: "foo-bar@example", authenticatedUser: "foo-bar@example"}, + "Success_when_usernames_differ_in_case": {requestedUsername: "foo-bar@example", authenticatedUser: "Foo-Bar@example"}, - "Error when usernames differ": {requestedUsername: "foo@example", authenticatedUser: "bar@foo", wantErr: true}, - "Error when requested username contains invalid characters": { + "Error_when_usernames_differ": {requestedUsername: "foo@example", authenticatedUser: "bar@foo", wantErr: true}, + "Error_when_requested_username_contains_invalid_characters": { requestedUsername: "fóó@example", authenticatedUser: "foo@example", wantErr: true, }, - "Error when authenticated username contains invalid characters": { + "Error_when_authenticated_username_contains_invalid_characters": { requestedUsername: "foo@example", authenticatedUser: "fóó@example", wantErr: true, }, } diff --git a/internal/token/migration_test.go b/internal/token/migration_test.go index b81f388390..61508406ca 100644 --- a/internal/token/migration_test.go +++ b/internal/token/migration_test.go @@ -26,12 +26,12 @@ func TestUseOldEncryptedToken(t *testing.T) { wantRet bool wantError bool }{ - "Success when both the password file and the new token file exist": {passwordFileExists: true, newTokenFileExists: true, wantRet: false}, - "Success when old encrypted token file exists": {oldEncryptedTokenFileExists: true, wantRet: true, wantError: false}, + "Success_when_both_the_password_file_and_the_new_token_file_exist": {passwordFileExists: true, newTokenFileExists: true, wantRet: false}, + "Success_when_old_encrypted_token_file_exists": {oldEncryptedTokenFileExists: true, wantRet: true, wantError: false}, - "Error if only the password file exists": {passwordFileExists: true, wantRet: false, wantError: true}, - "Error if only the new token file exists": {newTokenFileExists: true, wantRet: false, wantError: true}, - "Error if neither the password file nor the old encrypted token file exists": {wantError: true}, + "Error_if_only_the_password_file_exists": {passwordFileExists: true, wantRet: false, wantError: true}, + "Error_if_only_the_new_token_file_exists": {newTokenFileExists: true, wantRet: false, wantError: true}, + "Error_if_neither_the_password_file_nor_the_old_encrypted_token_file_exists": {wantError: true}, } for name, tc := range tests { @@ -77,10 +77,10 @@ func TestLoadOldEncryptedAuthInfo(t *testing.T) { wantToken token.AuthCachedInfo wantError bool }{ - "Successfully load old encrypted token": {wantToken: testToken, wantError: false}, - "Error when file does not exist": {noOldToken: true, wantError: true}, - "Error when file contains invalid data": {invalidData: true, wantError: true}, - "Error when password is incorrect": {incorrectPassword: true, wantError: true}, + "Successfully_load_old_encrypted_token": {wantToken: testToken, wantError: false}, + "Error_when_file_does_not_exist": {noOldToken: true, wantError: true}, + "Error_when_file_contains_invalid_data": {invalidData: true, wantError: true}, + "Error_when_password_is_incorrect": {incorrectPassword: true, wantError: true}, } for name, tc := range tests { diff --git a/internal/token/token_test.go b/internal/token/token_test.go index 7d1fd1f56a..d83af9fb51 100644 --- a/internal/token/token_test.go +++ b/internal/token/token_test.go @@ -40,12 +40,12 @@ func TestCacheAuthInfo(t *testing.T) { wantError bool }{ - "Successfully store token with non-existing parent directory": {}, - "Successfully store token with existing parent directory": {existingParentDir: true}, - "Successfully store token with existing file": {existingParentDir: true, existingFile: true}, + "Successfully_store_token_with_non-existing_parent_directory": {}, + "Successfully_store_token_with_existing_parent_directory": {existingParentDir: true}, + "Successfully_store_token_with_existing_file": {existingParentDir: true, existingFile: true}, - "Error when file exists and is a directory": {existingParentDir: true, existingFile: true, fileIsDir: true, wantError: true}, - "Error when parent directory is a file": {existingParentDir: true, parentIsFile: true, wantError: true}, + "Error_when_file_exists_and_is_a_directory": {existingParentDir: true, existingFile: true, fileIsDir: true, wantError: true}, + "Error_when_parent_directory_is_a_file": {existingParentDir: true, parentIsFile: true, wantError: true}, } for name, tc := range tests { @@ -92,9 +92,9 @@ func TestLoadAuthInfo(t *testing.T) { wantError bool }{ - "Successfully load token from existing file": {fileExists: true, expectedRet: testToken}, - "Error when file does not exist": {wantError: true}, - "Error when file contains invalid JSON": {fileExists: true, invalidJSON: true, wantError: true}, + "Successfully_load_token_from_existing_file": {fileExists: true, expectedRet: testToken}, + "Error_when_file_does_not_exist": {wantError: true}, + "Error_when_file_contains_invalid_JSON": {fileExists: true, invalidJSON: true, wantError: true}, } for name, tc := range tests { From dd06a0d11e84aea43b79db55709b9bbc74aed7e5 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 7 Nov 2024 23:06:41 +0100 Subject: [PATCH 0209/1670] Replace word boundary characters in test names To make it easier to copy/paste them. --- internal/broker/broker_test.go | 4 ++-- .../data/provider_url/test-user@email.com/password | 0 .../data/provider_url/test-user@email.com/token.json | 0 .../first_call | 0 .../second_call | 0 5 files changed, 2 insertions(+), 2 deletions(-) rename internal/broker/testdata/TestIsAuthenticated/golden/{successfully_authenticate_user_with_qrcode+newpassword => successfully_authenticate_user_with_device_auth_and_newpassword}/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{successfully_authenticate_user_with_qrcode+newpassword => successfully_authenticate_user_with_device_auth_and_newpassword}/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{successfully_authenticate_user_with_qrcode+newpassword => successfully_authenticate_user_with_device_auth_and_newpassword}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{successfully_authenticate_user_with_qrcode+newpassword => successfully_authenticate_user_with_device_auth_and_newpassword}/second_call (100%) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 8675c9da27..0a5315dd80 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -414,8 +414,8 @@ func TestIsAuthenticated(t *testing.T) { dontWaitForFirstCall bool readOnlyDataDir bool }{ - "Successfully_authenticate_user_with_QRCode+newpassword": {firstChallenge: "-", wantSecondCall: true}, - "Successfully_authenticate_user_with_password": {firstMode: authmodes.Password, token: &tokenOptions{}}, + "Successfully_authenticate_user_with_device_auth_and_newpassword": {firstChallenge: "-", wantSecondCall: true}, + "Successfully_authenticate_user_with_password": {firstMode: authmodes.Password, token: &tokenOptions{}}, "Authenticating_with_qrcode_reacquires_token": {firstChallenge: "-", wantSecondCall: true, token: &tokenOptions{}}, "Authenticating_with_password_refreshes_expired_token": {firstMode: authmodes.Password, token: &tokenOptions{expired: true}}, diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_device_auth_and_newpassword/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_device_auth_and_newpassword/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_device_auth_and_newpassword/second_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_qrcode+newpassword/second_call rename to internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_device_auth_and_newpassword/second_call From 9415c6af4d4fe84014ecc28884ac96ed8399ebdc Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Tue, 12 Nov 2024 10:31:18 +0100 Subject: [PATCH 0210/1670] Optional client secret for some providers Some providers, like google, request a client secret for the device authentication. Make it optional, so that the admin can set it if required in the generic broker. There is no integration tests for now that could make use of it, but all existing tests are passing. I have also tested manually with google and msentraid. UDENG-4954 --- conf/broker.conf | 3 +++ internal/broker/broker.go | 8 +++++--- internal/broker/config.go | 3 +++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/conf/broker.conf b/conf/broker.conf index 0c60684123..344aecbb05 100644 --- a/conf/broker.conf +++ b/conf/broker.conf @@ -1,6 +1,9 @@ [oidc] issuer = https:// client_id = +# Client secret is needed for some specific auth flows depending on the provider. +# Only enable it if this is needed for your particular configuration. +# client_secret = [users] # The directory where the home directory will be created for new users. diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 26c6c9f24f..1dd2bfb30e 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -47,6 +47,7 @@ type Config struct { type userConfig struct { clientID string + clientSecret string issuerURL string homeBaseDir string allowedSSHSuffixes []string @@ -213,9 +214,10 @@ func (b *Broker) connectToProvider(ctx context.Context) (authCfg authConfig, err } oauthCfg := oauth2.Config{ - ClientID: b.oidcCfg.ClientID, - Endpoint: provider.Endpoint(), - Scopes: append(consts.DefaultScopes, b.providerInfo.AdditionalScopes()...), + ClientID: b.oidcCfg.ClientID, + ClientSecret: b.cfg.clientSecret, + Endpoint: provider.Endpoint(), + Scopes: append(consts.DefaultScopes, b.providerInfo.AdditionalScopes()...), } return authConfig{provider: provider, oauth: oauthCfg}, nil diff --git a/internal/broker/config.go b/internal/broker/config.go index 6cf224cb99..772bf32e91 100644 --- a/internal/broker/config.go +++ b/internal/broker/config.go @@ -16,6 +16,8 @@ const ( issuerKey = "issuer" // clientIDKey is the key in the config file for the client ID. clientIDKey = "client_id" + // clientSecret is the optional client secret for this client. + clientSecret = "client_secret" // usersSection is the section name in the config file for the users and broker specific configuration. usersSection = "users" @@ -50,6 +52,7 @@ func parseConfigFile(cfgPath string) (userConfig, error) { if oidc != nil { cfg.issuerURL = oidc.Key(issuerKey).String() cfg.clientID = oidc.Key(clientIDKey).String() + cfg.clientSecret = oidc.Key(clientSecret).String() } users := iniCfg.Section(usersSection) From 0b2ef1f6e31b5437b0e81ffe6f8b784e11028ca0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Nov 2024 10:28:06 +0000 Subject: [PATCH 0211/1670] deps(go): bump golang.org/x/oauth2 from 0.23.0 to 0.24.0 Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.23.0 to 0.24.0. - [Commits](https://github.com/golang/oauth2/compare/v0.23.0...v0.24.0) --- updated-dependencies: - dependency-name: golang.org/x/oauth2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6edefe1adb..97e74f394b 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 golang.org/x/crypto v0.29.0 - golang.org/x/oauth2 v0.23.0 + golang.org/x/oauth2 v0.24.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 790418609e..a2c6ac3672 100644 --- a/go.sum +++ b/go.sum @@ -142,8 +142,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= +golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= From d2cc6713ed82a220f7a532cef9852b5d3914191a Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Tue, 12 Nov 2024 14:15:28 +0100 Subject: [PATCH 0212/1670] Update golden files for config with new optional secret --- .../config.txt | 1 + .../golden/successfully_parse_config_file/config.txt | 1 + .../config.txt | 1 + 3 files changed, 3 insertions(+) diff --git a/internal/broker/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter/config.txt b/internal/broker/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter/config.txt index 29d43c2f42..ac876a0035 100644 --- a/internal/broker/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter/config.txt +++ b/internal/broker/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter/config.txt @@ -1,4 +1,5 @@ clientID= homeBaseDir= allowedSSHSuffixes=[] \ No newline at end of file diff --git a/internal/broker/testdata/TestParseConfig/golden/successfully_parse_config_file/config.txt b/internal/broker/testdata/TestParseConfig/golden/successfully_parse_config_file/config.txt index 9e322c42ee..ba4609b6d2 100644 --- a/internal/broker/testdata/TestParseConfig/golden/successfully_parse_config_file/config.txt +++ b/internal/broker/testdata/TestParseConfig/golden/successfully_parse_config_file/config.txt @@ -1,4 +1,5 @@ clientID=client_id +clientSecret= issuerURL=https://issuer.url.com homeBaseDir= allowedSSHSuffixes=[] \ No newline at end of file diff --git a/internal/broker/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values/config.txt b/internal/broker/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values/config.txt index 0fac62eeb2..6ad8bf0549 100644 --- a/internal/broker/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values/config.txt +++ b/internal/broker/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values/config.txt @@ -1,4 +1,5 @@ clientID=client_id +clientSecret= issuerURL=https://issuer.url.com homeBaseDir=/home allowedSSHSuffixes=[] \ No newline at end of file From a5a11fd9ffd98727a34b6001912285ce43426e28 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 7 Nov 2024 23:06:41 +0100 Subject: [PATCH 0213/1670] Replace word boundary characters in test names To make it easier to copy/paste them. --- internal/password/password_test.go | 12 ++++++------ internal/token/token_test.go | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/password/password_test.go b/internal/password/password_test.go index 02d92a9b6a..caf6290d1b 100644 --- a/internal/password/password_test.go +++ b/internal/password/password_test.go @@ -22,9 +22,9 @@ func TestHashAndStorePassword(t *testing.T) { wantErr bool }{ - "Success_when_password_file_and_parent_dir_don't_exist_yet": {password: "test123"}, - "Success_when_parent_directory_already_exists": {password: "test123", parentDirExists: true}, - "Success_when_password_file_already_exists": {password: "test123", pathExists: true}, + "Success_when_password_file_and_parent_dir_do_not_exist_yet": {password: "test123"}, + "Success_when_parent_directory_already_exists": {password: "test123", parentDirExists: true}, + "Success_when_password_file_already_exists": {password: "test123", pathExists: true}, } for name, tc := range tests { @@ -67,10 +67,10 @@ func TestCheckPassword(t *testing.T) { wantMatch bool expectedError error }{ - "Success_when_password_matches": {password: "test123", wantMatch: true}, - "No_match_when_password_doesn't_match": {password: "not-test123", wantMatch: false}, + "Success_when_password_matches": {password: "test123", wantMatch: true}, + "No_match_when_password_does_not_match": {password: "not-test123", wantMatch: false}, - "Error_when_password_file_doesn't_exist": {password: "test123", pathToRead: "nonexistent", expectedError: os.ErrNotExist}, + "Error_when_password_file_does_not_exist": {password: "test123", pathToRead: "nonexistent", expectedError: os.ErrNotExist}, "Error_when_password_file_contains_garbage": {password: "test123", writeGarbage: true, expectedError: base64.CorruptInputError(0)}, } diff --git a/internal/token/token_test.go b/internal/token/token_test.go index d83af9fb51..a5318fc72a 100644 --- a/internal/token/token_test.go +++ b/internal/token/token_test.go @@ -40,7 +40,7 @@ func TestCacheAuthInfo(t *testing.T) { wantError bool }{ - "Successfully_store_token_with_non-existing_parent_directory": {}, + "Successfully_store_token_with_non_existing_parent_directory": {}, "Successfully_store_token_with_existing_parent_directory": {existingParentDir: true}, "Successfully_store_token_with_existing_file": {existingParentDir: true, existingFile: true}, From 6b12ccb53da917ddd0b19bd85b0f9eeba59f0ca6 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 11 Nov 2024 18:06:37 +0100 Subject: [PATCH 0214/1670] Use the subtest name as the golden file/dir name And check if the subtest name contains any characters that we don't want to allow in golden file names. This allows us to copy/paste the name of the golden file to search for the test definition and vice versa. --- internal/testutils/golden.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/internal/testutils/golden.go b/internal/testutils/golden.go index b822a821d7..279ce5f938 100644 --- a/internal/testutils/golden.go +++ b/internal/testutils/golden.go @@ -88,15 +88,13 @@ func LoadWithUpdateFromGoldenYAML[E any](t *testing.T, got E, opts ...GoldenOpti return wantDeserialized } -// NormalizeGoldenName returns the name of the golden file with illegal Windows -// characters replaced or removed. -func NormalizeGoldenName(t *testing.T, name string) string { +// CheckValidGoldenFileName checks if the provided name is a valid golden file name. +func CheckValidGoldenFileName(t *testing.T, name string) { t.Helper() - name = strings.ReplaceAll(name, `\`, "_") - name = strings.ReplaceAll(name, ":", "") - name = strings.ToLower(name) - return name + // A valid golden file contains only alphanumeric characters, underscores, dashes, and dots. + require.Regexp(t, `^[\w\-.]+$`, name, + "Invalid golden file name %q. Only alphanumeric characters, underscores, dashes, and dots are allowed", name) } // TestFamilyPath returns the path of the dir for storing fixtures and other files related to the test. @@ -114,9 +112,10 @@ func GoldenPath(t *testing.T) string { t.Helper() path := filepath.Join(TestFamilyPath(t), "golden") - _, sub, found := strings.Cut(t.Name(), "/") + _, subtestName, found := strings.Cut(t.Name(), "/") if found { - path = filepath.Join(path, NormalizeGoldenName(t, sub)) + CheckValidGoldenFileName(t, subtestName) + path = filepath.Join(path, subtestName) } return path From 21b0befdbc36aad8aa232936d7aa454f1b0db0f6 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 11 Nov 2024 18:23:30 +0100 Subject: [PATCH 0215/1670] Update names of golden files Update the names of the golden files according to the previous commit, by running: git rm "*/testdata/*" && TESTS_UPDATE_GOLDEN=1 go test ./... --- .../data/provider_url/user1/password | 0 .../data/provider_url/user1/token.json | 0 .../data/provider_url/user2/password | 0 .../data/provider_url/user2/token.json | 0 .../first_auth | 0 .../second_auth | 0 .../data/provider_url/user1/password | 0 .../data/provider_url/user1/token.json | 0 .../data/provider_url/user2/password | 0 .../data/provider_url/user2/token.json | 0 .../first_auth | 0 .../second_auth | 0 .../data/provider_url/user1/password | 0 .../data/provider_url/user1/token.json | 0 .../data/provider_url/user2/password | 0 .../data/provider_url/user2/token.json | 0 .../first_auth | 0 .../second_auth | 0 ...cessfully_fetch_user_info_with_default_home_when_not_provided} | 0 ..._info_with_groups => Successfully_fetch_user_info_with_groups} | 0 ...without_groups => Successfully_fetch_user_info_without_groups} | 0 ..._there_is_no_token => Get_device_auth_qr_if_there_is_no_token} | 0 ... Get_newpassword_if_already_authenticated_with_device_auth_qr} | 0 ..._if_already_authenticated_with_password_and_session_is_passwd} | 0 ..._if_token_exists_and_provider_does_not_support_device_auth_qr} | 0 ...t_only_password_if_token_exists_and_provider_is_not_available} | 0 ...wd => Get_only_password_if_token_exists_and_session_is_passwd} | 0 ...ken_exists => Get_password_and_device_auth_qr_if_token_exists} | 0 .../data/provider_url/test-user@email.com/password | 0 .../data/provider_url/test-user@email.com/token.json | 0 .../first_call | 0 .../second_call | 0 .../data/provider_url/test-user@email.com/password | 0 .../data/provider_url/test-user@email.com/token.json | 0 .../first_call | 0 .../data/provider_url/test-user@email.com/password | 0 .../data/provider_url/test-user@email.com/token.json | 0 .../first_call | 0 .../data/provider_url/test-user@email.com/password | 0 .../data/provider_url/test-user@email.com/token.json | 0 .../first_call | 0 .../data/provider_url/test-user@email.com/password | 0 .../data/provider_url/test-user@email.com/token.json | 0 .../first_call | 0 .../second_call | 0 .../data/.empty | 0 .../first_call | 0 .../second_call | 0 .../data/.empty | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../second_call | 0 .../data/.empty | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../second_call | 0 .../data/provider_url/test-user@email.com/password | 0 .../data/provider_url/test-user@email.com/token.json | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../data/provider_url/test-user@email.com/password | 0 .../data/provider_url/test-user@email.com/token.json | 0 .../first_call | 0 .../data/provider_url/test-user@email.com/password | 0 .../data/provider_url/test-user@email.com/token.json | 0 .../first_call | 0 .../data/provider_url/test-user@email.com/password | 0 .../data/provider_url/test-user@email.com/token.json | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../data/provider_url/test-user@email.com/password | 0 .../data/provider_url/test-user@email.com/token.json | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../data/provider_url/test-user@email.com/password | 0 .../data/provider_url/test-user@email.com/token.json | 0 .../first_call | 0 .../data/provider_url/test-user@email.com/password | 0 .../data/provider_url/test-user@email.com/token.json | 0 .../first_call | 0 .../second_call | 0 .../data/provider_url/test-user@email.com/password | 0 .../data/provider_url/test-user@email.com/token.json | 0 .../first_call | 0 .../config.txt | 0 .../config.txt | 0 .../config.txt | 0 ...=> Selected_newpassword_shows_correct_label_in_passwd_session} | 0 ...ssfully_select_device_auth => Successfully_select_device_auth} | 0 ...y_select_device_auth_qr => Successfully_select_device_auth_qr} | 0 ...ssfully_select_newpassword => Successfully_select_newpassword} | 0 ...{successfully_select_password => Successfully_select_password} | 0 ...echeck => Return_userinfo_with_correct_homedir_after_precheck} | 0 ...fully_allow_username_that_matches_at_least_one_allowed_suffix} | 0 ...x => Successfully_allow_username_with_matching_allowed_suffix} | 0 116 files changed, 0 insertions(+), 0 deletions(-) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/{first_auth_starts_and_finishes_before_second => First_auth_starts_and_finishes_before_second}/data/provider_url/user1/password (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/{first_auth_starts_and_finishes_before_second => First_auth_starts_and_finishes_before_second}/data/provider_url/user1/token.json (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/{first_auth_starts_and_finishes_before_second => First_auth_starts_and_finishes_before_second}/data/provider_url/user2/password (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/{first_auth_starts_and_finishes_before_second => First_auth_starts_and_finishes_before_second}/data/provider_url/user2/token.json (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/{first_auth_starts_and_finishes_before_second => First_auth_starts_and_finishes_before_second}/first_auth (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/{first_auth_starts_and_finishes_before_second => First_auth_starts_and_finishes_before_second}/second_auth (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/{first_auth_starts_first_but_second_finishes_first => First_auth_starts_first_but_second_finishes_first}/data/provider_url/user1/password (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/{first_auth_starts_first_but_second_finishes_first => First_auth_starts_first_but_second_finishes_first}/data/provider_url/user1/token.json (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/{first_auth_starts_first_but_second_finishes_first => First_auth_starts_first_but_second_finishes_first}/data/provider_url/user2/password (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/{first_auth_starts_first_but_second_finishes_first => First_auth_starts_first_but_second_finishes_first}/data/provider_url/user2/token.json (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/{first_auth_starts_first_but_second_finishes_first => First_auth_starts_first_but_second_finishes_first}/first_auth (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/{first_auth_starts_first_but_second_finishes_first => First_auth_starts_first_but_second_finishes_first}/second_auth (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/{first_auth_starts_first_then_second_starts_and_first_finishes => First_auth_starts_first_then_second_starts_and_first_finishes}/data/provider_url/user1/password (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/{first_auth_starts_first_then_second_starts_and_first_finishes => First_auth_starts_first_then_second_starts_and_first_finishes}/data/provider_url/user1/token.json (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/{first_auth_starts_first_then_second_starts_and_first_finishes => First_auth_starts_first_then_second_starts_and_first_finishes}/data/provider_url/user2/password (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/{first_auth_starts_first_then_second_starts_and_first_finishes => First_auth_starts_first_then_second_starts_and_first_finishes}/data/provider_url/user2/token.json (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/{first_auth_starts_first_then_second_starts_and_first_finishes => First_auth_starts_first_then_second_starts_and_first_finishes}/first_auth (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/{first_auth_starts_first_then_second_starts_and_first_finishes => First_auth_starts_first_then_second_starts_and_first_finishes}/second_auth (100%) rename internal/broker/testdata/TestFetchUserInfo/golden/{successfully_fetch_user_info_with_default_home_when_not_provided => Successfully_fetch_user_info_with_default_home_when_not_provided} (100%) rename internal/broker/testdata/TestFetchUserInfo/golden/{successfully_fetch_user_info_with_groups => Successfully_fetch_user_info_with_groups} (100%) rename internal/broker/testdata/TestFetchUserInfo/golden/{successfully_fetch_user_info_without_groups => Successfully_fetch_user_info_without_groups} (100%) rename internal/broker/testdata/TestGetAuthenticationModes/golden/{get_device_auth_qr_if_there_is_no_token => Get_device_auth_qr_if_there_is_no_token} (100%) rename internal/broker/testdata/TestGetAuthenticationModes/golden/{get_newpassword_if_already_authenticated_with_device_auth_qr => Get_newpassword_if_already_authenticated_with_device_auth_qr} (100%) rename internal/broker/testdata/TestGetAuthenticationModes/golden/{get_newpassword_if_already_authenticated_with_password_and_session_is_passwd => Get_newpassword_if_already_authenticated_with_password_and_session_is_passwd} (100%) rename internal/broker/testdata/TestGetAuthenticationModes/golden/{get_only_password_if_token_exists_and_provider_does_not_support_device_auth_qr => Get_only_password_if_token_exists_and_provider_does_not_support_device_auth_qr} (100%) rename internal/broker/testdata/TestGetAuthenticationModes/golden/{get_only_password_if_token_exists_and_provider_is_not_available => Get_only_password_if_token_exists_and_provider_is_not_available} (100%) rename internal/broker/testdata/TestGetAuthenticationModes/golden/{get_only_password_if_token_exists_and_session_is_passwd => Get_only_password_if_token_exists_and_session_is_passwd} (100%) rename internal/broker/testdata/TestGetAuthenticationModes/golden/{get_password_and_device_auth_qr_if_token_exists => Get_password_and_device_auth_qr_if_token_exists} (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{authenticating_still_allowed_if_token_is_missing_scopes => Authenticating_still_allowed_if_token_is_missing_scopes}/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{authenticating_still_allowed_if_token_is_missing_scopes => Authenticating_still_allowed_if_token_is_missing_scopes}/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{authenticating_still_allowed_if_token_is_missing_scopes => Authenticating_still_allowed_if_token_is_missing_scopes}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{authenticating_still_allowed_if_token_is_missing_scopes => Authenticating_still_allowed_if_token_is_missing_scopes}/second_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{authenticating_with_password_refreshes_expired_token => Authenticating_with_password_refreshes_expired_token}/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{authenticating_with_password_refreshes_expired_token => Authenticating_with_password_refreshes_expired_token}/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{authenticating_with_password_refreshes_expired_token => Authenticating_with_password_refreshes_expired_token}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{authenticating_with_password_still_allowed_if_server_is_unreachable => Authenticating_with_password_still_allowed_if_server_is_unreachable}/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{authenticating_with_password_still_allowed_if_server_is_unreachable => Authenticating_with_password_still_allowed_if_server_is_unreachable}/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{authenticating_with_password_still_allowed_if_server_is_unreachable => Authenticating_with_password_still_allowed_if_server_is_unreachable}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable => Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable}/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable => Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable}/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable => Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{authenticating_with_qrcode_reacquires_token => Authenticating_with_qrcode_reacquires_token}/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{authenticating_with_qrcode_reacquires_token => Authenticating_with_qrcode_reacquires_token}/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{authenticating_with_qrcode_reacquires_token => Authenticating_with_qrcode_reacquires_token}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{authenticating_with_qrcode_reacquires_token => Authenticating_with_qrcode_reacquires_token}/second_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_authentication_data_is_invalid => Error_when_IsAuthenticated_is_ongoing_for_session}/data/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_can_not_cache_token => Error_when_IsAuthenticated_is_ongoing_for_session}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_isauthenticated_is_ongoing_for_session => Error_when_IsAuthenticated_is_ongoing_for_session}/second_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_can_not_cache_token => Error_when_authentication_data_is_invalid}/data/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_authentication_data_is_invalid => Error_when_authentication_data_is_invalid}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_challenge_can_not_be_decrypted => Error_when_can_not_cache_token}/data/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_empty_challenge_is_provided_for_local_password => Error_when_can_not_cache_token}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_can_not_cache_token => Error_when_can_not_cache_token}/second_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_empty_challenge_is_provided_for_local_password => Error_when_challenge_can_not_be_decrypted}/data/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_challenge_can_not_be_decrypted => Error_when_challenge_can_not_be_decrypted}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_isauthenticated_is_ongoing_for_session => Error_when_empty_challenge_is_provided_for_local_password}/data/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_isauthenticated_is_ongoing_for_session => Error_when_empty_challenge_is_provided_for_local_password}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_empty_challenge_is_provided_for_local_password => Error_when_empty_challenge_is_provided_for_local_password}/second_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_existing_token_has_no_user_info_and_fetching_user_info_fails => Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails}/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_existing_token_has_no_user_info_and_fetching_user_info_fails => Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails}/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_existing_token_has_no_user_info_and_fetching_user_info_fails => Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_link_code_and_can_not_get_token => Error_when_mode_is_link_code_and_can_not_get_token}/data/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_link_code_and_can_not_get_token => Error_when_mode_is_link_code_and_can_not_get_token}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout => Error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout}/data/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout => Error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_link_code_and_link_expires => Error_when_mode_is_link_code_and_link_expires}/data/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_link_code_and_link_expires => Error_when_mode_is_link_code_and_link_expires}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_link_code_and_response_is_invalid => Error_when_mode_is_link_code_and_response_is_invalid}/data/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_link_code_and_response_is_invalid => Error_when_mode_is_link_code_and_response_is_invalid}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_newpassword_and_session_has_no_token => Error_when_mode_is_newpassword_and_session_has_no_token}/data/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_newpassword_and_session_has_no_token => Error_when_mode_is_newpassword_and_session_has_no_token}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_password_and_token_does_not_exist => Error_when_mode_is_password_and_token_does_not_exist}/data/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_password_and_token_does_not_exist => Error_when_mode_is_password_and_token_does_not_exist}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_password_and_token_is_invalid => Error_when_mode_is_password_and_token_is_invalid}/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_password_and_token_is_invalid => Error_when_mode_is_password_and_token_is_invalid}/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_password_and_token_is_invalid => Error_when_mode_is_password_and_token_is_invalid}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_password_and_token_refresh_times_out => Error_when_mode_is_password_and_token_refresh_times_out}/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_password_and_token_refresh_times_out => Error_when_mode_is_password_and_token_refresh_times_out}/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_password_and_token_refresh_times_out => Error_when_mode_is_password_and_token_refresh_times_out}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_password_but_server_returns_error => Error_when_mode_is_password_but_server_returns_error}/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_password_but_server_returns_error => Error_when_mode_is_password_but_server_returns_error}/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_password_but_server_returns_error => Error_when_mode_is_password_but_server_returns_error}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_qrcode_and_can_not_get_token => Error_when_mode_is_qrcode_and_can_not_get_token}/data/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_qrcode_and_can_not_get_token => Error_when_mode_is_qrcode_and_can_not_get_token}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout => Error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout}/data/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout => Error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_qrcode_and_link_expires => Error_when_mode_is_qrcode_and_link_expires}/data/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_qrcode_and_link_expires => Error_when_mode_is_qrcode_and_link_expires}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_qrcode_and_response_is_invalid => Error_when_mode_is_qrcode_and_response_is_invalid}/data/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_mode_is_qrcode_and_response_is_invalid => Error_when_mode_is_qrcode_and_response_is_invalid}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_provided_wrong_challenge => Error_when_provided_wrong_challenge}/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_provided_wrong_challenge => Error_when_provided_wrong_challenge}/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_provided_wrong_challenge => Error_when_provided_wrong_challenge}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_selected_username_does_not_match_the_provider_one => Error_when_selected_username_does_not_match_the_provider_one}/data/.empty (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_selected_username_does_not_match_the_provider_one => Error_when_selected_username_does_not_match_the_provider_one}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_token_is_expired_and_refreshing_token_fails => Error_when_token_is_expired_and_refreshing_token_fails}/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_token_is_expired_and_refreshing_token_fails => Error_when_token_is_expired_and_refreshing_token_fails}/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{error_when_token_is_expired_and_refreshing_token_fails => Error_when_token_is_expired_and_refreshing_token_fails}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{successfully_authenticate_user_with_device_auth_and_newpassword => Successfully_authenticate_user_with_device_auth_and_newpassword}/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{successfully_authenticate_user_with_device_auth_and_newpassword => Successfully_authenticate_user_with_device_auth_and_newpassword}/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{successfully_authenticate_user_with_device_auth_and_newpassword => Successfully_authenticate_user_with_device_auth_and_newpassword}/first_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{successfully_authenticate_user_with_device_auth_and_newpassword => Successfully_authenticate_user_with_device_auth_and_newpassword}/second_call (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{successfully_authenticate_user_with_password => Successfully_authenticate_user_with_password}/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{successfully_authenticate_user_with_password => Successfully_authenticate_user_with_password}/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/TestIsAuthenticated/golden/{successfully_authenticate_user_with_password => Successfully_authenticate_user_with_password}/first_call (100%) rename internal/broker/testdata/TestParseConfig/golden/{do_not_fail_if_values_contain_a_single_template_delimiter => Do_not_fail_if_values_contain_a_single_template_delimiter}/config.txt (100%) rename internal/broker/testdata/TestParseConfig/golden/{successfully_parse_config_file => Successfully_parse_config_file}/config.txt (100%) rename internal/broker/testdata/TestParseConfig/golden/{successfully_parse_config_file_with_optional_values => Successfully_parse_config_file_with_optional_values}/config.txt (100%) rename internal/broker/testdata/TestSelectAuthenticationMode/golden/{selected_newpassword_shows_correct_label_in_passwd_session => Selected_newpassword_shows_correct_label_in_passwd_session} (100%) rename internal/broker/testdata/TestSelectAuthenticationMode/golden/{successfully_select_device_auth => Successfully_select_device_auth} (100%) rename internal/broker/testdata/TestSelectAuthenticationMode/golden/{successfully_select_device_auth_qr => Successfully_select_device_auth_qr} (100%) rename internal/broker/testdata/TestSelectAuthenticationMode/golden/{successfully_select_newpassword => Successfully_select_newpassword} (100%) rename internal/broker/testdata/TestSelectAuthenticationMode/golden/{successfully_select_password => Successfully_select_password} (100%) rename internal/broker/testdata/TestUserPreCheck/golden/{return_userinfo_with_correct_homedir_after_precheck => Return_userinfo_with_correct_homedir_after_precheck} (100%) rename internal/broker/testdata/TestUserPreCheck/golden/{successfully_allow_username_that_matches_at_least_one_allowed_suffix => Successfully_allow_username_that_matches_at_least_one_allowed_suffix} (100%) rename internal/broker/testdata/TestUserPreCheck/golden/{successfully_allow_username_with_matching_allowed_suffix => Successfully_allow_username_with_matching_allowed_suffix} (100%) diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user1/password b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user1/password similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user1/password rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user1/password diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user1/token.json b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user1/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user1/token.json rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user1/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user2/password b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user2/password similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user2/password rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user2/password diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user2/token.json b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user2/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/data/provider_url/user2/token.json rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user2/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/first_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/first_auth similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/first_auth rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/first_auth diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/second_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/second_auth similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_and_finishes_before_second/second_auth rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/second_auth diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user1/password b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1/password similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user1/password rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1/password diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user1/token.json b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user1/token.json rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user2/password b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2/password similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user2/password rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2/password diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user2/token.json b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/data/provider_url/user2/token.json rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/first_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/first_auth similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/first_auth rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/first_auth diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/second_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/second_auth similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_but_second_finishes_first/second_auth rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/second_auth diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1/password b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1/password similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1/password rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1/password diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1/token.json b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1/token.json rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2/password b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2/password similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2/password rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2/password diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2/token.json b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2/token.json rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/first_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/first_auth similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/first_auth rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/first_auth diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/second_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/second_auth similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/first_auth_starts_first_then_second_starts_and_first_finishes/second_auth rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/second_auth diff --git a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_default_home_when_not_provided b/internal/broker/testdata/TestFetchUserInfo/golden/Successfully_fetch_user_info_with_default_home_when_not_provided similarity index 100% rename from internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_default_home_when_not_provided rename to internal/broker/testdata/TestFetchUserInfo/golden/Successfully_fetch_user_info_with_default_home_when_not_provided diff --git a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_groups b/internal/broker/testdata/TestFetchUserInfo/golden/Successfully_fetch_user_info_with_groups similarity index 100% rename from internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_with_groups rename to internal/broker/testdata/TestFetchUserInfo/golden/Successfully_fetch_user_info_with_groups diff --git a/internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_without_groups b/internal/broker/testdata/TestFetchUserInfo/golden/Successfully_fetch_user_info_without_groups similarity index 100% rename from internal/broker/testdata/TestFetchUserInfo/golden/successfully_fetch_user_info_without_groups rename to internal/broker/testdata/TestFetchUserInfo/golden/Successfully_fetch_user_info_without_groups diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_device_auth_qr_if_there_is_no_token b/internal/broker/testdata/TestGetAuthenticationModes/golden/Get_device_auth_qr_if_there_is_no_token similarity index 100% rename from internal/broker/testdata/TestGetAuthenticationModes/golden/get_device_auth_qr_if_there_is_no_token rename to internal/broker/testdata/TestGetAuthenticationModes/golden/Get_device_auth_qr_if_there_is_no_token diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_device_auth_qr b/internal/broker/testdata/TestGetAuthenticationModes/golden/Get_newpassword_if_already_authenticated_with_device_auth_qr similarity index 100% rename from internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_device_auth_qr rename to internal/broker/testdata/TestGetAuthenticationModes/golden/Get_newpassword_if_already_authenticated_with_device_auth_qr diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_password_and_session_is_passwd b/internal/broker/testdata/TestGetAuthenticationModes/golden/Get_newpassword_if_already_authenticated_with_password_and_session_is_passwd similarity index 100% rename from internal/broker/testdata/TestGetAuthenticationModes/golden/get_newpassword_if_already_authenticated_with_password_and_session_is_passwd rename to internal/broker/testdata/TestGetAuthenticationModes/golden/Get_newpassword_if_already_authenticated_with_password_and_session_is_passwd diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_provider_does_not_support_device_auth_qr b/internal/broker/testdata/TestGetAuthenticationModes/golden/Get_only_password_if_token_exists_and_provider_does_not_support_device_auth_qr similarity index 100% rename from internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_provider_does_not_support_device_auth_qr rename to internal/broker/testdata/TestGetAuthenticationModes/golden/Get_only_password_if_token_exists_and_provider_does_not_support_device_auth_qr diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_provider_is_not_available b/internal/broker/testdata/TestGetAuthenticationModes/golden/Get_only_password_if_token_exists_and_provider_is_not_available similarity index 100% rename from internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_provider_is_not_available rename to internal/broker/testdata/TestGetAuthenticationModes/golden/Get_only_password_if_token_exists_and_provider_is_not_available diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_session_is_passwd b/internal/broker/testdata/TestGetAuthenticationModes/golden/Get_only_password_if_token_exists_and_session_is_passwd similarity index 100% rename from internal/broker/testdata/TestGetAuthenticationModes/golden/get_only_password_if_token_exists_and_session_is_passwd rename to internal/broker/testdata/TestGetAuthenticationModes/golden/Get_only_password_if_token_exists_and_session_is_passwd diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/get_password_and_device_auth_qr_if_token_exists b/internal/broker/testdata/TestGetAuthenticationModes/golden/Get_password_and_device_auth_qr_if_token_exists similarity index 100% rename from internal/broker/testdata/TestGetAuthenticationModes/golden/get_password_and_device_auth_qr_if_token_exists rename to internal/broker/testdata/TestGetAuthenticationModes/golden/Get_password_and_device_auth_qr_if_token_exists diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_still_allowed_if_token_is_missing_scopes/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_still_allowed_if_token_is_missing_scopes/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_still_allowed_if_token_is_missing_scopes/second_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_still_allowed_if_token_is_missing_scopes/second_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_still_allowed_if_token_is_missing_scopes/second_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_expired_token/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_refreshes_expired_token/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_expired_token/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_still_allowed_if_server_is_unreachable/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_server_is_unreachable/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_still_allowed_if_server_is_unreachable/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_qrcode_reacquires_token/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_qrcode_reacquires_token/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_qrcode_reacquires_token/second_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/authenticating_with_qrcode_reacquires_token/second_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_qrcode_reacquires_token/second_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_authentication_data_is_invalid/data/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_IsAuthenticated_is_ongoing_for_session/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_authentication_data_is_invalid/data/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_IsAuthenticated_is_ongoing_for_session/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_IsAuthenticated_is_ongoing_for_session/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_IsAuthenticated_is_ongoing_for_session/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_IsAuthenticated_is_ongoing_for_session/second_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/second_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_IsAuthenticated_is_ongoing_for_session/second_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/data/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_authentication_data_is_invalid/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/data/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_authentication_data_is_invalid/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_authentication_data_is_invalid/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_authentication_data_is_invalid/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_authentication_data_is_invalid/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_authentication_data_is_invalid/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_challenge_can_not_be_decrypted/data/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_can_not_cache_token/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_challenge_can_not_be_decrypted/data/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_can_not_cache_token/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_can_not_cache_token/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_can_not_cache_token/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_can_not_cache_token/second_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_can_not_cache_token/second_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_can_not_cache_token/second_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/data/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_challenge_can_not_be_decrypted/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/data/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_challenge_can_not_be_decrypted/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_challenge_can_not_be_decrypted/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_challenge_can_not_be_decrypted/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_challenge_can_not_be_decrypted/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_challenge_can_not_be_decrypted/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/data/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_empty_challenge_is_provided_for_local_password/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/data/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_empty_challenge_is_provided_for_local_password/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_empty_challenge_is_provided_for_local_password/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_isauthenticated_is_ongoing_for_session/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_empty_challenge_is_provided_for_local_password/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_empty_challenge_is_provided_for_local_password/second_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_empty_challenge_is_provided_for_local_password/second_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_empty_challenge_is_provided_for_local_password/second_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token/data/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_can_not_get_token/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token/data/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_can_not_get_token/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_can_not_get_token/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_can_not_get_token/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/data/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/data/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_link_expires/data/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_link_expires/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_link_expires/data/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_link_expires/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_link_expires/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_link_expires/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_link_expires/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_link_expires/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_response_is_invalid/data/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_response_is_invalid/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_response_is_invalid/data/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_response_is_invalid/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_response_is_invalid/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_response_is_invalid/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_link_code_and_response_is_invalid/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_response_is_invalid/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_session_has_no_token/data/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_newpassword_and_session_has_no_token/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_session_has_no_token/data/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_newpassword_and_session_has_no_token/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_session_has_no_token/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_newpassword_and_session_has_no_token/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_newpassword_and_session_has_no_token/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_newpassword_and_session_has_no_token/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/data/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_does_not_exist/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/data/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_does_not_exist/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_does_not_exist/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_does_not_exist/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_does_not_exist/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_is_invalid/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_is_invalid/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_is_invalid/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_refresh_times_out/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_and_token_refresh_times_out/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_refresh_times_out/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_but_server_returns_error/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_password_but_server_returns_error/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_but_server_returns_error/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token/data/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_can_not_get_token/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token/data/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_can_not_get_token/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_can_not_get_token/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_can_not_get_token/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/data/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/data/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_link_expires/data/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_link_expires/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_link_expires/data/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_link_expires/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_link_expires/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_link_expires/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_link_expires/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_link_expires/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_response_is_invalid/data/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_response_is_invalid/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_response_is_invalid/data/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_response_is_invalid/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_response_is_invalid/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_response_is_invalid/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_mode_is_qrcode_and_response_is_invalid/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_response_is_invalid/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_provided_wrong_challenge/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_provided_wrong_challenge/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_provided_wrong_challenge/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/data/.empty b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_selected_username_does_not_match_the_provider_one/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/data/.empty rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_selected_username_does_not_match_the_provider_one/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_selected_username_does_not_match_the_provider_one/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_selected_username_does_not_match_the_provider_one/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_selected_username_does_not_match_the_provider_one/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/error_when_token_is_expired_and_refreshing_token_fails/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_token_is_expired_and_refreshing_token_fails/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/error_when_token_is_expired_and_refreshing_token_fails/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Error_when_token_is_expired_and_refreshing_token_fails/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_device_auth_and_newpassword/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_device_auth_and_newpassword/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_device_auth_and_newpassword/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_device_auth_and_newpassword/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_device_auth_and_newpassword/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_device_auth_and_newpassword/second_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_device_auth_and_newpassword/second_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_device_auth_and_newpassword/second_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_password/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/successfully_authenticate_user_with_password/first_call rename to internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_password/first_call diff --git a/internal/broker/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter/config.txt b/internal/broker/testdata/TestParseConfig/golden/Do_not_fail_if_values_contain_a_single_template_delimiter/config.txt similarity index 100% rename from internal/broker/testdata/TestParseConfig/golden/do_not_fail_if_values_contain_a_single_template_delimiter/config.txt rename to internal/broker/testdata/TestParseConfig/golden/Do_not_fail_if_values_contain_a_single_template_delimiter/config.txt diff --git a/internal/broker/testdata/TestParseConfig/golden/successfully_parse_config_file/config.txt b/internal/broker/testdata/TestParseConfig/golden/Successfully_parse_config_file/config.txt similarity index 100% rename from internal/broker/testdata/TestParseConfig/golden/successfully_parse_config_file/config.txt rename to internal/broker/testdata/TestParseConfig/golden/Successfully_parse_config_file/config.txt diff --git a/internal/broker/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values/config.txt b/internal/broker/testdata/TestParseConfig/golden/Successfully_parse_config_file_with_optional_values/config.txt similarity index 100% rename from internal/broker/testdata/TestParseConfig/golden/successfully_parse_config_file_with_optional_values/config.txt rename to internal/broker/testdata/TestParseConfig/golden/Successfully_parse_config_file_with_optional_values/config.txt diff --git a/internal/broker/testdata/TestSelectAuthenticationMode/golden/selected_newpassword_shows_correct_label_in_passwd_session b/internal/broker/testdata/TestSelectAuthenticationMode/golden/Selected_newpassword_shows_correct_label_in_passwd_session similarity index 100% rename from internal/broker/testdata/TestSelectAuthenticationMode/golden/selected_newpassword_shows_correct_label_in_passwd_session rename to internal/broker/testdata/TestSelectAuthenticationMode/golden/Selected_newpassword_shows_correct_label_in_passwd_session diff --git a/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_device_auth b/internal/broker/testdata/TestSelectAuthenticationMode/golden/Successfully_select_device_auth similarity index 100% rename from internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_device_auth rename to internal/broker/testdata/TestSelectAuthenticationMode/golden/Successfully_select_device_auth diff --git a/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_device_auth_qr b/internal/broker/testdata/TestSelectAuthenticationMode/golden/Successfully_select_device_auth_qr similarity index 100% rename from internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_device_auth_qr rename to internal/broker/testdata/TestSelectAuthenticationMode/golden/Successfully_select_device_auth_qr diff --git a/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_newpassword b/internal/broker/testdata/TestSelectAuthenticationMode/golden/Successfully_select_newpassword similarity index 100% rename from internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_newpassword rename to internal/broker/testdata/TestSelectAuthenticationMode/golden/Successfully_select_newpassword diff --git a/internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_password b/internal/broker/testdata/TestSelectAuthenticationMode/golden/Successfully_select_password similarity index 100% rename from internal/broker/testdata/TestSelectAuthenticationMode/golden/successfully_select_password rename to internal/broker/testdata/TestSelectAuthenticationMode/golden/Successfully_select_password diff --git a/internal/broker/testdata/TestUserPreCheck/golden/return_userinfo_with_correct_homedir_after_precheck b/internal/broker/testdata/TestUserPreCheck/golden/Return_userinfo_with_correct_homedir_after_precheck similarity index 100% rename from internal/broker/testdata/TestUserPreCheck/golden/return_userinfo_with_correct_homedir_after_precheck rename to internal/broker/testdata/TestUserPreCheck/golden/Return_userinfo_with_correct_homedir_after_precheck diff --git a/internal/broker/testdata/TestUserPreCheck/golden/successfully_allow_username_that_matches_at_least_one_allowed_suffix b/internal/broker/testdata/TestUserPreCheck/golden/Successfully_allow_username_that_matches_at_least_one_allowed_suffix similarity index 100% rename from internal/broker/testdata/TestUserPreCheck/golden/successfully_allow_username_that_matches_at_least_one_allowed_suffix rename to internal/broker/testdata/TestUserPreCheck/golden/Successfully_allow_username_that_matches_at_least_one_allowed_suffix diff --git a/internal/broker/testdata/TestUserPreCheck/golden/successfully_allow_username_with_matching_allowed_suffix b/internal/broker/testdata/TestUserPreCheck/golden/Successfully_allow_username_with_matching_allowed_suffix similarity index 100% rename from internal/broker/testdata/TestUserPreCheck/golden/successfully_allow_username_with_matching_allowed_suffix rename to internal/broker/testdata/TestUserPreCheck/golden/Successfully_allow_username_with_matching_allowed_suffix From 39256e8ddd72042c9a38db86a951b0a88a71329c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 12 Nov 2024 21:51:39 +0100 Subject: [PATCH 0216/1670] broker: Explicitly use Device auth mode for testing errors We have tests checking error cases using the qrcode auth mode, but we were not doing the same with the device case. Closes: https://github.com/ubuntu/authd/issues/629 --- internal/broker/broker_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 3e0d51c7fc..a14fdc0933 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -480,18 +480,23 @@ func TestIsAuthenticated(t *testing.T) { "/token": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, }, - "Error_when_mode_is_link_code_and_response_is_invalid": {firstAuthInfo: map[string]any{"response": "not a valid response"}}, + "Error_when_mode_is_link_code_and_response_is_invalid": { + firstMode: authmodes.Device, + firstAuthInfo: map[string]any{"response": "not a valid response"}, + }, "Error_when_mode_is_link_code_and_link_expires": { customHandlers: map[string]testutils.ProviderHandler{ "/device_auth": testutils.ExpiryDeviceAuthHandler(), }, }, "Error_when_mode_is_link_code_and_can_not_get_token": { + firstMode: authmodes.Device, customHandlers: map[string]testutils.ProviderHandler{ "/token": testutils.UnavailableHandler(), }, }, "Error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout": { + firstMode: authmodes.Device, customHandlers: map[string]testutils.ProviderHandler{ "/token": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, From 6df54011fa35e0ab0de8bbda040ad4b7cef853f1 Mon Sep 17 00:00:00 2001 From: shipperizer Date: Sun, 3 Nov 2024 19:54:36 +0100 Subject: [PATCH 0217/1670] fix: use AuthURLParam to set client id and secret Co-authored-by: 3v1n0 workaround to deal with golang/oauth2#320 tldr is that IDP servers tend to not be fully compliant with how client credentials are passed and have bespoke arrangements so anything goes this enforces the standard implementation from the RFC and has it working for any RFC compliant OIDC server full info here https://github.com/golang/oauth2/issues/320 --- internal/broker/broker.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 1dd2bfb30e..2833989b29 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -357,7 +357,20 @@ func (b *Broker) generateUILayout(session *sessionInfo, authModeID string) (map[ case authmodes.Device, authmodes.DeviceQr: ctx, cancel := context.WithTimeout(context.Background(), maxRequestDuration) defer cancel() - response, err := session.authCfg.oauth.DeviceAuth(ctx) + + var authOpts []oauth2.AuthCodeOption + + // workaround to cater for fully RFC compliant oauth2 server which require this + // extra option, public providers tend to have bespoke implementation for passing client + // credentials that completely bypass this + // full explanation in https://github.com/golang/oauth2/issues/320 + if secret := session.authCfg.oauth.ClientSecret; secret != "" { + // TODO @shipperizer verificationMethod should be a configurable value + verificationMethod := "client_post" + authOpts = append(authOpts, oauth2.SetAuthURLParam(verificationMethod, secret)) + } + + response, err := session.authCfg.oauth.DeviceAuth(ctx, authOpts...) if err != nil { return nil, fmt.Errorf("could not generate Device Authentication code layout: %v", err) } From c6975361f913b72cfe4e7f06984f817f069d412c Mon Sep 17 00:00:00 2001 From: Shiv Tyagi Date: Sat, 9 Nov 2024 10:30:54 +0530 Subject: [PATCH 0218/1670] broker: force refresh token at login when online --- internal/broker/broker.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 1dd2bfb30e..7d66b23ad7 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -559,9 +559,8 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo } } - // Refresh the token if it has expired and we're online. Ideally, we should always refresh it when we're online, - // but the oauth2.TokenSource implementation only refreshes the token when it's expired. - if !authInfo.Token.Valid() && !session.isOffline { + // Refresh the token if we're online even if the token has not expired + if !session.isOffline { authInfo, err = b.refreshToken(ctx, session.authCfg.oauth, authInfo) if err != nil { slog.Error(err.Error()) @@ -731,6 +730,9 @@ func (b *Broker) updateSession(sessionID string, session sessionInfo) error { func (b *Broker) refreshToken(ctx context.Context, oauth2Config oauth2.Config, oldToken token.AuthCachedInfo) (token.AuthCachedInfo, error) { timeoutCtx, cancel := context.WithTimeout(ctx, maxRequestDuration) defer cancel() + // set cached token expiry time to one hour in the past + // this makes sure the token is refreshed even if it has not 'actually' expired + oldToken.Token.Expiry = time.Now().Add(-time.Hour) oauthToken, err := oauth2Config.TokenSource(timeoutCtx, oldToken.Token).Token() if err != nil { return token.AuthCachedInfo{}, err From ec88057f18a65ddfbac25c65ebe06899ed8c16c7 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 12 Nov 2024 18:08:44 +0100 Subject: [PATCH 0219/1670] Fix test --- .../Successfully_authenticate_user_with_password/first_call | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_password/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_password/first_call index 20738888ae..12ca5144ad 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_password/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_password/first_call @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' err: From bf548daf88c84d921b174824efb42bd3b34a76ba Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 12 Nov 2024 18:50:50 +0100 Subject: [PATCH 0220/1670] Rename DefaultTokenHandler -> TokenHandler We don't have any other way to define a token handler, so calling this the "default" token handler doesn't make much sense. --- internal/broker/broker_test.go | 2 +- internal/testutils/provider.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index a14fdc0933..df0c65a8ec 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -436,7 +436,7 @@ func TestIsAuthenticated(t *testing.T) { firstChallenge: "-", wantSecondCall: true, customHandlers: map[string]testutils.ProviderHandler{ - "/token": testutils.DefaultTokenHandler("http://127.0.0.1:31313", []string{}), + "/token": testutils.TokenHandler("http://127.0.0.1:31313", []string{}), }, address: "127.0.0.1:31313", }, diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index 5c9bdb0a8e..b7859bd247 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -99,7 +99,7 @@ func StartMockProvider(address string, args ...OptionProvider) (*httptest.Server handlers: map[string]ProviderHandler{ "/.well-known/openid-configuration": DefaultOpenIDHandler(server.URL), "/device_auth": DefaultDeviceAuthHandler(), - "/token": DefaultTokenHandler(server.URL, consts.DefaultScopes), + "/token": TokenHandler(server.URL, consts.DefaultScopes), "/keys": DefaultJWKHandler(), }, } @@ -175,8 +175,8 @@ func DefaultDeviceAuthHandler() ProviderHandler { } } -// DefaultTokenHandler returns a handler that returns a default token response. -func DefaultTokenHandler(serverURL string, scopes []string) ProviderHandler { +// TokenHandler returns a handler that returns a default token response. +func TokenHandler(serverURL string, scopes []string) ProviderHandler { return func(w http.ResponseWriter, r *http.Request) { // Mimics user going through auth process time.Sleep(2 * time.Second) From 857643d43a9b55f6c2f098534bdea65361dd187d Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 12 Nov 2024 19:16:05 +0100 Subject: [PATCH 0221/1670] Support setting ID token claims in provider mock --- cmd/authd-oidc/daemon/daemon_test.go | 2 +- internal/broker/broker_test.go | 39 ++++++++++++++++--------- internal/testutils/provider.go | 43 ++++++++++++++++++++++++---- 3 files changed, 64 insertions(+), 20 deletions(-) diff --git a/cmd/authd-oidc/daemon/daemon_test.go b/cmd/authd-oidc/daemon/daemon_test.go index aacf2f7193..bf9c6a241e 100644 --- a/cmd/authd-oidc/daemon/daemon_test.go +++ b/cmd/authd-oidc/daemon/daemon_test.go @@ -403,7 +403,7 @@ func TestMain(m *testing.M) { defer cleanup() // Start provider mock - providerServer, cleanup := testutils.StartMockProvider("") + providerServer, cleanup := testutils.StartMockProvider("", nil) defer cleanup() mockProvider = providerServer diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index df0c65a8ec..d44d620c9d 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -107,7 +107,7 @@ func TestNewSession(t *testing.T) { opts = append(opts, testutils.WithHandler(endpoint, handler)) } - provider, stopServer := testutils.StartMockProvider("", opts...) + provider, stopServer := testutils.StartMockProvider("", nil, opts...) t.Cleanup(stopServer) cfg := &broker.Config{} cfg.SetIssuerURL(provider.URL) @@ -224,7 +224,7 @@ func TestGetAuthenticationModes(t *testing.T) { testutils.UnavailableHandler(), )) } - provider, stopServer = testutils.StartMockProvider(address, opts...) + provider, stopServer = testutils.StartMockProvider(address, nil, opts...) t.Cleanup(stopServer) } cfg := &broker.Config{} @@ -336,7 +336,7 @@ func TestSelectAuthenticationMode(t *testing.T) { for path, handler := range tc.customHandlers { opts = append(opts, testutils.WithHandler(path, handler)) } - p, cleanup := testutils.StartMockProvider("", opts...) + p, cleanup := testutils.StartMockProvider("", nil, opts...) defer cleanup() provider = p } @@ -436,7 +436,7 @@ func TestIsAuthenticated(t *testing.T) { firstChallenge: "-", wantSecondCall: true, customHandlers: map[string]testutils.ProviderHandler{ - "/token": testutils.TokenHandler("http://127.0.0.1:31313", []string{}), + "/token": testutils.TokenHandler("http://127.0.0.1:31313", nil), }, address: "127.0.0.1:31313", }, @@ -526,7 +526,7 @@ func TestIsAuthenticated(t *testing.T) { for path, handler := range tc.customHandlers { opts = append(opts, testutils.WithHandler(path, handler)) } - p, cleanup := testutils.StartMockProvider(tc.address, opts...) + p, cleanup := testutils.StartMockProvider(tc.address, nil, opts...) t.Cleanup(cleanup) provider = p } @@ -685,25 +685,38 @@ func TestConcurrentIsAuthenticated(t *testing.T) { "First_auth_starts_first_but_second_finishes_first": {firstCallDelay: 3, timeBetween: time.Second}, "First_auth_starts_first_then_second_starts_and_first_finishes": {firstCallDelay: 2, secondCallDelay: 3, timeBetween: time.Second}, } + for name, tc := range tests { t.Run(name, func(t *testing.T) { outDir := t.TempDir() dataDir := filepath.Join(outDir, "data") err := os.Mkdir(dataDir, 0700) require.NoError(t, err, "Setup: Mkdir should not have returned an error") + + username1 := "user1@example.com" + username2 := "user2@example.com" + + provider, cleanup := testutils.StartMockProvider("", &testutils.TokenHandlerOptions{ + IDTokenClaims: []map[string]interface{}{ + {"sub": "user1", "name": "user1", "email": username1}, + {"sub": "user2", "name": "user2", "email": username2}, + }, + }) + defer cleanup() + cfg := &broker.Config{DataDir: dataDir} - cfg.SetIssuerURL(defaultProvider.URL) + cfg.SetIssuerURL(provider.URL) mockInfoer := &testutils.MockProviderInfoer{FirstCallDelay: tc.firstCallDelay, SecondCallDelay: tc.secondCallDelay} b := newBrokerForTests(t, *cfg, mockInfoer) - firstSession, firstKey := newSessionForTests(t, b, "user1", "") - firstToken := tokenOptions{username: "user1", issuer: defaultProvider.URL} + firstSession, firstKey := newSessionForTests(t, b, username1, "") + firstToken := tokenOptions{username: username1, issuer: provider.URL} generateAndStoreCachedInfo(t, firstToken, b.TokenPathForSession(firstSession)) err = password.HashAndStorePassword("password", b.PasswordFilepathForSession(firstSession)) require.NoError(t, err, "Setup: HashAndStorePassword should not have returned an error") - secondSession, secondKey := newSessionForTests(t, b, "user2", "") - secondToken := tokenOptions{username: "user2", issuer: defaultProvider.URL} + secondSession, secondKey := newSessionForTests(t, b, username2, "") + secondToken := tokenOptions{username: username2, issuer: provider.URL} generateAndStoreCachedInfo(t, secondToken, b.TokenPathForSession(secondSession)) err = password.HashAndStorePassword("password", b.PasswordFilepathForSession(secondSession)) require.NoError(t, err, "Setup: HashAndStorePassword should not have returned an error") @@ -773,7 +786,7 @@ func TestConcurrentIsAuthenticated(t *testing.T) { // Ensure that the directory structure is generic to avoid golden file conflicts issuerDataDir := filepath.Dir(b.UserDataDirForSession(firstSession)) if _, err := os.Stat(issuerDataDir); err == nil { - toReplace := strings.ReplaceAll(strings.TrimPrefix(defaultProvider.URL, "http://"), ":", "_") + toReplace := strings.ReplaceAll(strings.TrimPrefix(provider.URL, "http://"), ":", "_") newIssuerDataDir := strings.ReplaceAll(issuerDataDir, toReplace, "provider_url") err := os.Rename(issuerDataDir, newIssuerDataDir) if err != nil { @@ -867,7 +880,7 @@ func TestFetchUserInfo(t *testing.T) { func TestCancelIsAuthenticated(t *testing.T) { t.Parallel() - provider, cleanup := testutils.StartMockProvider("", testutils.WithHandler("/token", testutils.HangingHandler(3*time.Second))) + provider, cleanup := testutils.StartMockProvider("", nil, testutils.WithHandler("/token", testutils.HangingHandler(3*time.Second))) t.Cleanup(cleanup) cfg := &broker.Config{} @@ -970,7 +983,7 @@ func TestUserPreCheck(t *testing.T) { } func TestMain(m *testing.M) { - server, cleanup := testutils.StartMockProvider("") + server, cleanup := testutils.StartMockProvider("", nil) defer cleanup() defaultProvider = server diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index b7859bd247..e85886d40b 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -82,7 +82,7 @@ func WithHandler(path string, handler func(http.ResponseWriter, *http.Request)) } // StartMockProvider starts a new HTTP server to be used as an OpenID Connect provider for tests. -func StartMockProvider(address string, args ...OptionProvider) (*httptest.Server, func()) { +func StartMockProvider(address string, tokenHandlerOpts *TokenHandlerOptions, args ...OptionProvider) (*httptest.Server, func()) { servMux := http.NewServeMux() server := httptest.NewUnstartedServer(servMux) @@ -99,7 +99,7 @@ func StartMockProvider(address string, args ...OptionProvider) (*httptest.Server handlers: map[string]ProviderHandler{ "/.well-known/openid-configuration": DefaultOpenIDHandler(server.URL), "/device_auth": DefaultDeviceAuthHandler(), - "/token": TokenHandler(server.URL, consts.DefaultScopes), + "/token": TokenHandler(server.URL, tokenHandlerOpts), "/keys": DefaultJWKHandler(), }, } @@ -175,13 +175,32 @@ func DefaultDeviceAuthHandler() ProviderHandler { } } +// TokenHandlerOptions contains options for the token handler. +type TokenHandlerOptions struct { + Scopes []string + // A list of custom claims to be added to the ID token. Each time the + // handler returns a token, the claims from the first element of the list + // will be added to the token, and then that element will be removed from + // the list. + IDTokenClaims []map[string]interface{} +} + +var idTokenClaimsMutex sync.Mutex + // TokenHandler returns a handler that returns a default token response. -func TokenHandler(serverURL string, scopes []string) ProviderHandler { +func TokenHandler(serverURL string, opts *TokenHandlerOptions) ProviderHandler { + if opts == nil { + opts = &TokenHandlerOptions{} + } + if opts.Scopes == nil { + opts.Scopes = consts.DefaultScopes + } + return func(w http.ResponseWriter, r *http.Request) { // Mimics user going through auth process time.Sleep(2 * time.Second) - idToken := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{ + claims := jwt.MapClaims{ "iss": serverURL, "sub": "test-user-id", "aud": "test-client-id", @@ -190,7 +209,19 @@ func TokenHandler(serverURL string, scopes []string) ProviderHandler { "preferred_username": "test-user-preferred-username@email.com", "email": "test-user@email.com", "email_verified": true, - }) + } + + idTokenClaimsMutex.Lock() + // Override the default claims with the custom claims + if len(opts.IDTokenClaims) > 0 { + for k, v := range opts.IDTokenClaims[0] { + claims[k] = v + } + opts.IDTokenClaims = opts.IDTokenClaims[1:] + } + idTokenClaimsMutex.Unlock() + + idToken := jwt.NewWithClaims(jwt.SigningMethodRS256, claims) rawToken, err := idToken.SignedString(MockKey) if err != nil { @@ -204,7 +235,7 @@ func TokenHandler(serverURL string, scopes []string) ProviderHandler { "scope": "%s", "expires_in": 3600, "id_token": "%s" - }`, strings.Join(scopes, " "), rawToken) + }`, strings.Join(opts.Scopes, " "), rawToken) w.Header().Add("Content-Type", "application/json") if _, err := w.Write([]byte(response)); err != nil { From adc4a426e7967dbbba0019f2c8b3f7cd5f0b2576 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 12 Nov 2024 19:18:11 +0100 Subject: [PATCH 0222/1670] Fix tests Now that we always refresh the token, we need to ensure that the username returned in the token matches the one in the session, else refreshing the user info fails. --- .../data/provider_url/{user1 => user1@example.com}/password | 0 .../data/provider_url/{user1 => user1@example.com}/token.json | 0 .../data/provider_url/{user2 => user2@example.com}/password | 0 .../data/provider_url/{user2 => user2@example.com}/token.json | 0 .../First_auth_starts_and_finishes_before_second/first_auth | 2 +- .../First_auth_starts_and_finishes_before_second/second_auth | 2 +- .../data/provider_url/{user1 => user1@example.com}/password | 0 .../data/provider_url/{user1 => user1@example.com}/token.json | 0 .../data/provider_url/{user2 => user2@example.com}/password | 0 .../data/provider_url/{user2 => user2@example.com}/token.json | 0 .../first_auth | 2 +- .../second_auth | 2 +- .../data/provider_url/{user1 => user1@example.com}/password | 0 .../data/provider_url/{user1 => user1@example.com}/token.json | 0 .../data/provider_url/{user2 => user2@example.com}/password | 0 .../data/provider_url/{user2 => user2@example.com}/token.json | 0 .../first_auth | 2 +- .../second_auth | 2 +- 18 files changed, 6 insertions(+), 6 deletions(-) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/{user1 => user1@example.com}/password (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/{user1 => user1@example.com}/token.json (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/{user2 => user2@example.com}/password (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/{user2 => user2@example.com}/token.json (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/{user1 => user1@example.com}/password (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/{user1 => user1@example.com}/token.json (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/{user2 => user2@example.com}/password (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/{user2 => user2@example.com}/token.json (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/{user1 => user1@example.com}/password (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/{user1 => user1@example.com}/token.json (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/{user2 => user2@example.com}/password (100%) rename internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/{user2 => user2@example.com}/token.json (100%) diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user1/password b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user1@example.com/password similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user1/password rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user1@example.com/password diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user1/token.json b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user1@example.com/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user1/token.json rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user1@example.com/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user2/password b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user2@example.com/password similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user2/password rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user2@example.com/password diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user2/token.json b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user2@example.com/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user2/token.json rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user2@example.com/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/first_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/first_auth index 13dbd8761c..073d10ad49 100644 --- a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/first_auth +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/first_auth @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"user1","uuid":"saved-user-id","dir":"/home/user1","shell":"/usr/bin/bash","gecos":"user1","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"user1@example.com","uuid":"user1","dir":"/home/user1@example.com","shell":"/usr/bin/bash","gecos":"user1@example.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/second_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/second_auth index c0b252ffdf..c73c8a4d4c 100644 --- a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/second_auth +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/second_auth @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"user2","uuid":"saved-user-id","dir":"/home/user2","shell":"/usr/bin/bash","gecos":"user2","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"user2@example.com","uuid":"user2","dir":"/home/user2@example.com","shell":"/usr/bin/bash","gecos":"user2@example.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1/password b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1@example.com/password similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1/password rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1@example.com/password diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1/token.json b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1@example.com/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1/token.json rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1@example.com/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2/password b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2@example.com/password similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2/password rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2@example.com/password diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2/token.json b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2@example.com/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2/token.json rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2@example.com/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/first_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/first_auth index 13dbd8761c..073d10ad49 100644 --- a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/first_auth +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/first_auth @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"user1","uuid":"saved-user-id","dir":"/home/user1","shell":"/usr/bin/bash","gecos":"user1","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"user1@example.com","uuid":"user1","dir":"/home/user1@example.com","shell":"/usr/bin/bash","gecos":"user1@example.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/second_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/second_auth index c0b252ffdf..c73c8a4d4c 100644 --- a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/second_auth +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/second_auth @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"user2","uuid":"saved-user-id","dir":"/home/user2","shell":"/usr/bin/bash","gecos":"user2","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"user2@example.com","uuid":"user2","dir":"/home/user2@example.com","shell":"/usr/bin/bash","gecos":"user2@example.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1/password b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1@example.com/password similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1/password rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1@example.com/password diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1/token.json b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1@example.com/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1/token.json rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1@example.com/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2/password b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2@example.com/password similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2/password rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2@example.com/password diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2/token.json b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2@example.com/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2/token.json rename to internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2@example.com/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/first_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/first_auth index 13dbd8761c..073d10ad49 100644 --- a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/first_auth +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/first_auth @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"user1","uuid":"saved-user-id","dir":"/home/user1","shell":"/usr/bin/bash","gecos":"user1","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"user1@example.com","uuid":"user1","dir":"/home/user1@example.com","shell":"/usr/bin/bash","gecos":"user1@example.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/second_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/second_auth index c0b252ffdf..c73c8a4d4c 100644 --- a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/second_auth +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/second_auth @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"user2","uuid":"saved-user-id","dir":"/home/user2","shell":"/usr/bin/bash","gecos":"user2","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"user2@example.com","uuid":"user2","dir":"/home/user2@example.com","shell":"/usr/bin/bash","gecos":"user2@example.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' err: From ed031f6f8d05d96af3edc7fa3154ce3e818370e9 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 11 Nov 2024 19:05:41 +0100 Subject: [PATCH 0223/1670] Remove obsolete comment The function does not do any dconf database filtering --- internal/testutils/golden.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/testutils/golden.go b/internal/testutils/golden.go index 279ce5f938..61d40c94ea 100644 --- a/internal/testutils/golden.go +++ b/internal/testutils/golden.go @@ -122,7 +122,6 @@ func GoldenPath(t *testing.T) string { } // CompareTreesWithFiltering allows comparing a goldPath directory to p. Those can be updated via the dedicated flag. -// It will filter dconf database and not commit it in the new golden directory. func CompareTreesWithFiltering(t *testing.T, p, goldPath string, update bool) { t.Helper() From 0404bb664c05a3adf40ffe61bdc02c9a4bff7176 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 11 Nov 2024 19:07:37 +0100 Subject: [PATCH 0224/1670] Rename CompareTreesWithFiltering -> CheckOrUpdateGoldenFileTree * Remove the "Filtering" part from the name because the function doesn't do any filtering * Mention that it can also update the golden files --- internal/broker/broker_test.go | 4 ++-- internal/broker/config_test.go | 2 +- internal/testutils/golden.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index d44d620c9d..14bd3f5ec4 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -668,7 +668,7 @@ func TestIsAuthenticated(t *testing.T) { } } - testutils.CompareTreesWithFiltering(t, outDir, testutils.GoldenPath(t), testutils.UpdateEnabled()) + testutils.CheckOrUpdateGoldenFileTree(t, outDir, testutils.GoldenPath(t), testutils.UpdateEnabled()) }) } } @@ -794,7 +794,7 @@ func TestConcurrentIsAuthenticated(t *testing.T) { t.Logf("Failed to rename issuer data directory: %v", err) } } - testutils.CompareTreesWithFiltering(t, outDir, testutils.GoldenPath(t), testutils.UpdateEnabled()) + testutils.CheckOrUpdateGoldenFileTree(t, outDir, testutils.GoldenPath(t), testutils.UpdateEnabled()) }) } } diff --git a/internal/broker/config_test.go b/internal/broker/config_test.go index aee75211e3..4685c9dfa5 100644 --- a/internal/broker/config_test.go +++ b/internal/broker/config_test.go @@ -106,7 +106,7 @@ func TestParseConfig(t *testing.T) { err = os.WriteFile(filepath.Join(outDir, "config.txt"), []byte(strings.Join(fields, "\n")), 0600) require.NoError(t, err) - testutils.CompareTreesWithFiltering(t, outDir, testutils.GoldenPath(t), testutils.UpdateEnabled()) + testutils.CheckOrUpdateGoldenFileTree(t, outDir, testutils.GoldenPath(t), testutils.UpdateEnabled()) }) } } diff --git a/internal/testutils/golden.go b/internal/testutils/golden.go index 61d40c94ea..d930b42120 100644 --- a/internal/testutils/golden.go +++ b/internal/testutils/golden.go @@ -121,8 +121,8 @@ func GoldenPath(t *testing.T) string { return path } -// CompareTreesWithFiltering allows comparing a goldPath directory to p. Those can be updated via the dedicated flag. -func CompareTreesWithFiltering(t *testing.T, p, goldPath string, update bool) { +// CheckOrUpdateGoldenFileTree allows comparing a goldPath directory to p. Those can be updated via the dedicated flag. +func CheckOrUpdateGoldenFileTree(t *testing.T, p, goldPath string, update bool) { t.Helper() // UpdateEnabled golden file From f638255354ce70f6353ed53511333cc4120b0c35 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 11 Nov 2024 19:09:50 +0100 Subject: [PATCH 0225/1670] Remove "update" argument from CheckOrUpdateGoldenFileTree We don't need to pass it, the function can access the package variable instead. --- internal/broker/broker_test.go | 4 ++-- internal/broker/config_test.go | 2 +- internal/testutils/golden.go | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 14bd3f5ec4..67b0b4a888 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -668,7 +668,7 @@ func TestIsAuthenticated(t *testing.T) { } } - testutils.CheckOrUpdateGoldenFileTree(t, outDir, testutils.GoldenPath(t), testutils.UpdateEnabled()) + testutils.CheckOrUpdateGoldenFileTree(t, outDir, testutils.GoldenPath(t)) }) } } @@ -794,7 +794,7 @@ func TestConcurrentIsAuthenticated(t *testing.T) { t.Logf("Failed to rename issuer data directory: %v", err) } } - testutils.CheckOrUpdateGoldenFileTree(t, outDir, testutils.GoldenPath(t), testutils.UpdateEnabled()) + testutils.CheckOrUpdateGoldenFileTree(t, outDir, testutils.GoldenPath(t)) }) } } diff --git a/internal/broker/config_test.go b/internal/broker/config_test.go index 4685c9dfa5..bd02d62fe0 100644 --- a/internal/broker/config_test.go +++ b/internal/broker/config_test.go @@ -106,7 +106,7 @@ func TestParseConfig(t *testing.T) { err = os.WriteFile(filepath.Join(outDir, "config.txt"), []byte(strings.Join(fields, "\n")), 0600) require.NoError(t, err) - testutils.CheckOrUpdateGoldenFileTree(t, outDir, testutils.GoldenPath(t), testutils.UpdateEnabled()) + testutils.CheckOrUpdateGoldenFileTree(t, outDir, testutils.GoldenPath(t)) }) } } diff --git a/internal/testutils/golden.go b/internal/testutils/golden.go index d930b42120..b9cf6c7c1d 100644 --- a/internal/testutils/golden.go +++ b/internal/testutils/golden.go @@ -122,10 +122,9 @@ func GoldenPath(t *testing.T) string { } // CheckOrUpdateGoldenFileTree allows comparing a goldPath directory to p. Those can be updated via the dedicated flag. -func CheckOrUpdateGoldenFileTree(t *testing.T, p, goldPath string, update bool) { +func CheckOrUpdateGoldenFileTree(t *testing.T, p, goldPath string) { t.Helper() - // UpdateEnabled golden file if update { t.Logf("updating golden file %s", goldPath) require.NoError(t, os.RemoveAll(goldPath), "Cannot remove target golden directory") From e7de8bb98093b3f8f550121cfbbefb0a2de2643e Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 11 Nov 2024 19:11:16 +0100 Subject: [PATCH 0226/1670] Don't nest function calls in require.NoError Move function calls out of require.NoError for improved readability. --- internal/testutils/golden.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/testutils/golden.go b/internal/testutils/golden.go index b9cf6c7c1d..8b0dc2a418 100644 --- a/internal/testutils/golden.go +++ b/internal/testutils/golden.go @@ -127,7 +127,8 @@ func CheckOrUpdateGoldenFileTree(t *testing.T, p, goldPath string) { if update { t.Logf("updating golden file %s", goldPath) - require.NoError(t, os.RemoveAll(goldPath), "Cannot remove target golden directory") + err := os.RemoveAll(goldPath) + require.NoError(t, err, "Cannot remove target golden directory") // check the source directory exists before trying to copy it info, err := os.Stat(p) @@ -140,7 +141,8 @@ func CheckOrUpdateGoldenFileTree(t *testing.T, p, goldPath string) { // copy file data, err := os.ReadFile(p) require.NoError(t, err, "Cannot read new generated file file %s", p) - require.NoError(t, os.WriteFile(goldPath, data, info.Mode()), "Cannot write golden file") + err = os.WriteFile(goldPath, data, info.Mode()) + require.NoError(t, err, "Cannot write golden file") } else { err := addEmptyMarker(p) require.NoError(t, err, "Cannot add empty marker to directory %s", p) From 126b619725800025f0cab688a1fd9766d0ee6da5 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 11 Nov 2024 19:14:58 +0100 Subject: [PATCH 0227/1670] Rename arguments --- internal/testutils/golden.go | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/internal/testutils/golden.go b/internal/testutils/golden.go index 8b0dc2a418..d2dadf898a 100644 --- a/internal/testutils/golden.go +++ b/internal/testutils/golden.go @@ -122,47 +122,47 @@ func GoldenPath(t *testing.T) string { } // CheckOrUpdateGoldenFileTree allows comparing a goldPath directory to p. Those can be updated via the dedicated flag. -func CheckOrUpdateGoldenFileTree(t *testing.T, p, goldPath string) { +func CheckOrUpdateGoldenFileTree(t *testing.T, path, goldenPath string) { t.Helper() if update { - t.Logf("updating golden file %s", goldPath) - err := os.RemoveAll(goldPath) + t.Logf("updating golden path %s", goldenPath) + err := os.RemoveAll(goldenPath) require.NoError(t, err, "Cannot remove target golden directory") // check the source directory exists before trying to copy it - info, err := os.Stat(p) + info, err := os.Stat(path) if errors.Is(err, fs.ErrNotExist) { return } - require.NoErrorf(t, err, "Error on checking %q", p) + require.NoErrorf(t, err, "Error on checking %q", path) if !info.IsDir() { // copy file - data, err := os.ReadFile(p) - require.NoError(t, err, "Cannot read new generated file file %s", p) - err = os.WriteFile(goldPath, data, info.Mode()) + data, err := os.ReadFile(path) + require.NoError(t, err, "Cannot read new generated file file %s", path) + err = os.WriteFile(goldenPath, data, info.Mode()) require.NoError(t, err, "Cannot write golden file") } else { - err := addEmptyMarker(p) - require.NoError(t, err, "Cannot add empty marker to directory %s", p) + err := addEmptyMarker(path) + require.NoError(t, err, "Cannot add empty marker to directory %s", path) - err = cp.Copy(p, goldPath) + err = cp.Copy(path, goldenPath) require.NoError(t, err, "Can’t update golden directory") } } var gotContent map[string]treeAttrs - if _, err := os.Stat(p); err == nil { - gotContent, err = treeContentAndAttrs(t, p, nil) + if _, err := os.Stat(path); err == nil { + gotContent, err = treeContentAndAttrs(t, path, nil) if err != nil { t.Fatalf("No generated content: %v", err) } } var goldContent map[string]treeAttrs - if _, err := os.Stat(goldPath); err == nil { - goldContent, err = treeContentAndAttrs(t, goldPath, nil) + if _, err := os.Stat(goldenPath); err == nil { + goldContent, err = treeContentAndAttrs(t, goldenPath, nil) if err != nil { t.Fatalf("No golden directory found: %v", err) } @@ -175,8 +175,8 @@ func CheckOrUpdateGoldenFileTree(t *testing.T, p, goldPath string) { } require.Empty(t, gotContent, "Some files are missing in the golden directory") - // No more verification on p if it doesn’t exists - if _, err := os.Stat(p); errors.Is(err, fs.ErrNotExist) { + // No more verification on path if it doesn’t exists + if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) { return } } From d994038d511fc48eae23e6c3d0cb98703eb65fe1 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 11 Nov 2024 18:46:11 +0100 Subject: [PATCH 0228/1670] Print golden file path on mismatch --- internal/broker/broker_test.go | 12 +-- internal/testutils/golden.go | 177 +++++++++++++++++---------------- 2 files changed, 96 insertions(+), 93 deletions(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 67b0b4a888..4061bb1327 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -259,8 +259,7 @@ func TestGetAuthenticationModes(t *testing.T) { } require.NoError(t, err, "GetAuthenticationModes should not have returned an error") - want := testutils.LoadWithUpdateFromGoldenYAML(t, got) - require.Equal(t, want, got, "GetAuthenticationModes should have returned the expected value") + testutils.CheckOrUpdateGoldenYAML(t, got, nil) }) } } @@ -375,8 +374,7 @@ func TestSelectAuthenticationMode(t *testing.T) { } require.NoError(t, err, "SelectAuthenticationMode should not have returned an error") - want := testutils.LoadWithUpdateFromGoldenYAML(t, got) - require.Equal(t, want, got, "SelectAuthenticationMode should have returned the expected layout") + testutils.CheckOrUpdateGoldenYAML(t, got, nil) }) } } @@ -871,8 +869,7 @@ func TestFetchUserInfo(t *testing.T) { } require.NoError(t, err, "FetchUserInfo should not have returned an error") - want := testutils.LoadWithUpdateFromGoldenYAML(t, got) - require.Equal(t, want, got, "FetchUserInfo should have returned the expected value") + testutils.CheckOrUpdateGoldenYAML(t, got, nil) }) } } @@ -976,8 +973,7 @@ func TestUserPreCheck(t *testing.T) { } require.NoError(t, err, "UserPreCheck should not have returned an error") - want := testutils.LoadWithUpdateFromGolden(t, got) - require.Equal(t, want, got, "UserPreCheck should have returned the expected value") + testutils.CheckOrUpdateGolden(t, got, nil) }) } } diff --git a/internal/testutils/golden.go b/internal/testutils/golden.go index d2dadf898a..55e15d8155 100644 --- a/internal/testutils/golden.go +++ b/internal/testutils/golden.go @@ -1,11 +1,11 @@ package testutils import ( - "bytes" "errors" "io/fs" "os" "path/filepath" + "strconv" "strings" "testing" @@ -28,44 +28,63 @@ func init() { } } -type goldenOptions struct { - goldenPath string +// GoldenOptions are options for functions that work with golden files. +type GoldenOptions struct { + Path string } -// GoldenOption is a supported option reference to change the golden files comparison. -type GoldenOption func(*goldenOptions) +func updateGoldenFile(t *testing.T, path string, data []byte) { + t.Logf("updating golden file %s", path) + err := os.MkdirAll(filepath.Dir(path), 0750) + require.NoError(t, err, "Cannot create directory for updating golden files") + err = os.WriteFile(path, data, 0600) + require.NoError(t, err, "Cannot write golden file") +} -// WithGoldenPath overrides the default path for golden files used. -func WithGoldenPath(path string) GoldenOption { - return func(o *goldenOptions) { - if path != "" { - o.goldenPath = path - } +// CheckOrUpdateGolden compares the provided string with the content of the golden file. If the update environment +// variable is set, the golden file is updated with the provided string. +func CheckOrUpdateGolden(t *testing.T, got string, opts *GoldenOptions) { + t.Helper() + + if opts == nil { + opts = &GoldenOptions{} + } + if opts.Path == "" { + opts.Path = GoldenPath(t) } + + want := LoadWithUpdateFromGolden(t, got, opts) + require.Equal(t, want, got, "Output does not match golden file %s", opts.Path) +} + +// CheckOrUpdateGoldenYAML compares the provided object with the content of the golden file. If the update environment +// variable is set, the golden file is updated with the provided object serialized as YAML. +func CheckOrUpdateGoldenYAML[E any](t *testing.T, got E, opts *GoldenOptions) { + t.Helper() + + data, err := yaml.Marshal(got) + require.NoError(t, err, "Cannot serialize provided object") + + CheckOrUpdateGolden(t, string(data), opts) } // LoadWithUpdateFromGolden loads the element from a plaintext golden file. // It will update the file if the update flag is used prior to loading it. -func LoadWithUpdateFromGolden(t *testing.T, data string, opts ...GoldenOption) string { +func LoadWithUpdateFromGolden(t *testing.T, data string, opts *GoldenOptions) string { t.Helper() - o := goldenOptions{ - goldenPath: GoldenPath(t), + if opts == nil { + opts = &GoldenOptions{} } - - for _, opt := range opts { - opt(&o) + if opts.Path == "" { + opts.Path = GoldenPath(t) } if update { - t.Logf("updating golden file %s", o.goldenPath) - err := os.MkdirAll(filepath.Dir(o.goldenPath), 0750) - require.NoError(t, err, "Cannot create directory for updating golden files") - err = os.WriteFile(o.goldenPath, []byte(data), 0600) - require.NoError(t, err, "Cannot write golden file") + updateGoldenFile(t, opts.Path, []byte(data)) } - want, err := os.ReadFile(o.goldenPath) + want, err := os.ReadFile(opts.Path) require.NoError(t, err, "Cannot load golden file") return string(want) @@ -73,13 +92,13 @@ func LoadWithUpdateFromGolden(t *testing.T, data string, opts ...GoldenOption) s // LoadWithUpdateFromGoldenYAML load the generic element from a YAML serialized golden file. // It will update the file if the update flag is used prior to deserializing it. -func LoadWithUpdateFromGoldenYAML[E any](t *testing.T, got E, opts ...GoldenOption) E { +func LoadWithUpdateFromGoldenYAML[E any](t *testing.T, got E, opts *GoldenOptions) E { t.Helper() t.Logf("Serializing object for golden file") data, err := yaml.Marshal(got) require.NoError(t, err, "Cannot serialize provided object") - want := LoadWithUpdateFromGolden(t, string(data), opts...) + want := LoadWithUpdateFromGolden(t, string(data), opts) var wantDeserialized E err = yaml.Unmarshal([]byte(want), &wantDeserialized) @@ -128,7 +147,7 @@ func CheckOrUpdateGoldenFileTree(t *testing.T, path, goldenPath string) { if update { t.Logf("updating golden path %s", goldenPath) err := os.RemoveAll(goldenPath) - require.NoError(t, err, "Cannot remove target golden directory") + require.NoError(t, err, "Cannot remove golden path %s", goldenPath) // check the source directory exists before trying to copy it info, err := os.Stat(path) @@ -140,7 +159,7 @@ func CheckOrUpdateGoldenFileTree(t *testing.T, path, goldenPath string) { if !info.IsDir() { // copy file data, err := os.ReadFile(path) - require.NoError(t, err, "Cannot read new generated file file %s", path) + require.NoError(t, err, "Cannot read file %s", path) err = os.WriteFile(goldenPath, data, info.Mode()) require.NoError(t, err, "Cannot write golden file") } else { @@ -152,86 +171,74 @@ func CheckOrUpdateGoldenFileTree(t *testing.T, path, goldenPath string) { } } - var gotContent map[string]treeAttrs - if _, err := os.Stat(path); err == nil { - gotContent, err = treeContentAndAttrs(t, path, nil) + // Compare the content and attributes of the files in the directories. + err := filepath.WalkDir(path, func(p string, de fs.DirEntry, err error) error { if err != nil { - t.Fatalf("No generated content: %v", err) + return err } - } - var goldContent map[string]treeAttrs - if _, err := os.Stat(goldenPath); err == nil { - goldContent, err = treeContentAndAttrs(t, goldenPath, nil) - if err != nil { - t.Fatalf("No golden directory found: %v", err) - } - } + relPath, err := filepath.Rel(path, p) + require.NoError(t, err, "Cannot get relative path for %s", p) + goldenFilePath := filepath.Join(goldenPath, relPath) - // Maps are not ordered, so we need to compare the content and attributes of each file - for key, value := range goldContent { - require.Equal(t, value, gotContent[key], "Content or attributes are different for %s", key) - delete(gotContent, key) - } - require.Empty(t, gotContent, "Some files are missing in the golden directory") + if de.IsDir() { + return nil + } - // No more verification on path if it doesn’t exists - if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) { - return - } -} + goldenFile, err := os.Stat(goldenFilePath) + if errors.Is(err, fs.ErrNotExist) { + require.Failf(t, "Unexpected file %s", p) + } + require.NoError(t, err, "Cannot get golden file %s", goldenFilePath) -// treeAttrs are the attributes to take into consideration when comparing each file. -type treeAttrs struct { - content string - path string - executable bool -} + file, err := os.Stat(p) + require.NoError(t, err, "Cannot get file %s", p) -const fileForEmptyDir = ".empty" + // Compare executable bit + a := strconv.FormatInt(int64(goldenFile.Mode().Perm()&0o111), 8) + b := strconv.FormatInt(int64(file.Mode().Perm()&0o111), 8) + require.Equal(t, a, b, "Executable bit does not match.\nFile: %s\nGolden file: %s", p, goldenFilePath) -// treeContentAndAttrs builds a recursive file list of dir with their content and other attributes. -// It can ignore files starting with ignoreHeaders. -func treeContentAndAttrs(t *testing.T, dir string, ignoreHeaders []byte) (map[string]treeAttrs, error) { - t.Helper() + // Compare content + fileContent, err := os.ReadFile(p) + require.NoError(t, err, "Cannot read file %s", p) + goldenContent, err := os.ReadFile(goldenFilePath) + require.NoError(t, err, "Cannot read golden file %s", goldenFilePath) + require.Equal(t, string(fileContent), string(goldenContent), "Content does not match.\nFile: %s\nGolden file: %s", p, goldenFilePath) - r := make(map[string]treeAttrs) + return nil + }) + require.NoError(t, err, "Cannot walk through directory %s", path) - err := filepath.WalkDir(dir, func(path string, de fs.DirEntry, err error) error { + // Check if there are files in the golden directory that are not in the source directory. + err = filepath.WalkDir(goldenPath, func(p string, de fs.DirEntry, err error) error { if err != nil { return err } - // Ignore markers for empty directories - if filepath.Base(path) == fileForEmptyDir { + // Ignore the ".empty" file + if de.Name() == fileForEmptyDir { return nil } - content := "" - info, err := os.Stat(path) - require.NoError(t, err, "Cannot stat %s", path) - if !de.IsDir() { - d, err := os.ReadFile(path) - if err != nil { - return err - } - // ignore given header - if ignoreHeaders != nil && bytes.HasPrefix(d, ignoreHeaders) { - return nil - } - content = string(d) + relPath, err := filepath.Rel(goldenPath, p) + require.NoError(t, err, "Cannot get relative path for %s", p) + filePath := filepath.Join(path, relPath) + + if de.IsDir() { + return nil } - trimmedPath := strings.TrimPrefix(path, dir) - r[trimmedPath] = treeAttrs{content, strings.TrimPrefix(path, dir), info.Mode()&0111 != 0} + + _, err = os.Stat(filePath) + require.NoError(t, err, "Missing expected file %s", filePath) + return nil }) - if err != nil { - return nil, err - } - - return r, nil + require.NoError(t, err, "Cannot walk through directory %s", goldenPath) } +const fileForEmptyDir = ".empty" + // addEmptyMarker adds to any empty directory, fileForEmptyDir to it. // That allows git to commit it. func addEmptyMarker(p string) error { From d1376a088dfb23acfb77d611aa4cf1ca7ae3f658 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 12 Nov 2024 10:27:05 +0100 Subject: [PATCH 0229/1670] Improve the diff of mismatching golden files Print the content in multiple lines instead of a single line. --- go.mod | 2 +- internal/testutils/golden.go | 67 ++++++++++++++++++++++++++++++++---- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 97e74f394b..730545a1b7 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/microsoftgraph/msgraph-sdk-go v1.48.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 github.com/otiai10/copy v1.14.0 + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 @@ -48,7 +49,6 @@ require ( github.com/microsoft/kiota-serialization-text-go v1.0.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect diff --git a/internal/testutils/golden.go b/internal/testutils/golden.go index 55e15d8155..3d49cd4c00 100644 --- a/internal/testutils/golden.go +++ b/internal/testutils/golden.go @@ -2,6 +2,7 @@ package testutils import ( "errors" + "fmt" "io/fs" "os" "path/filepath" @@ -10,6 +11,7 @@ import ( "testing" cp "github.com/otiai10/copy" + "github.com/pmezard/go-difflib/difflib" "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" ) @@ -53,8 +55,11 @@ func CheckOrUpdateGolden(t *testing.T, got string, opts *GoldenOptions) { opts.Path = GoldenPath(t) } - want := LoadWithUpdateFromGolden(t, got, opts) - require.Equal(t, want, got, "Output does not match golden file %s", opts.Path) + if update { + updateGoldenFile(t, opts.Path, []byte(got)) + } + + checkGoldenFileEqualsString(t, got, opts.Path) } // CheckOrUpdateGoldenYAML compares the provided object with the content of the golden file. If the update environment @@ -140,6 +145,58 @@ func GoldenPath(t *testing.T) string { return path } +// checkFileContent compares the content of the actual and golden files and reports any differences. +func checkFileContent(t *testing.T, actual, expected, actualPath, expectedPath string) { + if actual == expected { + return + } + + diff := difflib.UnifiedDiff{ + A: difflib.SplitLines(expected), + B: difflib.SplitLines(actual), + FromFile: "Expected (golden)", + ToFile: "Actual", + Context: 3, + } + diffStr, err := difflib.GetUnifiedDiffString(diff) + require.NoError(t, err, "Cannot get unified diff") + + msg := fmt.Sprintf("Golden file: %s", expectedPath) + if actualPath != "Actual" { + msg += fmt.Sprintf("\nFile: %s", actualPath) + } + + require.Failf(t, strings.Join([]string{ + "Golden file content mismatch", + "\nExpected (golden):", + strings.Repeat("-", 50), + strings.TrimSuffix(expected, "\n"), + strings.Repeat("-", 50), + "\nActual: ", + strings.Repeat("-", 50), + strings.TrimSuffix(actual, "\n"), + strings.Repeat("-", 50), + "\nDiff:", + diffStr, + }, "\n"), msg) +} + +func checkGoldenFileEqualsFile(t *testing.T, path, goldenPath string) { + fileContent, err := os.ReadFile(path) + require.NoError(t, err, "Cannot read file %s", path) + goldenContent, err := os.ReadFile(goldenPath) + require.NoError(t, err, "Cannot read golden file %s", goldenPath) + + checkFileContent(t, string(fileContent), string(goldenContent), path, goldenPath) +} + +func checkGoldenFileEqualsString(t *testing.T, got, goldenPath string) { + goldenContent, err := os.ReadFile(goldenPath) + require.NoError(t, err, "Cannot read golden file %s", goldenPath) + + checkFileContent(t, got, string(goldenContent), "Actual", goldenPath) +} + // CheckOrUpdateGoldenFileTree allows comparing a goldPath directory to p. Those can be updated via the dedicated flag. func CheckOrUpdateGoldenFileTree(t *testing.T, path, goldenPath string) { t.Helper() @@ -200,11 +257,7 @@ func CheckOrUpdateGoldenFileTree(t *testing.T, path, goldenPath string) { require.Equal(t, a, b, "Executable bit does not match.\nFile: %s\nGolden file: %s", p, goldenFilePath) // Compare content - fileContent, err := os.ReadFile(p) - require.NoError(t, err, "Cannot read file %s", p) - goldenContent, err := os.ReadFile(goldenFilePath) - require.NoError(t, err, "Cannot read golden file %s", goldenFilePath) - require.Equal(t, string(fileContent), string(goldenContent), "Content does not match.\nFile: %s\nGolden file: %s", p, goldenFilePath) + checkGoldenFileEqualsFile(t, p, goldenFilePath) return nil }) From 9487cd7460a42f6140a7320d31fb205e1469c09b Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 12 Nov 2024 10:58:51 +0100 Subject: [PATCH 0230/1670] Colorize diff of mismatching golden files Use delta to colorize the diff of mismatching golden files if delta is found in the PATH. --- internal/testutils/golden.go | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/internal/testutils/golden.go b/internal/testutils/golden.go index 3d49cd4c00..6396688934 100644 --- a/internal/testutils/golden.go +++ b/internal/testutils/golden.go @@ -1,10 +1,12 @@ package testutils import ( + "bytes" "errors" "fmt" "io/fs" "os" + "os/exec" "path/filepath" "strconv" "strings" @@ -145,6 +147,22 @@ func GoldenPath(t *testing.T) string { return path } +// runDelta pipes the unified diff through the `delta` command for word-level diff and coloring. +func runDelta(diff string) (string, error) { + cmd := exec.Command("delta", "--diff-so-fancy", "--hunk-header-style", "omit") + cmd.Stdin = strings.NewReader(diff) + + var out bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &out + + err := cmd.Run() + if err != nil { + return "", fmt.Errorf("failed to run delta: %w", err) + } + return out.String(), nil +} + // checkFileContent compares the content of the actual and golden files and reports any differences. func checkFileContent(t *testing.T, actual, expected, actualPath, expectedPath string) { if actual == expected { @@ -161,6 +179,15 @@ func checkFileContent(t *testing.T, actual, expected, actualPath, expectedPath s diffStr, err := difflib.GetUnifiedDiffString(diff) require.NoError(t, err, "Cannot get unified diff") + // Check if the `delta` command is available and use it to colorize the diff. + _, err = exec.LookPath("delta") + if err == nil { + diffStr, err = runDelta(diffStr) + require.NoError(t, err, "Cannot run delta") + } else { + diffStr = "\nDiff:\n" + diffStr + } + msg := fmt.Sprintf("Golden file: %s", expectedPath) if actualPath != "Actual" { msg += fmt.Sprintf("\nFile: %s", actualPath) @@ -176,7 +203,6 @@ func checkFileContent(t *testing.T, actual, expected, actualPath, expectedPath s strings.Repeat("-", 50), strings.TrimSuffix(actual, "\n"), strings.Repeat("-", 50), - "\nDiff:", diffStr, }, "\n"), msg) } From 838e863291455ddd77a901ab38f8100600b3897d Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 13 Nov 2024 16:54:21 +0100 Subject: [PATCH 0231/1670] Use variadic options --- internal/broker/broker_test.go | 8 ++--- internal/testutils/golden.go | 53 ++++++++++++++++++++-------------- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 4061bb1327..5574afc4ef 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -259,7 +259,7 @@ func TestGetAuthenticationModes(t *testing.T) { } require.NoError(t, err, "GetAuthenticationModes should not have returned an error") - testutils.CheckOrUpdateGoldenYAML(t, got, nil) + testutils.CheckOrUpdateGoldenYAML(t, got) }) } } @@ -374,7 +374,7 @@ func TestSelectAuthenticationMode(t *testing.T) { } require.NoError(t, err, "SelectAuthenticationMode should not have returned an error") - testutils.CheckOrUpdateGoldenYAML(t, got, nil) + testutils.CheckOrUpdateGoldenYAML(t, got) }) } } @@ -869,7 +869,7 @@ func TestFetchUserInfo(t *testing.T) { } require.NoError(t, err, "FetchUserInfo should not have returned an error") - testutils.CheckOrUpdateGoldenYAML(t, got, nil) + testutils.CheckOrUpdateGoldenYAML(t, got) }) } } @@ -973,7 +973,7 @@ func TestUserPreCheck(t *testing.T) { } require.NoError(t, err, "UserPreCheck should not have returned an error") - testutils.CheckOrUpdateGolden(t, got, nil) + testutils.CheckOrUpdateGolden(t, got) }) } } diff --git a/internal/testutils/golden.go b/internal/testutils/golden.go index 6396688934..9a693d2711 100644 --- a/internal/testutils/golden.go +++ b/internal/testutils/golden.go @@ -32,9 +32,20 @@ func init() { } } -// GoldenOptions are options for functions that work with golden files. -type GoldenOptions struct { - Path string +type goldenOptions struct { + path string +} + +// GoldenOption is a supported option reference to change the golden files comparison. +type GoldenOption func(*goldenOptions) + +// WithGoldenPath overrides the default path for golden files used. +func WithGoldenPath(path string) GoldenOption { + return func(o *goldenOptions) { + if path != "" { + o.path = path + } + } } func updateGoldenFile(t *testing.T, path string, data []byte) { @@ -47,51 +58,51 @@ func updateGoldenFile(t *testing.T, path string, data []byte) { // CheckOrUpdateGolden compares the provided string with the content of the golden file. If the update environment // variable is set, the golden file is updated with the provided string. -func CheckOrUpdateGolden(t *testing.T, got string, opts *GoldenOptions) { +func CheckOrUpdateGolden(t *testing.T, got string, options ...GoldenOption) { t.Helper() - if opts == nil { - opts = &GoldenOptions{} + opts := goldenOptions{ + path: GoldenPath(t), } - if opts.Path == "" { - opts.Path = GoldenPath(t) + for _, f := range options { + f(&opts) } if update { - updateGoldenFile(t, opts.Path, []byte(got)) + updateGoldenFile(t, opts.path, []byte(got)) } - checkGoldenFileEqualsString(t, got, opts.Path) + checkGoldenFileEqualsString(t, got, opts.path) } // CheckOrUpdateGoldenYAML compares the provided object with the content of the golden file. If the update environment // variable is set, the golden file is updated with the provided object serialized as YAML. -func CheckOrUpdateGoldenYAML[E any](t *testing.T, got E, opts *GoldenOptions) { +func CheckOrUpdateGoldenYAML[E any](t *testing.T, got E, options ...GoldenOption) { t.Helper() data, err := yaml.Marshal(got) require.NoError(t, err, "Cannot serialize provided object") - CheckOrUpdateGolden(t, string(data), opts) + CheckOrUpdateGolden(t, string(data), options...) } // LoadWithUpdateFromGolden loads the element from a plaintext golden file. // It will update the file if the update flag is used prior to loading it. -func LoadWithUpdateFromGolden(t *testing.T, data string, opts *GoldenOptions) string { +func LoadWithUpdateFromGolden(t *testing.T, data string, options ...GoldenOption) string { t.Helper() - if opts == nil { - opts = &GoldenOptions{} + opts := goldenOptions{ + path: GoldenPath(t), } - if opts.Path == "" { - opts.Path = GoldenPath(t) + for _, f := range options { + f(&opts) } if update { - updateGoldenFile(t, opts.Path, []byte(data)) + updateGoldenFile(t, opts.path, []byte(data)) } - want, err := os.ReadFile(opts.Path) + want, err := os.ReadFile(opts.path) require.NoError(t, err, "Cannot load golden file") return string(want) @@ -99,13 +110,13 @@ func LoadWithUpdateFromGolden(t *testing.T, data string, opts *GoldenOptions) st // LoadWithUpdateFromGoldenYAML load the generic element from a YAML serialized golden file. // It will update the file if the update flag is used prior to deserializing it. -func LoadWithUpdateFromGoldenYAML[E any](t *testing.T, got E, opts *GoldenOptions) E { +func LoadWithUpdateFromGoldenYAML[E any](t *testing.T, got E, options ...GoldenOption) E { t.Helper() t.Logf("Serializing object for golden file") data, err := yaml.Marshal(got) require.NoError(t, err, "Cannot serialize provided object") - want := LoadWithUpdateFromGolden(t, string(data), opts) + want := LoadWithUpdateFromGolden(t, string(data), options...) var wantDeserialized E err = yaml.Unmarshal([]byte(want), &wantDeserialized) From ea5374979d83e198c2cb26e5a68fc574f92c8e99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 18 Nov 2024 19:35:41 +0100 Subject: [PATCH 0232/1670] dbusservice: Fix indentation on method arguments --- internal/dbusservice/dbusservice.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/dbusservice/dbusservice.go b/internal/dbusservice/dbusservice.go index b0131fe575..357843ef7a 100644 --- a/internal/dbusservice/dbusservice.go +++ b/internal/dbusservice/dbusservice.go @@ -15,16 +15,16 @@ const intro = ` - - - - - + + + + + - - - + + + From ff546773a49a41ad507ff9ba1ede8d4ee03663db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 18 Nov 2024 19:36:03 +0100 Subject: [PATCH 0233/1670] dbusservice: Use array of strings maps arguments types for GetAuthenticationModes The method accepts a slice of maps and returns another slice of maps, so we should respect that in the introspection data. --- internal/dbusservice/dbusservice.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/dbusservice/dbusservice.go b/internal/dbusservice/dbusservice.go index 357843ef7a..2159cbd595 100644 --- a/internal/dbusservice/dbusservice.go +++ b/internal/dbusservice/dbusservice.go @@ -23,8 +23,8 @@ const intro = ` - - + + From ca35d7c883229b0a57418f24f7e0da180165a7ca Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 13 Nov 2024 13:35:41 +0100 Subject: [PATCH 0234/1670] ci: Use gotestfmt to format go test output The output of go test is notoriously hard to read, especially when a lot of tests are executed and a lot of output is produced, which is the case in our CI. This commit uses gotestfmt to format the go test output. It only prints the output of failed tests to stdout, because that's what we're usually interested in. In case we need the full test output, that's now uploaded as an artifact. --- .github/workflows/ci.yml | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aab5fb236d..8b6cc09ed9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,16 @@ jobs: with: go-version-file: go.mod + - name: Prepare tests artifacts path + run: | + set -eu + + artifacts_dir=$(mktemp -d --tmpdir authd-test-artifacts-XXXXXX) + echo AUTHD_TEST_ARTIFACTS_DIR="${artifacts_dir}" >> $GITHUB_ENV + + - name: Install gotestfmt and our wrapper script + uses: canonical/desktop-engineering/gh-actions/go/gotestfmt@main + - name: Run tests (with coverage collection) run: | set -eu @@ -37,10 +47,14 @@ jobs: rm -fr "${raw_cov_dir}" mkdir -p "${raw_cov_dir}" + # Print executed commands to ease debugging + set -x + # Overriding the default coverage directory is not an exported flag of go test (yet), so # we need to override it using the test.gocoverdir flag instead. #TODO: Update when https://go-review.googlesource.com/c/go/+/456595 is merged. - go test -cover -covermode=set ./... -shuffle=on -args -test.gocoverdir="${raw_cov_dir}" + go test -json -cover -covermode=set ./... -shuffle=on -args -test.gocoverdir="${raw_cov_dir}" 2>&1 | \ + gotestfmt --logfile "${AUTHD_TEST_ARTIFACTS_DIR}/gotestfmt.cover.log" # Convert the raw coverage data into textfmt so we can merge the Rust one into it go tool covdata textfmt -i="${raw_cov_dir}" -o="/tmp/coverage.out" @@ -48,11 +62,9 @@ jobs: # Filter out the testutils package and the pb.go file grep -v -e "testutils" "/tmp/coverage.out" >"/tmp/coverage.out.filtered" - - name: Run tests (with race detector) - run: | - go test -race ./... - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + - name: Upload test artifacts + if: failure() + uses: actions/upload-artifact@v4 with: - file: /tmp/coverage.out.filtered + name: authd-${{ github.job }}-artifacts-${{ github.run_attempt }} + path: ${{ env.AUTHD_TEST_ARTIFACTS_DIR }} From 48d4d8f3f593de4c34fef7d25a49948b132ab50d Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 14 Nov 2024 18:01:37 +0100 Subject: [PATCH 0235/1670] refactor: Rename sessionInfo struct to session The "Info" suffix doesn't add any useful information. --- internal/broker/broker.go | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index d9cff55c4c..54a276d1c1 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -60,13 +60,13 @@ type Broker struct { providerInfo providers.ProviderInfoer oidcCfg oidc.Config - currentSessions map[string]sessionInfo + currentSessions map[string]session currentSessionsMu sync.RWMutex privateKey *rsa.PrivateKey } -type sessionInfo struct { +type session struct { username string lang string mode string @@ -155,7 +155,7 @@ func New(cfg Config, args ...Option) (b *Broker, err error) { oidcCfg: oidc.Config{ClientID: cfg.clientID}, privateKey: privateKey, - currentSessions: make(map[string]sessionInfo), + currentSessions: make(map[string]session), currentSessionsMu: sync.RWMutex{}, } return b, nil @@ -166,7 +166,7 @@ func (b *Broker) NewSession(username, lang, mode string) (sessionID, encryptionK defer decorate.OnError(&err, "could not create new session for user %q", username) sessionID = uuid.New().String() - session := sessionInfo{ + s := session{ username: username, lang: lang, mode: mode, @@ -183,22 +183,22 @@ func (b *Broker) NewSession(username, lang, mode string) (sessionID, encryptionK _, issuer, _ := strings.Cut(b.cfg.issuerURL, "://") issuer = strings.ReplaceAll(issuer, "/", "_") issuer = strings.ReplaceAll(issuer, ":", "_") - session.userDataDir = filepath.Join(b.cfg.DataDir, issuer, username) + s.userDataDir = filepath.Join(b.cfg.DataDir, issuer, username) // The token is stored in $DATA_DIR/$ISSUER/$USERNAME/token.json. - session.tokenPath = filepath.Join(session.userDataDir, "token.json") + s.tokenPath = filepath.Join(s.userDataDir, "token.json") // The password is stored in $DATA_DIR/$ISSUER/$USERNAME/password. - session.passwordPath = filepath.Join(session.userDataDir, "password") - session.oldEncryptedTokenPath = filepath.Join(b.cfg.OldEncryptedTokensDir, issuer, username+".cache") + s.passwordPath = filepath.Join(s.userDataDir, "password") + s.oldEncryptedTokenPath = filepath.Join(b.cfg.OldEncryptedTokensDir, issuer, username+".cache") // Check whether to start the session in offline mode. - session.authCfg, err = b.connectToProvider(context.Background()) + s.authCfg, err = b.connectToProvider(context.Background()) if err != nil { slog.Debug(fmt.Sprintf("Could not connect to the provider: %v. Starting session in offline mode.", err)) - session.isOffline = true + s.isOffline = true } b.currentSessionsMu.Lock() - b.currentSessions[sessionID] = session + b.currentSessions[sessionID] = s b.currentSessionsMu.Unlock() return sessionID, base64.StdEncoding.EncodeToString(pubASN1), nil @@ -347,7 +347,7 @@ func (b *Broker) SelectAuthenticationMode(sessionID, authModeID string) (uiLayou return uiLayoutInfo, nil } -func (b *Broker) generateUILayout(session *sessionInfo, authModeID string) (map[string]string, error) { +func (b *Broker) generateUILayout(session *session, authModeID string) (map[string]string, error) { if !slices.Contains(session.authModes, authModeID) { return nil, fmt.Errorf("selected authentication mode %q does not exist", authModeID) } @@ -485,7 +485,7 @@ func (b *Broker) IsAuthenticated(sessionID, authenticationData string) (string, return access, data, nil } -func (b *Broker) handleIsAuthenticated(ctx context.Context, session *sessionInfo, authData map[string]string) (access string, data isAuthenticatedDataResponse) { +func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, authData map[string]string) (access string, data isAuthenticatedDataResponse) { defer decorateErrorMessage(&data, "authentication failure") // Decrypt challenge if present. @@ -717,18 +717,18 @@ func (b *Broker) UserPreCheck(username string) (string, error) { } // getSession returns the session information for the specified session ID or an error if the session is not active. -func (b *Broker) getSession(sessionID string) (sessionInfo, error) { +func (b *Broker) getSession(sessionID string) (session, error) { b.currentSessionsMu.RLock() defer b.currentSessionsMu.RUnlock() - session, active := b.currentSessions[sessionID] + s, active := b.currentSessions[sessionID] if !active { - return sessionInfo{}, fmt.Errorf("%s is not a current transaction", sessionID) + return session{}, fmt.Errorf("%s is not a current transaction", sessionID) } - return session, nil + return s, nil } // updateSession checks if the session is still active and updates the session info. -func (b *Broker) updateSession(sessionID string, session sessionInfo) error { +func (b *Broker) updateSession(sessionID string, session session) error { // Checks if the session was ended in the meantime, otherwise we would just accidentally recreate it. if _, err := b.getSession(sessionID); err != nil { return err @@ -763,7 +763,7 @@ func (b *Broker) refreshToken(ctx context.Context, oauth2Config oauth2.Config, o return t, nil } -func (b *Broker) fetchUserInfo(ctx context.Context, session *sessionInfo, t *token.AuthCachedInfo) (userInfo info.User, err error) { +func (b *Broker) fetchUserInfo(ctx context.Context, session *session, t *token.AuthCachedInfo) (userInfo info.User, err error) { if session.isOffline { return info.User{}, errors.New("session is in offline mode") } From 1249bf6d05b1be00f103b622188595c803082103 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 14 Nov 2024 18:00:20 +0100 Subject: [PATCH 0236/1670] refactor: Inline fields of the authConfig struct into the session struct The authConfig struct added an unnecessary level of nested structs. --- internal/broker/broker.go | 50 ++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 54a276d1c1..ced2eeb7f5 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -76,7 +76,8 @@ type session struct { authModes []string attemptsPerMode map[string]int - authCfg authConfig + oidcProvider *oidc.Provider + oauth2Config oauth2.Config authInfo map[string]any isOffline bool userDataDir string @@ -89,12 +90,6 @@ type session struct { isAuthenticating *isAuthenticatedCtx } -// authConfig holds the required values for authenticating a user with OIDC. -type authConfig struct { - provider *oidc.Provider - oauth oauth2.Config -} - type isAuthenticatedCtx struct { ctx context.Context cancelFunc context.CancelFunc @@ -190,13 +185,22 @@ func (b *Broker) NewSession(username, lang, mode string) (sessionID, encryptionK s.passwordPath = filepath.Join(s.userDataDir, "password") s.oldEncryptedTokenPath = filepath.Join(b.cfg.OldEncryptedTokensDir, issuer, username+".cache") - // Check whether to start the session in offline mode. - s.authCfg, err = b.connectToProvider(context.Background()) + // Construct an OIDC provider via OIDC discovery. + s.oidcProvider, err = b.connectToProvider(context.Background()) if err != nil { slog.Debug(fmt.Sprintf("Could not connect to the provider: %v. Starting session in offline mode.", err)) s.isOffline = true } + if s.oidcProvider != nil { + s.oauth2Config = oauth2.Config{ + ClientID: b.oidcCfg.ClientID, + ClientSecret: b.cfg.clientSecret, + Endpoint: s.oidcProvider.Endpoint(), + Scopes: append(consts.DefaultScopes, b.providerInfo.AdditionalScopes()...), + } + } + b.currentSessionsMu.Lock() b.currentSessions[sessionID] = s b.currentSessionsMu.Unlock() @@ -204,23 +208,11 @@ func (b *Broker) NewSession(username, lang, mode string) (sessionID, encryptionK return sessionID, base64.StdEncoding.EncodeToString(pubASN1), nil } -func (b *Broker) connectToProvider(ctx context.Context) (authCfg authConfig, err error) { +func (b *Broker) connectToProvider(ctx context.Context) (*oidc.Provider, error) { ctx, cancel := context.WithTimeout(ctx, maxRequestDuration) defer cancel() - provider, err := oidc.NewProvider(ctx, b.cfg.issuerURL) - if err != nil { - return authConfig{}, err - } - - oauthCfg := oauth2.Config{ - ClientID: b.oidcCfg.ClientID, - ClientSecret: b.cfg.clientSecret, - Endpoint: provider.Endpoint(), - Scopes: append(consts.DefaultScopes, b.providerInfo.AdditionalScopes()...), - } - - return authConfig{provider: provider, oauth: oauthCfg}, nil + return oidc.NewProvider(ctx, b.cfg.issuerURL) } // GetAuthenticationModes returns the authentication modes available for the user. @@ -249,7 +241,7 @@ func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []m } endpoints := make(map[string]struct{}) - if session.authCfg.provider != nil && session.authCfg.provider.Endpoint().DeviceAuthURL != "" { + if session.oidcProvider != nil && session.oidcProvider.Endpoint().DeviceAuthURL != "" { authMode := authmodes.DeviceQr if _, ok := supportedAuthModes[authMode]; ok { endpoints[authMode] = struct{}{} @@ -364,13 +356,13 @@ func (b *Broker) generateUILayout(session *session, authModeID string) (map[stri // extra option, public providers tend to have bespoke implementation for passing client // credentials that completely bypass this // full explanation in https://github.com/golang/oauth2/issues/320 - if secret := session.authCfg.oauth.ClientSecret; secret != "" { + if secret := session.oauth2Config.ClientSecret; secret != "" { // TODO @shipperizer verificationMethod should be a configurable value verificationMethod := "client_post" authOpts = append(authOpts, oauth2.SetAuthURLParam(verificationMethod, secret)) } - response, err := session.authCfg.oauth.DeviceAuth(ctx, authOpts...) + response, err := session.oauth2Config.DeviceAuth(ctx, authOpts...) if err != nil { return nil, fmt.Errorf("could not generate Device Authentication code layout: %v", err) } @@ -509,7 +501,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au } expiryCtx, cancel := context.WithDeadline(ctx, response.Expiry) defer cancel() - t, err := session.authCfg.oauth.DeviceAccessToken(expiryCtx, response, b.providerInfo.AuthOptions()...) + t, err := session.oauth2Config.DeviceAccessToken(expiryCtx, response, b.providerInfo.AuthOptions()...) if err != nil { slog.Error(err.Error()) return AuthRetry, errorMessage{Message: "could not authenticate user remotely"} @@ -574,7 +566,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au // Refresh the token if we're online even if the token has not expired if !session.isOffline { - authInfo, err = b.refreshToken(ctx, session.authCfg.oauth, authInfo) + authInfo, err = b.refreshToken(ctx, session.oauth2Config, authInfo) if err != nil { slog.Error(err.Error()) return AuthDenied, errorMessage{Message: "could not refresh token"} @@ -768,7 +760,7 @@ func (b *Broker) fetchUserInfo(ctx context.Context, session *session, t *token.A return info.User{}, errors.New("session is in offline mode") } - idToken, err := session.authCfg.provider.Verifier(&b.oidcCfg).Verify(ctx, t.RawIDToken) + idToken, err := session.oidcProvider.Verifier(&b.oidcCfg).Verify(ctx, t.RawIDToken) if err != nil { return info.User{}, fmt.Errorf("could not verify token: %v", err) } From 782fbbdb6bf46afc5e5b0836622b992516b9f791 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 14 Nov 2024 18:30:53 +0100 Subject: [PATCH 0237/1670] refactor: Rename ProviderInfoer to Provider --- internal/broker/broker.go | 32 ++++++++++++++--------------- internal/broker/broker_test.go | 14 ++++++------- internal/broker/helper_test.go | 12 +++++------ internal/broker/options_test.go | 6 +++--- internal/providers/default.go | 4 ++-- internal/providers/providers.go | 4 ++-- internal/providers/withgoogle.go | 4 ++-- internal/providers/withmsentraid.go | 4 ++-- internal/testutils/provider.go | 16 +++++++-------- internal/token/token.go | 2 +- 10 files changed, 49 insertions(+), 49 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index ced2eeb7f5..42e1ac1574 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -57,8 +57,8 @@ type userConfig struct { type Broker struct { cfg Config - providerInfo providers.ProviderInfoer - oidcCfg oidc.Config + provider providers.Provider + oidcCfg oidc.Config currentSessions map[string]session currentSessionsMu sync.RWMutex @@ -96,7 +96,7 @@ type isAuthenticatedCtx struct { } type option struct { - providerInfo providers.ProviderInfoer + provider providers.Provider } // Option is a func that allows to override some of the broker default settings. @@ -114,7 +114,7 @@ func New(cfg Config, args ...Option) (b *Broker, err error) { } opts := option{ - providerInfo: providers.CurrentProviderInfo(), + provider: providers.CurrentProvider(), } for _, arg := range args { arg(&opts) @@ -145,10 +145,10 @@ func New(cfg Config, args ...Option) (b *Broker, err error) { } b = &Broker{ - cfg: cfg, - providerInfo: opts.providerInfo, - oidcCfg: oidc.Config{ClientID: cfg.clientID}, - privateKey: privateKey, + cfg: cfg, + provider: opts.provider, + oidcCfg: oidc.Config{ClientID: cfg.clientID}, + privateKey: privateKey, currentSessions: make(map[string]session), currentSessionsMu: sync.RWMutex{}, @@ -197,7 +197,7 @@ func (b *Broker) NewSession(username, lang, mode string) (sessionID, encryptionK ClientID: b.oidcCfg.ClientID, ClientSecret: b.cfg.clientSecret, Endpoint: s.oidcProvider.Endpoint(), - Scopes: append(consts.DefaultScopes, b.providerInfo.AdditionalScopes()...), + Scopes: append(consts.DefaultScopes, b.provider.AdditionalScopes()...), } } @@ -252,7 +252,7 @@ func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []m } } - availableModes, err := b.providerInfo.CurrentAuthenticationModesOffered( + availableModes, err := b.provider.CurrentAuthenticationModesOffered( session.mode, supportedAuthModes, tokenExists, @@ -501,13 +501,13 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au } expiryCtx, cancel := context.WithDeadline(ctx, response.Expiry) defer cancel() - t, err := session.oauth2Config.DeviceAccessToken(expiryCtx, response, b.providerInfo.AuthOptions()...) + t, err := session.oauth2Config.DeviceAccessToken(expiryCtx, response, b.provider.AuthOptions()...) if err != nil { slog.Error(err.Error()) return AuthRetry, errorMessage{Message: "could not authenticate user remotely"} } - if err = b.providerInfo.CheckTokenScopes(t); err != nil { + if err = b.provider.CheckTokenScopes(t); err != nil { slog.Warn(err.Error()) } @@ -517,7 +517,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au return AuthDenied, errorMessage{Message: "could not get ID token"} } - authInfo = token.NewAuthCachedInfo(t, rawIDToken, b.providerInfo) + authInfo = token.NewAuthCachedInfo(t, rawIDToken, b.provider) authInfo.UserInfo, err = b.fetchUserInfo(ctx, session, &authInfo) if err != nil { slog.Error(err.Error()) @@ -750,7 +750,7 @@ func (b *Broker) refreshToken(ctx context.Context, oauth2Config oauth2.Config, o rawIDToken = oldToken.RawIDToken } - t := token.NewAuthCachedInfo(oauthToken, rawIDToken, b.providerInfo) + t := token.NewAuthCachedInfo(oauthToken, rawIDToken, b.provider) t.UserInfo = oldToken.UserInfo return t, nil } @@ -765,12 +765,12 @@ func (b *Broker) fetchUserInfo(ctx context.Context, session *session, t *token.A return info.User{}, fmt.Errorf("could not verify token: %v", err) } - userInfo, err = b.providerInfo.GetUserInfo(ctx, t.Token, idToken) + userInfo, err = b.provider.GetUserInfo(ctx, t.Token, idToken) if err != nil { return info.User{}, fmt.Errorf("could not get user info: %w", err) } - if err = b.providerInfo.VerifyUsername(session.username, userInfo.Name); err != nil { + if err = b.provider.VerifyUsername(session.username, userInfo.Name); err != nil { return info.User{}, fmt.Errorf("username verification failed: %w", err) } diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 5574afc4ef..4c7e976783 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -531,8 +531,8 @@ func TestIsAuthenticated(t *testing.T) { cfg := &broker.Config{DataDir: dataDir} cfg.SetIssuerURL(provider.URL) - mockInfoer := &testutils.MockProviderInfoer{GetUserInfoFails: tc.getUserInfoFails} - b := newBrokerForTests(t, *cfg, mockInfoer) + mockProvider := &testutils.MockProvider{GetUserInfoFails: tc.getUserInfoFails} + b := newBrokerForTests(t, *cfg, mockProvider) sessionID, key := newSessionForTests(t, b, tc.username, tc.sessionMode) if tc.token != nil { @@ -704,8 +704,8 @@ func TestConcurrentIsAuthenticated(t *testing.T) { cfg := &broker.Config{DataDir: dataDir} cfg.SetIssuerURL(provider.URL) - mockInfoer := &testutils.MockProviderInfoer{FirstCallDelay: tc.firstCallDelay, SecondCallDelay: tc.secondCallDelay} - b := newBrokerForTests(t, *cfg, mockInfoer) + mockProvider := &testutils.MockProvider{FirstCallDelay: tc.firstCallDelay, SecondCallDelay: tc.secondCallDelay} + b := newBrokerForTests(t, *cfg, mockProvider) firstSession, firstKey := newSessionForTests(t, b, username1, "") firstToken := tokenOptions{username: username1, issuer: provider.URL} @@ -835,7 +835,7 @@ func TestFetchUserInfo(t *testing.T) { brokerCfg.SetHomeBaseDir(homeDirPath) brokerCfg.SetClientID(clientID) - mockInfoer := &testutils.MockProviderInfoer{ + mockProvider := &testutils.MockProvider{ GroupsErr: tc.wantGroupErr, Groups: []info.Group{ {Name: "test-fetch-user-info-remote-group", UGID: "12345"}, @@ -843,10 +843,10 @@ func TestFetchUserInfo(t *testing.T) { }, } if tc.emptyGroups { - mockInfoer.Groups = []info.Group{} + mockProvider.Groups = []info.Group{} } - b, err := broker.New(*brokerCfg, broker.WithCustomProviderInfo(mockInfoer)) + b, err := broker.New(*brokerCfg, broker.WithCustomProvider(mockProvider)) require.NoError(t, err, "Setup: New should not have returned an error") if tc.username == "" { diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index 7bb5d7d998..45ccd33814 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -23,16 +23,16 @@ import ( // newBrokerForTests is a helper function to create a new broker for tests with the specified configuration. // // Note that the issuerURL is required in the configuration. -func newBrokerForTests(t *testing.T, cfg broker.Config, providerInfoer *testutils.MockProviderInfoer) (b *broker.Broker) { +func newBrokerForTests(t *testing.T, cfg broker.Config, provider *testutils.MockProvider) (b *broker.Broker) { t.Helper() require.NotEmpty(t, cfg.IssuerURL(), "Setup: issuerURL must not be empty") - if providerInfoer == nil { - providerInfoer = &testutils.MockProviderInfoer{} + if provider == nil { + provider = &testutils.MockProvider{} } - if providerInfoer.Groups == nil { - providerInfoer.Groups = []info.Group{ + if provider.Groups == nil { + provider.Groups = []info.Group{ {Name: "new-broker-for-tests-remote-group", UGID: "12345"}, {Name: "linux-new-broker-for-tests-local-group", UGID: ""}, } @@ -47,7 +47,7 @@ func newBrokerForTests(t *testing.T, cfg broker.Config, providerInfoer *testutil b, err := broker.New( cfg, - broker.WithCustomProviderInfo(providerInfoer), + broker.WithCustomProvider(provider), ) require.NoError(t, err, "Setup: New should not have returned an error") return b diff --git a/internal/broker/options_test.go b/internal/broker/options_test.go index 9858d4dce9..57da53f5b1 100644 --- a/internal/broker/options_test.go +++ b/internal/broker/options_test.go @@ -2,9 +2,9 @@ package broker import "github.com/ubuntu/authd-oidc-brokers/internal/providers" -// WithCustomProviderInfo returns an option that sets a custom provider infoer for the broker. -func WithCustomProviderInfo(p providers.ProviderInfoer) Option { +// WithCustomProvider returns an option that sets a custom provider for the broker. +func WithCustomProvider(p providers.Provider) Option { return func(o *option) { - o.providerInfo = p + o.provider = p } } diff --git a/internal/providers/default.go b/internal/providers/default.go index 494a50f688..68ce946ee6 100644 --- a/internal/providers/default.go +++ b/internal/providers/default.go @@ -6,7 +6,7 @@ import ( "github.com/ubuntu/authd-oidc-brokers/internal/providers/noprovider" ) -// CurrentProviderInfo returns a generic oidc provider implementation. -func CurrentProviderInfo() ProviderInfoer { +// CurrentProvider returns a generic oidc provider implementation. +func CurrentProvider() Provider { return noprovider.New() } diff --git a/internal/providers/providers.go b/internal/providers/providers.go index ca111e1ba1..6e6db807da 100644 --- a/internal/providers/providers.go +++ b/internal/providers/providers.go @@ -9,8 +9,8 @@ import ( "golang.org/x/oauth2" ) -// ProviderInfoer defines provider-specific methods to be used by the broker. -type ProviderInfoer interface { +// Provider defines provider-specific methods to be used by the broker. +type Provider interface { AdditionalScopes() []string AuthOptions() []oauth2.AuthCodeOption CheckTokenScopes(token *oauth2.Token) error diff --git a/internal/providers/withgoogle.go b/internal/providers/withgoogle.go index caa758db12..8f0cb9dd72 100644 --- a/internal/providers/withgoogle.go +++ b/internal/providers/withgoogle.go @@ -4,7 +4,7 @@ package providers import "github.com/ubuntu/authd-oidc-brokers/internal/providers/google" -// CurrentProviderInfo returns a Google provider implementation. -func CurrentProviderInfo() ProviderInfoer { +// CurrentProvider returns a Google provider implementation. +func CurrentProvider() Provider { return google.New() } diff --git a/internal/providers/withmsentraid.go b/internal/providers/withmsentraid.go index 3d29d7e98e..52d4259603 100644 --- a/internal/providers/withmsentraid.go +++ b/internal/providers/withmsentraid.go @@ -6,7 +6,7 @@ import ( "github.com/ubuntu/authd-oidc-brokers/internal/providers/msentraid" ) -// CurrentProviderInfo returns a Microsoft Entra ID provider implementation. -func CurrentProviderInfo() ProviderInfoer { +// CurrentProvider returns a Microsoft Entra ID provider implementation. +func CurrentProvider() Provider { return msentraid.New() } diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index e85886d40b..0b00134925 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -323,8 +323,8 @@ func ExpiryDeviceAuthHandler() ProviderHandler { } } -// MockProviderInfoer is a mock that implements the ProviderInfoer interface. -type MockProviderInfoer struct { +// MockProvider is a mock that implements the Provider interface. +type MockProvider struct { noprovider.NoProvider Scopes []string Options []oauth2.AuthCodeOption @@ -339,7 +339,7 @@ type MockProviderInfoer struct { } // CheckTokenScopes checks if the token has the required scopes. -func (p *MockProviderInfoer) CheckTokenScopes(token *oauth2.Token) error { +func (p *MockProvider) CheckTokenScopes(token *oauth2.Token) error { scopesStr, ok := token.Extra("scope").(string) if !ok { return fmt.Errorf("failed to cast token scopes to string: %v", token.Extra("scope")) @@ -359,7 +359,7 @@ func (p *MockProviderInfoer) CheckTokenScopes(token *oauth2.Token) error { } // AdditionalScopes returns the additional scopes required by the provider. -func (p *MockProviderInfoer) AdditionalScopes() []string { +func (p *MockProvider) AdditionalScopes() []string { if p.Scopes != nil { return p.Scopes } @@ -367,7 +367,7 @@ func (p *MockProviderInfoer) AdditionalScopes() []string { } // AuthOptions returns the additional options required by the provider. -func (p *MockProviderInfoer) AuthOptions() []oauth2.AuthCodeOption { +func (p *MockProvider) AuthOptions() []oauth2.AuthCodeOption { if p.Options != nil { return p.Options } @@ -375,7 +375,7 @@ func (p *MockProviderInfoer) AuthOptions() []oauth2.AuthCodeOption { } // GetUserInfo is a no-op when no specific provider is in use. -func (p *MockProviderInfoer) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken) (info.User, error) { +func (p *MockProvider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken) (info.User, error) { if p.GetUserInfoFails { return info.User{}, errors.New("error requested in the mock") } @@ -421,7 +421,7 @@ type claims struct { } // userClaims returns the user claims parsed from the ID token. -func (p *MockProviderInfoer) userClaims(idToken *oidc.IDToken) (claims, error) { +func (p *MockProvider) userClaims(idToken *oidc.IDToken) (claims, error) { var userClaims claims if err := idToken.Claims(&userClaims); err != nil { return claims{}, fmt.Errorf("failed to get ID token claims: %v", err) @@ -430,7 +430,7 @@ func (p *MockProviderInfoer) userClaims(idToken *oidc.IDToken) (claims, error) { } // GetGroups returns the groups the user is a member of. -func (p *MockProviderInfoer) getGroups(*oauth2.Token) ([]info.Group, error) { +func (p *MockProvider) getGroups(*oauth2.Token) ([]info.Group, error) { if p.GroupsErr { return nil, errors.New("error requested in the mock") } diff --git a/internal/token/token.go b/internal/token/token.go index f12810a6de..917d0e111b 100644 --- a/internal/token/token.go +++ b/internal/token/token.go @@ -22,7 +22,7 @@ type AuthCachedInfo struct { // NewAuthCachedInfo creates a new AuthCachedInfo. It sets the provided token and rawIDToken and the provider-specific // extra fields which should be stored persistently. -func NewAuthCachedInfo(token *oauth2.Token, rawIDToken string, provider providers.ProviderInfoer) AuthCachedInfo { +func NewAuthCachedInfo(token *oauth2.Token, rawIDToken string, provider providers.Provider) AuthCachedInfo { return AuthCachedInfo{ Token: token, RawIDToken: rawIDToken, From 262234af7681e172ed1e74090f3f815601c58bdd Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 14 Nov 2024 19:02:04 +0100 Subject: [PATCH 0238/1670] refactor: Only store the URL of the mock provider server Avoids the name clash between the Provider interface and the provider server. --- cmd/authd-oidc/daemon/daemon_test.go | 32 ++++++------- internal/broker/broker_test.go | 68 ++++++++++++++-------------- internal/testutils/provider.go | 4 +- 3 files changed, 50 insertions(+), 54 deletions(-) diff --git a/cmd/authd-oidc/daemon/daemon_test.go b/cmd/authd-oidc/daemon/daemon_test.go index bf9c6a241e..87c642f933 100644 --- a/cmd/authd-oidc/daemon/daemon_test.go +++ b/cmd/authd-oidc/daemon/daemon_test.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" "io" - "net/http/httptest" "os" "path/filepath" "strings" @@ -18,10 +17,10 @@ import ( "github.com/ubuntu/authd-oidc-brokers/internal/testutils" ) -var mockProvider *httptest.Server +var providerURL string func TestHelp(t *testing.T) { - a := daemon.NewForTests(t, nil, mockProvider.URL, "--help") + a := daemon.NewForTests(t, nil, providerURL, "--help") getStdout := captureStdout(t) @@ -30,7 +29,7 @@ func TestHelp(t *testing.T) { } func TestCompletion(t *testing.T) { - a := daemon.NewForTests(t, nil, mockProvider.URL, "completion", "bash") + a := daemon.NewForTests(t, nil, providerURL, "completion", "bash") getStdout := captureStdout(t) @@ -39,7 +38,7 @@ func TestCompletion(t *testing.T) { } func TestVersion(t *testing.T) { - a := daemon.NewForTests(t, nil, mockProvider.URL, "version") + a := daemon.NewForTests(t, nil, providerURL, "version") getStdout := captureStdout(t) @@ -56,7 +55,7 @@ func TestVersion(t *testing.T) { } func TestNoUsageError(t *testing.T) { - a := daemon.NewForTests(t, nil, mockProvider.URL, "completion", "bash") + a := daemon.NewForTests(t, nil, providerURL, "completion", "bash") getStdout := captureStdout(t) err := a.Run() @@ -67,7 +66,7 @@ func TestNoUsageError(t *testing.T) { } func TestUsageError(t *testing.T) { - a := daemon.NewForTests(t, nil, mockProvider.URL, "doesnotexist") + a := daemon.NewForTests(t, nil, providerURL, "doesnotexist") err := a.Run() require.Error(t, err, "Run should return an error, stdout: %v") @@ -94,7 +93,7 @@ func TestCanQuitTwice(t *testing.T) { func TestAppCanQuitWithoutExecute(t *testing.T) { t.Skipf("This test is skipped because it is flaky. There is no way to guarantee Quit has been called before run.") - a := daemon.NewForTests(t, nil, mockProvider.URL) + a := daemon.NewForTests(t, nil, providerURL) requireGoroutineStarted(t, a.Quit) err := a.Run() @@ -142,7 +141,7 @@ func TestAppRunFailsOnComponentsCreationAndQuit(t *testing.T) { }, } - a := daemon.NewForTests(t, &config, mockProvider.URL) + a := daemon.NewForTests(t, &config, providerURL) err := a.Run() require.Error(t, err, "Run should return an error") }) @@ -198,7 +197,7 @@ func TestAppCanSigHupWithoutExecute(t *testing.T) { r, w, err := os.Pipe() require.NoError(t, err, "Setup: pipe shouldn't fail") - a := daemon.NewForTests(t, nil, mockProvider.URL) + a := daemon.NewForTests(t, nil, providerURL) orig := os.Stdout os.Stdout = w @@ -215,7 +214,7 @@ func TestAppCanSigHupWithoutExecute(t *testing.T) { } func TestAppGetRootCmd(t *testing.T) { - a := daemon.NewForTests(t, nil, mockProvider.URL) + a := daemon.NewForTests(t, nil, providerURL) require.NotNil(t, a.RootCmd(), "Returns root command") } @@ -247,8 +246,8 @@ func TestConfigHasPrecedenceOverPathsConfig(t *testing.T) { } overrideBrokerConfPath := filepath.Join(tmpDir, "override", "via", "config", "broker.conf") - daemon.GenerateBrokerConfig(t, overrideBrokerConfPath, mockProvider.URL) - a := daemon.NewForTests(t, &config, mockProvider.URL, "--config", overrideBrokerConfPath) + daemon.GenerateBrokerConfig(t, overrideBrokerConfPath, providerURL) + a := daemon.NewForTests(t, &config, providerURL, "--config", overrideBrokerConfPath) wg := sync.WaitGroup{} wg.Add(1) @@ -278,7 +277,7 @@ func TestAutoDetectConfig(t *testing.T) { }, } - configPath := daemon.GenerateTestConfig(t, &config, mockProvider.URL) + configPath := daemon.GenerateTestConfig(t, &config, providerURL) configNextToBinaryPath := filepath.Join(filepath.Dir(os.Args[0]), t.Name()+".yaml") err := os.Rename(configPath, configNextToBinaryPath) require.NoError(t, err, "Could not relocate authd configuration file in the binary directory") @@ -344,7 +343,7 @@ func requireGoroutineStarted(t *testing.T, f func()) { func startDaemon(t *testing.T, conf *daemon.DaemonConfig) (app *daemon.App, done func()) { t.Helper() - a := daemon.NewForTests(t, conf, mockProvider.URL) + a := daemon.NewForTests(t, conf, providerURL) wg := sync.WaitGroup{} wg.Add(1) @@ -403,9 +402,8 @@ func TestMain(m *testing.M) { defer cleanup() // Start provider mock - providerServer, cleanup := testutils.StartMockProvider("", nil) + providerURL, cleanup = testutils.StartMockProvider("", nil) defer cleanup() - mockProvider = providerServer m.Run() } diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 4c7e976783..830afdd9c3 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -3,7 +3,6 @@ package broker_test import ( "encoding/json" "fmt" - "net/http/httptest" "os" "path/filepath" "strings" @@ -20,7 +19,7 @@ import ( "gopkg.in/yaml.v3" ) -var defaultProvider *httptest.Server +var defaultProviderURL string func TestNew(t *testing.T) { t.Parallel() @@ -45,7 +44,7 @@ func TestNew(t *testing.T) { switch tc.issuer { case "": - tc.issuer = defaultProvider.URL + tc.issuer = defaultProviderURL case "-": tc.issuer = "" } @@ -107,10 +106,10 @@ func TestNewSession(t *testing.T) { opts = append(opts, testutils.WithHandler(endpoint, handler)) } - provider, stopServer := testutils.StartMockProvider("", nil, opts...) - t.Cleanup(stopServer) + providerURL, cleanup := testutils.StartMockProvider("", nil, opts...) + t.Cleanup(cleanup) cfg := &broker.Config{} - cfg.SetIssuerURL(provider.URL) + cfg.SetIssuerURL(providerURL) b := newBrokerForTests(t, *cfg, nil) id, _, err := b.NewSession("test-user", "lang", "auth") @@ -207,8 +206,7 @@ func TestGetAuthenticationModes(t *testing.T) { tc.sessionMode = "auth" } - provider := defaultProvider - var stopServer func() + providerURL := defaultProviderURL if tc.providerAddress != "" { address := tc.providerAddress opts := []testutils.OptionProvider{} @@ -224,11 +222,12 @@ func TestGetAuthenticationModes(t *testing.T) { testutils.UnavailableHandler(), )) } - provider, stopServer = testutils.StartMockProvider(address, nil, opts...) - t.Cleanup(stopServer) + var cleanup func() + providerURL, cleanup = testutils.StartMockProvider(address, nil, opts...) + t.Cleanup(cleanup) } cfg := &broker.Config{} - cfg.SetIssuerURL(provider.URL) + cfg.SetIssuerURL(providerURL) b := newBrokerForTests(t, *cfg, nil) sessionID, _ := newSessionForTests(t, b, "", tc.sessionMode) if tc.sessionID == "-" { @@ -329,15 +328,15 @@ func TestSelectAuthenticationMode(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - provider := defaultProvider + providerURL := defaultProviderURL if tc.customHandlers != nil { var opts []testutils.OptionProvider for path, handler := range tc.customHandlers { opts = append(opts, testutils.WithHandler(path, handler)) } - p, cleanup := testutils.StartMockProvider("", nil, opts...) + var cleanup func() + providerURL, cleanup = testutils.StartMockProvider("", nil, opts...) defer cleanup() - provider = p } sessionType := "auth" @@ -346,7 +345,7 @@ func TestSelectAuthenticationMode(t *testing.T) { } cfg := &broker.Config{} - cfg.SetIssuerURL(provider.URL) + cfg.SetIssuerURL(providerURL) b := newBrokerForTests(t, *cfg, nil) sessionID, _ := newSessionForTests(t, b, "", sessionType) @@ -518,25 +517,25 @@ func TestIsAuthenticated(t *testing.T) { err := os.Mkdir(dataDir, 0700) require.NoError(t, err, "Setup: Mkdir should not have returned an error") - provider := defaultProvider + providerURL := defaultProviderURL if tc.customHandlers != nil { var opts []testutils.OptionProvider for path, handler := range tc.customHandlers { opts = append(opts, testutils.WithHandler(path, handler)) } - p, cleanup := testutils.StartMockProvider(tc.address, nil, opts...) + var cleanup func() + providerURL, cleanup = testutils.StartMockProvider(tc.address, nil, opts...) t.Cleanup(cleanup) - provider = p } cfg := &broker.Config{DataDir: dataDir} - cfg.SetIssuerURL(provider.URL) + cfg.SetIssuerURL(providerURL) mockProvider := &testutils.MockProvider{GetUserInfoFails: tc.getUserInfoFails} b := newBrokerForTests(t, *cfg, mockProvider) sessionID, key := newSessionForTests(t, b, tc.username, tc.sessionMode) if tc.token != nil { - tc.token.issuer = provider.URL + tc.token.issuer = providerURL generateAndStoreCachedInfo(t, *tc.token, b.TokenPathForSession(sessionID)) err = password.HashAndStorePassword(correctPassword, b.PasswordFilepathForSession(sessionID)) require.NoError(t, err, "Setup: HashAndStorePassword should not have returned an error") @@ -656,7 +655,7 @@ func TestIsAuthenticated(t *testing.T) { // Ensure that the directory structure is generic to avoid golden file conflicts if _, err := os.Stat(filepath.Dir(b.TokenPathForSession(sessionID))); err == nil { - toReplace := strings.ReplaceAll(strings.TrimPrefix(provider.URL, "http://"), ":", "_") + toReplace := strings.ReplaceAll(strings.TrimPrefix(providerURL, "http://"), ":", "_") tokenDir := filepath.Dir(filepath.Dir(b.TokenPathForSession(sessionID))) newTokenDir := strings.ReplaceAll(tokenDir, toReplace, "provider_url") err := os.Rename(tokenDir, newTokenDir) @@ -694,7 +693,7 @@ func TestConcurrentIsAuthenticated(t *testing.T) { username1 := "user1@example.com" username2 := "user2@example.com" - provider, cleanup := testutils.StartMockProvider("", &testutils.TokenHandlerOptions{ + providerURL, cleanup := testutils.StartMockProvider("", &testutils.TokenHandlerOptions{ IDTokenClaims: []map[string]interface{}{ {"sub": "user1", "name": "user1", "email": username1}, {"sub": "user2", "name": "user2", "email": username2}, @@ -703,18 +702,18 @@ func TestConcurrentIsAuthenticated(t *testing.T) { defer cleanup() cfg := &broker.Config{DataDir: dataDir} - cfg.SetIssuerURL(provider.URL) + cfg.SetIssuerURL(providerURL) mockProvider := &testutils.MockProvider{FirstCallDelay: tc.firstCallDelay, SecondCallDelay: tc.secondCallDelay} b := newBrokerForTests(t, *cfg, mockProvider) firstSession, firstKey := newSessionForTests(t, b, username1, "") - firstToken := tokenOptions{username: username1, issuer: provider.URL} + firstToken := tokenOptions{username: username1, issuer: providerURL} generateAndStoreCachedInfo(t, firstToken, b.TokenPathForSession(firstSession)) err = password.HashAndStorePassword("password", b.PasswordFilepathForSession(firstSession)) require.NoError(t, err, "Setup: HashAndStorePassword should not have returned an error") secondSession, secondKey := newSessionForTests(t, b, username2, "") - secondToken := tokenOptions{username: username2, issuer: provider.URL} + secondToken := tokenOptions{username: username2, issuer: providerURL} generateAndStoreCachedInfo(t, secondToken, b.TokenPathForSession(secondSession)) err = password.HashAndStorePassword("password", b.PasswordFilepathForSession(secondSession)) require.NoError(t, err, "Setup: HashAndStorePassword should not have returned an error") @@ -784,7 +783,7 @@ func TestConcurrentIsAuthenticated(t *testing.T) { // Ensure that the directory structure is generic to avoid golden file conflicts issuerDataDir := filepath.Dir(b.UserDataDirForSession(firstSession)) if _, err := os.Stat(issuerDataDir); err == nil { - toReplace := strings.ReplaceAll(strings.TrimPrefix(provider.URL, "http://"), ":", "_") + toReplace := strings.ReplaceAll(strings.TrimPrefix(providerURL, "http://"), ":", "_") newIssuerDataDir := strings.ReplaceAll(issuerDataDir, toReplace, "provider_url") err := os.Rename(issuerDataDir, newIssuerDataDir) if err != nil { @@ -831,7 +830,7 @@ func TestFetchUserInfo(t *testing.T) { dataDir := t.TempDir() clientID := "test-client-id" brokerCfg := &broker.Config{DataDir: dataDir} - brokerCfg.SetIssuerURL(defaultProvider.URL) + brokerCfg.SetIssuerURL(defaultProviderURL) brokerCfg.SetHomeBaseDir(homeDirPath) brokerCfg.SetClientID(clientID) @@ -852,7 +851,7 @@ func TestFetchUserInfo(t *testing.T) { if tc.username == "" { tc.username = "test-user@email.com" } - tc.token.issuer = defaultProvider.URL + tc.token.issuer = defaultProviderURL sessionID, _, err := b.NewSession(tc.username, "lang", "auth") require.NoError(t, err, "Setup: Failed to create session for the tests") @@ -877,11 +876,11 @@ func TestFetchUserInfo(t *testing.T) { func TestCancelIsAuthenticated(t *testing.T) { t.Parallel() - provider, cleanup := testutils.StartMockProvider("", nil, testutils.WithHandler("/token", testutils.HangingHandler(3*time.Second))) + providerURL, cleanup := testutils.StartMockProvider("", nil, testutils.WithHandler("/token", testutils.HangingHandler(3*time.Second))) t.Cleanup(cleanup) cfg := &broker.Config{} - cfg.SetIssuerURL(provider.URL) + cfg.SetIssuerURL(providerURL) b := newBrokerForTests(t, *cfg, nil) sessionID, _ := newSessionForTests(t, b, "", "") @@ -905,7 +904,7 @@ func TestEndSession(t *testing.T) { t.Parallel() cfg := &broker.Config{} - cfg.SetIssuerURL(defaultProvider.URL) + cfg.SetIssuerURL(defaultProviderURL) b := newBrokerForTests(t, *cfg, nil) sessionID, _ := newSessionForTests(t, b, "", "") @@ -961,7 +960,7 @@ func TestUserPreCheck(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() cfg := &broker.Config{} - cfg.SetIssuerURL(defaultProvider.URL) + cfg.SetIssuerURL(defaultProviderURL) cfg.SetHomeBaseDir(tc.homePrefix) cfg.SetAllowedSSHSuffixes(tc.allowedSuffixes) b := newBrokerForTests(t, *cfg, nil) @@ -979,10 +978,9 @@ func TestUserPreCheck(t *testing.T) { } func TestMain(m *testing.M) { - server, cleanup := testutils.StartMockProvider("", nil) + var cleanup func() + defaultProviderURL, cleanup = testutils.StartMockProvider("", nil) defer cleanup() - defaultProvider = server - os.Exit(m.Run()) } diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index 0b00134925..085af99d3e 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -82,7 +82,7 @@ func WithHandler(path string, handler func(http.ResponseWriter, *http.Request)) } // StartMockProvider starts a new HTTP server to be used as an OpenID Connect provider for tests. -func StartMockProvider(address string, tokenHandlerOpts *TokenHandlerOptions, args ...OptionProvider) (*httptest.Server, func()) { +func StartMockProvider(address string, tokenHandlerOpts *TokenHandlerOptions, args ...OptionProvider) (string, func()) { servMux := http.NewServeMux() server := httptest.NewUnstartedServer(servMux) @@ -114,7 +114,7 @@ func StartMockProvider(address string, tokenHandlerOpts *TokenHandlerOptions, ar servMux.HandleFunc(path, handler) } - return server, func() { + return server.URL, func() { server.Close() } } From 776881611e10a8a70eeb02924cec59ccd6ddbddb Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 14 Nov 2024 19:03:35 +0100 Subject: [PATCH 0239/1670] refactor: Rename StartMockProvider to StartMockProviderServer To avoid the confusion between the provider interface and the provider server. --- cmd/authd-oidc/daemon/daemon_test.go | 2 +- internal/broker/broker_test.go | 22 +++++++++++----------- internal/testutils/provider.go | 16 ++++++++-------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/cmd/authd-oidc/daemon/daemon_test.go b/cmd/authd-oidc/daemon/daemon_test.go index 87c642f933..4d6dab1632 100644 --- a/cmd/authd-oidc/daemon/daemon_test.go +++ b/cmd/authd-oidc/daemon/daemon_test.go @@ -402,7 +402,7 @@ func TestMain(m *testing.M) { defer cleanup() // Start provider mock - providerURL, cleanup = testutils.StartMockProvider("", nil) + providerURL, cleanup = testutils.StartMockProviderServer("", nil) defer cleanup() m.Run() diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 830afdd9c3..db1f0f33f7 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -101,12 +101,12 @@ func TestNewSession(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - var opts []testutils.OptionProvider + var opts []testutils.ProviderServerOption for endpoint, handler := range tc.customHandlers { opts = append(opts, testutils.WithHandler(endpoint, handler)) } - providerURL, cleanup := testutils.StartMockProvider("", nil, opts...) + providerURL, cleanup := testutils.StartMockProviderServer("", nil, opts...) t.Cleanup(cleanup) cfg := &broker.Config{} cfg.SetIssuerURL(providerURL) @@ -209,7 +209,7 @@ func TestGetAuthenticationModes(t *testing.T) { providerURL := defaultProviderURL if tc.providerAddress != "" { address := tc.providerAddress - opts := []testutils.OptionProvider{} + opts := []testutils.ProviderServerOption{} if tc.deviceAuthUnsupported { opts = append(opts, testutils.WithHandler( "/.well-known/openid-configuration", @@ -223,7 +223,7 @@ func TestGetAuthenticationModes(t *testing.T) { )) } var cleanup func() - providerURL, cleanup = testutils.StartMockProvider(address, nil, opts...) + providerURL, cleanup = testutils.StartMockProviderServer(address, nil, opts...) t.Cleanup(cleanup) } cfg := &broker.Config{} @@ -330,12 +330,12 @@ func TestSelectAuthenticationMode(t *testing.T) { providerURL := defaultProviderURL if tc.customHandlers != nil { - var opts []testutils.OptionProvider + var opts []testutils.ProviderServerOption for path, handler := range tc.customHandlers { opts = append(opts, testutils.WithHandler(path, handler)) } var cleanup func() - providerURL, cleanup = testutils.StartMockProvider("", nil, opts...) + providerURL, cleanup = testutils.StartMockProviderServer("", nil, opts...) defer cleanup() } @@ -519,12 +519,12 @@ func TestIsAuthenticated(t *testing.T) { providerURL := defaultProviderURL if tc.customHandlers != nil { - var opts []testutils.OptionProvider + var opts []testutils.ProviderServerOption for path, handler := range tc.customHandlers { opts = append(opts, testutils.WithHandler(path, handler)) } var cleanup func() - providerURL, cleanup = testutils.StartMockProvider(tc.address, nil, opts...) + providerURL, cleanup = testutils.StartMockProviderServer(tc.address, nil, opts...) t.Cleanup(cleanup) } @@ -693,7 +693,7 @@ func TestConcurrentIsAuthenticated(t *testing.T) { username1 := "user1@example.com" username2 := "user2@example.com" - providerURL, cleanup := testutils.StartMockProvider("", &testutils.TokenHandlerOptions{ + providerURL, cleanup := testutils.StartMockProviderServer("", &testutils.TokenHandlerOptions{ IDTokenClaims: []map[string]interface{}{ {"sub": "user1", "name": "user1", "email": username1}, {"sub": "user2", "name": "user2", "email": username2}, @@ -876,7 +876,7 @@ func TestFetchUserInfo(t *testing.T) { func TestCancelIsAuthenticated(t *testing.T) { t.Parallel() - providerURL, cleanup := testutils.StartMockProvider("", nil, testutils.WithHandler("/token", testutils.HangingHandler(3*time.Second))) + providerURL, cleanup := testutils.StartMockProviderServer("", nil, testutils.WithHandler("/token", testutils.HangingHandler(3*time.Second))) t.Cleanup(cleanup) cfg := &broker.Config{} @@ -979,7 +979,7 @@ func TestUserPreCheck(t *testing.T) { func TestMain(m *testing.M) { var cleanup func() - defaultProviderURL, cleanup = testutils.StartMockProvider("", nil) + defaultProviderURL, cleanup = testutils.StartMockProviderServer("", nil) defer cleanup() os.Exit(m.Run()) diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index 085af99d3e..92887e710a 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -67,22 +67,22 @@ func init() { // ProviderHandler is a function that handles a request to the mock provider. type ProviderHandler func(http.ResponseWriter, *http.Request) -type optionProvider struct { +type providerServerOption struct { handlers map[string]ProviderHandler } -// OptionProvider is a function that allows to override default options of the mock provider. -type OptionProvider func(*optionProvider) +// ProviderServerOption is a function that allows to override default options of the mock provider. +type ProviderServerOption func(*providerServerOption) // WithHandler specifies a handler to the requested path in the mock provider. -func WithHandler(path string, handler func(http.ResponseWriter, *http.Request)) OptionProvider { - return func(o *optionProvider) { +func WithHandler(path string, handler func(http.ResponseWriter, *http.Request)) ProviderServerOption { + return func(o *providerServerOption) { o.handlers[path] = handler } } -// StartMockProvider starts a new HTTP server to be used as an OpenID Connect provider for tests. -func StartMockProvider(address string, tokenHandlerOpts *TokenHandlerOptions, args ...OptionProvider) (string, func()) { +// StartMockProviderServer starts a new HTTP server to be used as an OpenID Connect provider for tests. +func StartMockProviderServer(address string, tokenHandlerOpts *TokenHandlerOptions, args ...ProviderServerOption) (string, func()) { servMux := http.NewServeMux() server := httptest.NewUnstartedServer(servMux) @@ -95,7 +95,7 @@ func StartMockProvider(address string, tokenHandlerOpts *TokenHandlerOptions, ar } server.Start() - opts := optionProvider{ + opts := providerServerOption{ handlers: map[string]ProviderHandler{ "/.well-known/openid-configuration": DefaultOpenIDHandler(server.URL), "/device_auth": DefaultDeviceAuthHandler(), From e20e3f0be469c086d6633ea08f582ffab802da60 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 14 Nov 2024 19:07:27 +0100 Subject: [PATCH 0240/1670] refactor: Rename oidcProvider to oidcServer To avoid confusion with the Provider interface --- internal/broker/broker.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 42e1ac1574..4c4d188432 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -76,7 +76,7 @@ type session struct { authModes []string attemptsPerMode map[string]int - oidcProvider *oidc.Provider + oidcServer *oidc.Provider oauth2Config oauth2.Config authInfo map[string]any isOffline bool @@ -186,17 +186,17 @@ func (b *Broker) NewSession(username, lang, mode string) (sessionID, encryptionK s.oldEncryptedTokenPath = filepath.Join(b.cfg.OldEncryptedTokensDir, issuer, username+".cache") // Construct an OIDC provider via OIDC discovery. - s.oidcProvider, err = b.connectToProvider(context.Background()) + s.oidcServer, err = b.connectToOIDCServer(context.Background()) if err != nil { slog.Debug(fmt.Sprintf("Could not connect to the provider: %v. Starting session in offline mode.", err)) s.isOffline = true } - if s.oidcProvider != nil { + if s.oidcServer != nil { s.oauth2Config = oauth2.Config{ ClientID: b.oidcCfg.ClientID, ClientSecret: b.cfg.clientSecret, - Endpoint: s.oidcProvider.Endpoint(), + Endpoint: s.oidcServer.Endpoint(), Scopes: append(consts.DefaultScopes, b.provider.AdditionalScopes()...), } } @@ -208,7 +208,7 @@ func (b *Broker) NewSession(username, lang, mode string) (sessionID, encryptionK return sessionID, base64.StdEncoding.EncodeToString(pubASN1), nil } -func (b *Broker) connectToProvider(ctx context.Context) (*oidc.Provider, error) { +func (b *Broker) connectToOIDCServer(ctx context.Context) (*oidc.Provider, error) { ctx, cancel := context.WithTimeout(ctx, maxRequestDuration) defer cancel() @@ -241,7 +241,7 @@ func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []m } endpoints := make(map[string]struct{}) - if session.oidcProvider != nil && session.oidcProvider.Endpoint().DeviceAuthURL != "" { + if session.oidcServer != nil && session.oidcServer.Endpoint().DeviceAuthURL != "" { authMode := authmodes.DeviceQr if _, ok := supportedAuthModes[authMode]; ok { endpoints[authMode] = struct{}{} @@ -760,7 +760,7 @@ func (b *Broker) fetchUserInfo(ctx context.Context, session *session, t *token.A return info.User{}, errors.New("session is in offline mode") } - idToken, err := session.oidcProvider.Verifier(&b.oidcCfg).Verify(ctx, t.RawIDToken) + idToken, err := session.oidcServer.Verifier(&b.oidcCfg).Verify(ctx, t.RawIDToken) if err != nil { return info.User{}, fmt.Errorf("could not verify token: %v", err) } From 123d450b4432a115b14924e47d5f7f6e7f0332c1 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 14 Nov 2024 19:16:02 +0100 Subject: [PATCH 0241/1670] refactor: Rename ProviderHandlers to EndpointHandlers These handlers only handle one endpoint of an OIDC provider. --- internal/broker/broker_test.go | 40 +++++++++++++++++----------------- internal/testutils/provider.go | 28 ++++++++++++------------ 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index db1f0f33f7..4fa5afee9f 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -79,19 +79,19 @@ func TestNewSession(t *testing.T) { t.Parallel() tests := map[string]struct { - customHandlers map[string]testutils.ProviderHandler + customHandlers map[string]testutils.EndpointHandler wantOffline bool }{ "Successfully_create_new_session": {}, "Creates_new_session_in_offline_mode_if_provider_is_not_available": { - customHandlers: map[string]testutils.ProviderHandler{ + customHandlers: map[string]testutils.EndpointHandler{ "/.well-known/openid-configuration": testutils.UnavailableHandler(), }, wantOffline: true, }, "Creates_new_session_in_offline_mode_if_provider_connection_times_out": { - customHandlers: map[string]testutils.ProviderHandler{ + customHandlers: map[string]testutils.EndpointHandler{ "/.well-known/openid-configuration": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, wantOffline: true, @@ -284,7 +284,7 @@ func TestSelectAuthenticationMode(t *testing.T) { tokenExists bool secondAuthStep bool passwdSession bool - customHandlers map[string]testutils.ProviderHandler + customHandlers map[string]testutils.EndpointHandler supportedLayouts []map[string]string wantErr bool @@ -298,27 +298,27 @@ func TestSelectAuthenticationMode(t *testing.T) { "Error_when_selecting_invalid_mode": {modeName: "invalid", wantErr: true}, "Error_when_selecting_device_auth_qr_but_provider_is_unavailable": {modeName: authmodes.DeviceQr, wantErr: true, - customHandlers: map[string]testutils.ProviderHandler{ + customHandlers: map[string]testutils.EndpointHandler{ "/device_auth": testutils.UnavailableHandler(), }, }, "Error_when_selecting_device_auth_but_provider_is_unavailable": { supportedLayouts: supportedLayoutsWithoutQrCode, modeName: authmodes.Device, - customHandlers: map[string]testutils.ProviderHandler{ + customHandlers: map[string]testutils.EndpointHandler{ "/device_auth": testutils.UnavailableHandler(), }, wantErr: true, }, "Error_when_selecting_device_auth_qr_but_request_times_out": {modeName: authmodes.DeviceQr, wantErr: true, - customHandlers: map[string]testutils.ProviderHandler{ + customHandlers: map[string]testutils.EndpointHandler{ "/device_auth": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, }, "Error_when_selecting_device_auth_but_request_times_out": { supportedLayouts: supportedLayoutsWithoutQrCode, modeName: authmodes.Device, - customHandlers: map[string]testutils.ProviderHandler{ + customHandlers: map[string]testutils.EndpointHandler{ "/device_auth": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, wantErr: true, @@ -399,7 +399,7 @@ func TestIsAuthenticated(t *testing.T) { badFirstKey bool getUserInfoFails bool - customHandlers map[string]testutils.ProviderHandler + customHandlers map[string]testutils.EndpointHandler address string wantSecondCall bool @@ -418,21 +418,21 @@ func TestIsAuthenticated(t *testing.T) { "Authenticating_with_password_still_allowed_if_server_is_unreachable": { firstMode: authmodes.Password, token: &tokenOptions{}, - customHandlers: map[string]testutils.ProviderHandler{ + customHandlers: map[string]testutils.EndpointHandler{ "/.well-known/openid-configuration": testutils.UnavailableHandler(), }, }, "Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable": { firstMode: authmodes.Password, token: &tokenOptions{expired: true}, - customHandlers: map[string]testutils.ProviderHandler{ + customHandlers: map[string]testutils.EndpointHandler{ "/.well-known/openid-configuration": testutils.UnavailableHandler(), }, }, "Authenticating_still_allowed_if_token_is_missing_scopes": { firstChallenge: "-", wantSecondCall: true, - customHandlers: map[string]testutils.ProviderHandler{ + customHandlers: map[string]testutils.EndpointHandler{ "/token": testutils.TokenHandler("http://127.0.0.1:31313", nil), }, address: "127.0.0.1:31313", @@ -448,14 +448,14 @@ func TestIsAuthenticated(t *testing.T) { "Error_when_mode_is_password_but_server_returns_error": { firstMode: authmodes.Password, token: &tokenOptions{expired: true}, - customHandlers: map[string]testutils.ProviderHandler{ + customHandlers: map[string]testutils.EndpointHandler{ "/token": testutils.BadRequestHandler(), }, }, "Error_when_mode_is_password_and_token_is_invalid": {firstMode: authmodes.Password, token: &tokenOptions{invalid: true}}, "Error_when_token_is_expired_and_refreshing_token_fails": {firstMode: authmodes.Password, token: &tokenOptions{expired: true, noRefreshToken: true}}, "Error_when_mode_is_password_and_token_refresh_times_out": {firstMode: authmodes.Password, token: &tokenOptions{expired: true}, - customHandlers: map[string]testutils.ProviderHandler{ + customHandlers: map[string]testutils.EndpointHandler{ "/token": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, }, @@ -463,17 +463,17 @@ func TestIsAuthenticated(t *testing.T) { "Error_when_mode_is_qrcode_and_response_is_invalid": {firstAuthInfo: map[string]any{"response": "not a valid response"}}, "Error_when_mode_is_qrcode_and_link_expires": { - customHandlers: map[string]testutils.ProviderHandler{ + customHandlers: map[string]testutils.EndpointHandler{ "/device_auth": testutils.ExpiryDeviceAuthHandler(), }, }, "Error_when_mode_is_qrcode_and_can_not_get_token": { - customHandlers: map[string]testutils.ProviderHandler{ + customHandlers: map[string]testutils.EndpointHandler{ "/token": testutils.UnavailableHandler(), }, }, "Error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout": { - customHandlers: map[string]testutils.ProviderHandler{ + customHandlers: map[string]testutils.EndpointHandler{ "/token": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, }, @@ -482,19 +482,19 @@ func TestIsAuthenticated(t *testing.T) { firstAuthInfo: map[string]any{"response": "not a valid response"}, }, "Error_when_mode_is_link_code_and_link_expires": { - customHandlers: map[string]testutils.ProviderHandler{ + customHandlers: map[string]testutils.EndpointHandler{ "/device_auth": testutils.ExpiryDeviceAuthHandler(), }, }, "Error_when_mode_is_link_code_and_can_not_get_token": { firstMode: authmodes.Device, - customHandlers: map[string]testutils.ProviderHandler{ + customHandlers: map[string]testutils.EndpointHandler{ "/token": testutils.UnavailableHandler(), }, }, "Error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout": { firstMode: authmodes.Device, - customHandlers: map[string]testutils.ProviderHandler{ + customHandlers: map[string]testutils.EndpointHandler{ "/token": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, }, diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index 92887e710a..2177a4e4fd 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -64,11 +64,11 @@ func init() { mockCertificate = cert } -// ProviderHandler is a function that handles a request to the mock provider. -type ProviderHandler func(http.ResponseWriter, *http.Request) +// EndpointHandler is a function that handles a request to an OIDC provider endpoint. +type EndpointHandler func(http.ResponseWriter, *http.Request) type providerServerOption struct { - handlers map[string]ProviderHandler + handlers map[string]EndpointHandler } // ProviderServerOption is a function that allows to override default options of the mock provider. @@ -96,7 +96,7 @@ func StartMockProviderServer(address string, tokenHandlerOpts *TokenHandlerOptio server.Start() opts := providerServerOption{ - handlers: map[string]ProviderHandler{ + handlers: map[string]EndpointHandler{ "/.well-known/openid-configuration": DefaultOpenIDHandler(server.URL), "/device_auth": DefaultDeviceAuthHandler(), "/token": TokenHandler(server.URL, tokenHandlerOpts), @@ -120,7 +120,7 @@ func StartMockProviderServer(address string, tokenHandlerOpts *TokenHandlerOptio } // DefaultOpenIDHandler returns a handler that returns a default OpenID Connect configuration. -func DefaultOpenIDHandler(serverURL string) ProviderHandler { +func DefaultOpenIDHandler(serverURL string) EndpointHandler { return func(w http.ResponseWriter, _ *http.Request) { wellKnown := fmt.Sprintf(`{ "issuer": "%[1]s", @@ -140,7 +140,7 @@ func DefaultOpenIDHandler(serverURL string) ProviderHandler { } // OpenIDHandlerWithNoDeviceEndpoint returns a handler that returns an OpenID Connect configuration without device endpoint. -func OpenIDHandlerWithNoDeviceEndpoint(serverURL string) ProviderHandler { +func OpenIDHandlerWithNoDeviceEndpoint(serverURL string) EndpointHandler { return func(w http.ResponseWriter, _ *http.Request) { wellKnown := fmt.Sprintf(`{ "issuer": "%[1]s", @@ -159,7 +159,7 @@ func OpenIDHandlerWithNoDeviceEndpoint(serverURL string) ProviderHandler { } // DefaultDeviceAuthHandler returns a handler that returns a default device auth response. -func DefaultDeviceAuthHandler() ProviderHandler { +func DefaultDeviceAuthHandler() EndpointHandler { return func(w http.ResponseWriter, _ *http.Request) { response := `{ "device_code": "device_code", @@ -188,7 +188,7 @@ type TokenHandlerOptions struct { var idTokenClaimsMutex sync.Mutex // TokenHandler returns a handler that returns a default token response. -func TokenHandler(serverURL string, opts *TokenHandlerOptions) ProviderHandler { +func TokenHandler(serverURL string, opts *TokenHandlerOptions) EndpointHandler { if opts == nil { opts = &TokenHandlerOptions{} } @@ -247,7 +247,7 @@ func TokenHandler(serverURL string, opts *TokenHandlerOptions) ProviderHandler { // DefaultJWKHandler returns a handler that provides the signing keys from the broker. // // Meant to be used an the endpoint for /keys. -func DefaultJWKHandler() ProviderHandler { +func DefaultJWKHandler() EndpointHandler { return func(w http.ResponseWriter, r *http.Request) { jwk := jose.JSONWebKey{ Key: &MockKey.PublicKey, @@ -271,21 +271,21 @@ func DefaultJWKHandler() ProviderHandler { } // UnavailableHandler returns a handler that returns a 503 Service Unavailable response. -func UnavailableHandler() ProviderHandler { +func UnavailableHandler() EndpointHandler { return func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusServiceUnavailable) } } // BadRequestHandler returns a handler that returns a 400 Bad Request response. -func BadRequestHandler() ProviderHandler { +func BadRequestHandler() EndpointHandler { return func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusBadRequest) } } // CustomResponseHandler returns a handler that returns a custom token response. -func CustomResponseHandler(response string) ProviderHandler { +func CustomResponseHandler(response string) EndpointHandler { return func(w http.ResponseWriter, _ *http.Request) { w.Header().Add("Content-Type", "application/json") _, err := w.Write([]byte(response)) @@ -296,7 +296,7 @@ func CustomResponseHandler(response string) ProviderHandler { } // HangingHandler returns a handler that hangs the request until the duration has elapsed. -func HangingHandler(d time.Duration) ProviderHandler { +func HangingHandler(d time.Duration) EndpointHandler { return func(w http.ResponseWriter, r *http.Request) { time.Sleep(d) @@ -306,7 +306,7 @@ func HangingHandler(d time.Duration) ProviderHandler { } // ExpiryDeviceAuthHandler returns a handler that returns a device auth response with a short expiry time. -func ExpiryDeviceAuthHandler() ProviderHandler { +func ExpiryDeviceAuthHandler() EndpointHandler { return func(w http.ResponseWriter, _ *http.Request) { response := `{ "device_code": "device_code", From dfd7479888203c6679b51f8be1d46b34e5c2ebbc Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 14 Nov 2024 19:18:03 +0100 Subject: [PATCH 0242/1670] Improve comments --- internal/testutils/provider.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index 2177a4e4fd..9e88dd3f62 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -74,14 +74,14 @@ type providerServerOption struct { // ProviderServerOption is a function that allows to override default options of the mock provider. type ProviderServerOption func(*providerServerOption) -// WithHandler specifies a handler to the requested path in the mock provider. +// WithHandler returns a ProviderServerOption that adds a handler for a provider endpoint specified by path. func WithHandler(path string, handler func(http.ResponseWriter, *http.Request)) ProviderServerOption { return func(o *providerServerOption) { o.handlers[path] = handler } } -// StartMockProviderServer starts a new HTTP server to be used as an OpenID Connect provider for tests. +// StartMockProviderServer starts a new HTTP server to be used as an OIDC provider for tests. func StartMockProviderServer(address string, tokenHandlerOpts *TokenHandlerOptions, args ...ProviderServerOption) (string, func()) { servMux := http.NewServeMux() server := httptest.NewUnstartedServer(servMux) @@ -119,7 +119,7 @@ func StartMockProviderServer(address string, tokenHandlerOpts *TokenHandlerOptio } } -// DefaultOpenIDHandler returns a handler that returns a default OpenID Connect configuration. +// DefaultOpenIDHandler returns a handler that returns a default OIDC configuration. func DefaultOpenIDHandler(serverURL string) EndpointHandler { return func(w http.ResponseWriter, _ *http.Request) { wellKnown := fmt.Sprintf(`{ @@ -139,7 +139,7 @@ func DefaultOpenIDHandler(serverURL string) EndpointHandler { } } -// OpenIDHandlerWithNoDeviceEndpoint returns a handler that returns an OpenID Connect configuration without device endpoint. +// OpenIDHandlerWithNoDeviceEndpoint returns a handler that returns an OIDC configuration without device endpoint. func OpenIDHandlerWithNoDeviceEndpoint(serverURL string) EndpointHandler { return func(w http.ResponseWriter, _ *http.Request) { wellKnown := fmt.Sprintf(`{ From 3e9907c3b33953dba750093eb1f5ed78dcda1b01 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 14 Nov 2024 13:57:06 +0100 Subject: [PATCH 0243/1670] TestIsAuthenticated: Support setting auth mode in second call --- internal/broker/broker_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 4fa5afee9f..1c9bea369b 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -403,6 +403,7 @@ func TestIsAuthenticated(t *testing.T) { address string wantSecondCall bool + secondMode string secondChallenge string token *tokenOptions @@ -614,11 +615,15 @@ func TestIsAuthenticated(t *testing.T) { secondAuthData = "invalid json" } + if tc.secondMode == "" { + tc.secondMode = authmodes.NewPassword + } + secondCallDone := make(chan struct{}) go func() { defer close(secondCallDone) - updateAuthModes(t, b, sessionID, authmodes.NewPassword) + updateAuthModes(t, b, sessionID, tc.secondMode) access, data, err := b.IsAuthenticated(sessionID, secondAuthData) require.True(t, json.Valid([]byte(data)), "IsAuthenticated returned data must be a valid JSON") From b82abc0d38368384366474e24539b61b5bc9606e Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 14 Nov 2024 14:02:53 +0100 Subject: [PATCH 0244/1670] broker tests: Support returning different groups in subsequent calls --- internal/broker/broker_test.go | 6 +++--- internal/broker/helper_test.go | 4 ++-- internal/testutils/provider.go | 19 +++++++++++++------ 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 1c9bea369b..42112bbaed 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -841,13 +841,13 @@ func TestFetchUserInfo(t *testing.T) { mockProvider := &testutils.MockProvider{ GroupsErr: tc.wantGroupErr, - Groups: []info.Group{ + Groups: [][]info.Group{{ {Name: "test-fetch-user-info-remote-group", UGID: "12345"}, {Name: "linux-test-fetch-user-info-local-group", UGID: ""}, - }, + }}, } if tc.emptyGroups { - mockProvider.Groups = []info.Group{} + mockProvider.Groups = [][]info.Group{} } b, err := broker.New(*brokerCfg, broker.WithCustomProvider(mockProvider)) diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index 45ccd33814..9cd80f995f 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -32,10 +32,10 @@ func newBrokerForTests(t *testing.T, cfg broker.Config, provider *testutils.Mock provider = &testutils.MockProvider{} } if provider.Groups == nil { - provider.Groups = []info.Group{ + provider.Groups = [][]info.Group{{ {Name: "new-broker-for-tests-remote-group", UGID: "12345"}, {Name: "linux-new-broker-for-tests-local-group", UGID: ""}, - } + }} } if cfg.DataDir == "" { diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index 9e88dd3f62..65c0eddae8 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -326,9 +326,12 @@ func ExpiryDeviceAuthHandler() EndpointHandler { // MockProvider is a mock that implements the Provider interface. type MockProvider struct { noprovider.NoProvider - Scopes []string - Options []oauth2.AuthCodeOption - Groups []info.Group + Scopes []string + Options []oauth2.AuthCodeOption + // A list of groups to be returned by the GetUserInfo method. Each time the + // method is called, the groups from the first element of the list will be + // returned, and then that element will be removed from the list. + Groups [][]info.Group GroupsErr bool FirstCallDelay int SecondCallDelay int @@ -434,8 +437,12 @@ func (p *MockProvider) getGroups(*oauth2.Token) ([]info.Group, error) { if p.GroupsErr { return nil, errors.New("error requested in the mock") } - if p.Groups != nil { - return p.Groups, nil + + if len(p.Groups) == 0 { + return nil, nil } - return nil, nil + + groups := p.Groups[0] + p.Groups = p.Groups[1:] + return groups, nil } From 9df0fb8dafb09e9e2dba8a0eb8733379d53b4f3c Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 14 Nov 2024 14:21:51 +0100 Subject: [PATCH 0245/1670] broker tests: Support returning different groups in separate calls In preparation for testing the refreshing of groups. --- internal/broker/broker_test.go | 28 +++++++++++++++++++--------- internal/broker/helper_test.go | 12 +++++++----- internal/testutils/provider.go | 34 +++++++++------------------------- 3 files changed, 35 insertions(+), 39 deletions(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 42112bbaed..e77aac5b40 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -2,6 +2,7 @@ package broker_test import ( "encoding/json" + "errors" "fmt" "os" "path/filepath" @@ -839,17 +840,26 @@ func TestFetchUserInfo(t *testing.T) { brokerCfg.SetHomeBaseDir(homeDirPath) brokerCfg.SetClientID(clientID) - mockProvider := &testutils.MockProvider{ - GroupsErr: tc.wantGroupErr, - Groups: [][]info.Group{{ - {Name: "test-fetch-user-info-remote-group", UGID: "12345"}, - {Name: "linux-test-fetch-user-info-local-group", UGID: ""}, - }}, - } - if tc.emptyGroups { - mockProvider.Groups = [][]info.Group{} + var getGroupsFunc func() ([]info.Group, error) + if tc.wantGroupErr { + getGroupsFunc = func() ([]info.Group, error) { + return nil, errors.New("error getting groups") + } + } else if tc.emptyGroups { + getGroupsFunc = func() ([]info.Group, error) { + return []info.Group{}, nil + } + } else { + getGroupsFunc = func() ([]info.Group, error) { + return []info.Group{ + {Name: "test-fetch-user-info-remote-group", UGID: "12345"}, + {Name: "linux-test-fetch-user-info-local-group", UGID: ""}, + }, nil + } } + mockProvider := &testutils.MockProvider{GetGroupsFunc: getGroupsFunc} + b, err := broker.New(*brokerCfg, broker.WithCustomProvider(mockProvider)) require.NoError(t, err, "Setup: New should not have returned an error") diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index 9cd80f995f..1ac4b8898a 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -31,11 +31,13 @@ func newBrokerForTests(t *testing.T, cfg broker.Config, provider *testutils.Mock if provider == nil { provider = &testutils.MockProvider{} } - if provider.Groups == nil { - provider.Groups = [][]info.Group{{ - {Name: "new-broker-for-tests-remote-group", UGID: "12345"}, - {Name: "linux-new-broker-for-tests-local-group", UGID: ""}, - }} + if provider.GetGroupsFunc == nil { + provider.GetGroupsFunc = func() ([]info.Group, error) { + return []info.Group{ + {Name: "new-broker-for-tests-remote-group", UGID: "12345"}, + {Name: "linux-new-broker-for-tests-local-group", UGID: ""}, + }, nil + } } if cfg.DataDir == "" { diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index 65c0eddae8..f53be11064 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -326,13 +326,9 @@ func ExpiryDeviceAuthHandler() EndpointHandler { // MockProvider is a mock that implements the Provider interface. type MockProvider struct { noprovider.NoProvider - Scopes []string - Options []oauth2.AuthCodeOption - // A list of groups to be returned by the GetUserInfo method. Each time the - // method is called, the groups from the first element of the list will be - // returned, and then that element will be removed from the list. - Groups [][]info.Group - GroupsErr bool + Scopes []string + Options []oauth2.AuthCodeOption + GetGroupsFunc func() ([]info.Group, error) FirstCallDelay int SecondCallDelay int GetUserInfoFails bool @@ -388,9 +384,12 @@ func (p *MockProvider) GetUserInfo(ctx context.Context, accessToken *oauth2.Toke return info.User{}, err } - userGroups, err := p.getGroups(accessToken) - if err != nil { - return info.User{}, err + var userGroups []info.Group + if p.GetGroupsFunc != nil { + userGroups, err = p.GetGroupsFunc() + if err != nil { + return info.User{}, err + } } p.numCallsLock.Lock() @@ -431,18 +430,3 @@ func (p *MockProvider) userClaims(idToken *oidc.IDToken) (claims, error) { } return userClaims, nil } - -// GetGroups returns the groups the user is a member of. -func (p *MockProvider) getGroups(*oauth2.Token) ([]info.Group, error) { - if p.GroupsErr { - return nil, errors.New("error requested in the mock") - } - - if len(p.Groups) == 0 { - return nil, nil - } - - groups := p.Groups[0] - p.Groups = p.Groups[1:] - return groups, nil -} From 6007797e3a2d8d10186acd4f2cda4de60ec18039 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 14 Nov 2024 15:30:38 +0100 Subject: [PATCH 0246/1670] refactor: Use default groups in MockProvider This way we don't need to specify any groups when instantiating the MockProvider in the most common case that we just want to return the same groups in each call. --- internal/broker/broker_test.go | 7 ------- internal/broker/helper_test.go | 8 -------- .../first_auth | 2 +- .../second_auth | 2 +- .../first_auth | 2 +- .../second_auth | 2 +- .../first_auth | 2 +- .../second_auth | 2 +- ...ly_fetch_user_info_with_default_home_when_not_provided | 4 ++-- .../golden/Successfully_fetch_user_info_with_groups | 4 ++-- .../second_call | 2 +- .../first_call | 2 +- .../second_call | 2 +- .../second_call | 2 +- .../first_call | 2 +- internal/testutils/provider.go | 5 ++++- 16 files changed, 19 insertions(+), 31 deletions(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index e77aac5b40..b724c0a0be 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -849,13 +849,6 @@ func TestFetchUserInfo(t *testing.T) { getGroupsFunc = func() ([]info.Group, error) { return []info.Group{}, nil } - } else { - getGroupsFunc = func() ([]info.Group, error) { - return []info.Group{ - {Name: "test-fetch-user-info-remote-group", UGID: "12345"}, - {Name: "linux-test-fetch-user-info-local-group", UGID: ""}, - }, nil - } } mockProvider := &testutils.MockProvider{GetGroupsFunc: getGroupsFunc} diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index 1ac4b8898a..013dc55e15 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -31,14 +31,6 @@ func newBrokerForTests(t *testing.T, cfg broker.Config, provider *testutils.Mock if provider == nil { provider = &testutils.MockProvider{} } - if provider.GetGroupsFunc == nil { - provider.GetGroupsFunc = func() ([]info.Group, error) { - return []info.Group{ - {Name: "new-broker-for-tests-remote-group", UGID: "12345"}, - {Name: "linux-new-broker-for-tests-local-group", UGID: ""}, - }, nil - } - } if cfg.DataDir == "" { cfg.DataDir = t.TempDir() diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/first_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/first_auth index 073d10ad49..89c182d5f3 100644 --- a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/first_auth +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/first_auth @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"user1@example.com","uuid":"user1","dir":"/home/user1@example.com","shell":"/usr/bin/bash","gecos":"user1@example.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"user1@example.com","uuid":"user1","dir":"/home/user1@example.com","shell":"/usr/bin/bash","gecos":"user1@example.com","groups":[{"name":"remote-test-group","ugid":"12345"},{"name":"local-test-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/second_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/second_auth index c73c8a4d4c..f0643dd9fe 100644 --- a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/second_auth +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/second_auth @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"user2@example.com","uuid":"user2","dir":"/home/user2@example.com","shell":"/usr/bin/bash","gecos":"user2@example.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"user2@example.com","uuid":"user2","dir":"/home/user2@example.com","shell":"/usr/bin/bash","gecos":"user2@example.com","groups":[{"name":"remote-test-group","ugid":"12345"},{"name":"local-test-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/first_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/first_auth index 073d10ad49..89c182d5f3 100644 --- a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/first_auth +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/first_auth @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"user1@example.com","uuid":"user1","dir":"/home/user1@example.com","shell":"/usr/bin/bash","gecos":"user1@example.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"user1@example.com","uuid":"user1","dir":"/home/user1@example.com","shell":"/usr/bin/bash","gecos":"user1@example.com","groups":[{"name":"remote-test-group","ugid":"12345"},{"name":"local-test-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/second_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/second_auth index c73c8a4d4c..f0643dd9fe 100644 --- a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/second_auth +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/second_auth @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"user2@example.com","uuid":"user2","dir":"/home/user2@example.com","shell":"/usr/bin/bash","gecos":"user2@example.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"user2@example.com","uuid":"user2","dir":"/home/user2@example.com","shell":"/usr/bin/bash","gecos":"user2@example.com","groups":[{"name":"remote-test-group","ugid":"12345"},{"name":"local-test-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/first_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/first_auth index 073d10ad49..89c182d5f3 100644 --- a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/first_auth +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/first_auth @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"user1@example.com","uuid":"user1","dir":"/home/user1@example.com","shell":"/usr/bin/bash","gecos":"user1@example.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"user1@example.com","uuid":"user1","dir":"/home/user1@example.com","shell":"/usr/bin/bash","gecos":"user1@example.com","groups":[{"name":"remote-test-group","ugid":"12345"},{"name":"local-test-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/second_auth b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/second_auth index c73c8a4d4c..f0643dd9fe 100644 --- a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/second_auth +++ b/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/second_auth @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"user2@example.com","uuid":"user2","dir":"/home/user2@example.com","shell":"/usr/bin/bash","gecos":"user2@example.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"user2@example.com","uuid":"user2","dir":"/home/user2@example.com","shell":"/usr/bin/bash","gecos":"user2@example.com","groups":[{"name":"remote-test-group","ugid":"12345"},{"name":"local-test-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestFetchUserInfo/golden/Successfully_fetch_user_info_with_default_home_when_not_provided b/internal/broker/testdata/TestFetchUserInfo/golden/Successfully_fetch_user_info_with_default_home_when_not_provided index 78d05dda77..23789d6618 100644 --- a/internal/broker/testdata/TestFetchUserInfo/golden/Successfully_fetch_user_info_with_default_home_when_not_provided +++ b/internal/broker/testdata/TestFetchUserInfo/golden/Successfully_fetch_user_info_with_default_home_when_not_provided @@ -4,7 +4,7 @@ home: /home/test-user@email.com shell: /usr/bin/bash gecos: test-user@email.com groups: - - name: test-fetch-user-info-remote-group + - name: remote-test-group ugid: "12345" - - name: linux-test-fetch-user-info-local-group + - name: local-test-group ugid: "" diff --git a/internal/broker/testdata/TestFetchUserInfo/golden/Successfully_fetch_user_info_with_groups b/internal/broker/testdata/TestFetchUserInfo/golden/Successfully_fetch_user_info_with_groups index 606bf2442f..471df1693b 100644 --- a/internal/broker/testdata/TestFetchUserInfo/golden/Successfully_fetch_user_info_with_groups +++ b/internal/broker/testdata/TestFetchUserInfo/golden/Successfully_fetch_user_info_with_groups @@ -4,7 +4,7 @@ home: /home/userInfoTests/test-user@email.com shell: /usr/bin/bash gecos: test-user@email.com groups: - - name: test-fetch-user-info-remote-group + - name: remote-test-group ugid: "12345" - - name: linux-test-fetch-user-info-local-group + - name: local-test-group ugid: "" diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_still_allowed_if_token_is_missing_scopes/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_still_allowed_if_token_is_missing_scopes/second_call index 12ca5144ad..f50b5eb551 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_still_allowed_if_token_is_missing_scopes/second_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_still_allowed_if_token_is_missing_scopes/second_call @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-test-group","ugid":"12345"},{"name":"local-test-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_expired_token/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_expired_token/first_call index 12ca5144ad..f50b5eb551 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_expired_token/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_expired_token/first_call @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-test-group","ugid":"12345"},{"name":"local-test-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_qrcode_reacquires_token/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_qrcode_reacquires_token/second_call index 12ca5144ad..f50b5eb551 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_qrcode_reacquires_token/second_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_qrcode_reacquires_token/second_call @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-test-group","ugid":"12345"},{"name":"local-test-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_device_auth_and_newpassword/second_call b/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_device_auth_and_newpassword/second_call index 12ca5144ad..f50b5eb551 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_device_auth_and_newpassword/second_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_device_auth_and_newpassword/second_call @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-test-group","ugid":"12345"},{"name":"local-test-group","ugid":""}]}}' err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_password/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_password/first_call index 12ca5144ad..f50b5eb551 100644 --- a/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_password/first_call +++ b/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_password/first_call @@ -1,3 +1,3 @@ access: granted -data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"new-broker-for-tests-remote-group","ugid":"12345"},{"name":"linux-new-broker-for-tests-local-group","ugid":""}]}}' +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-test-group","ugid":"12345"},{"name":"local-test-group","ugid":""}]}}' err: diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index f53be11064..4d53c7a508 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -384,7 +384,10 @@ func (p *MockProvider) GetUserInfo(ctx context.Context, accessToken *oauth2.Toke return info.User{}, err } - var userGroups []info.Group + userGroups := []info.Group{ + {Name: "remote-test-group", UGID: "12345"}, + {Name: "local-test-group", UGID: ""}, + } if p.GetGroupsFunc != nil { userGroups, err = p.GetGroupsFunc() if err != nil { From 06ea8845e1cc9f1ca99a1f853a8aa5b9c02826a9 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 14 Nov 2024 21:02:40 +0100 Subject: [PATCH 0247/1670] refactor: Move more functionality to newBrokerForTests Make it easier to create the mock provider, start the mock provider server, and create the broker with a single function call to newBrokerForTests. --- internal/broker/broker_test.go | 189 +++++++++++++++------------------ internal/broker/helper_test.go | 58 ++++++++-- 2 files changed, 130 insertions(+), 117 deletions(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index b724c0a0be..a898de847a 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -6,7 +6,6 @@ import ( "fmt" "os" "path/filepath" - "strings" "testing" "time" @@ -102,16 +101,9 @@ func TestNewSession(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - var opts []testutils.ProviderServerOption - for endpoint, handler := range tc.customHandlers { - opts = append(opts, testutils.WithHandler(endpoint, handler)) - } - - providerURL, cleanup := testutils.StartMockProviderServer("", nil, opts...) - t.Cleanup(cleanup) - cfg := &broker.Config{} - cfg.SetIssuerURL(providerURL) - b := newBrokerForTests(t, *cfg, nil) + b := newBrokerForTests(t, &brokerForTestConfig{ + customHandlers: tc.customHandlers, + }) id, _, err := b.NewSession("test-user", "lang", "auth") require.NoError(t, err, "NewSession should not have returned an error") @@ -207,29 +199,27 @@ func TestGetAuthenticationModes(t *testing.T) { tc.sessionMode = "auth" } - providerURL := defaultProviderURL - if tc.providerAddress != "" { - address := tc.providerAddress - opts := []testutils.ProviderServerOption{} + cfg := &brokerForTestConfig{} + if tc.providerAddress == "" { + // Use the default provider URL if no address is provided. + cfg.issuerURL = defaultProviderURL + } else { + cfg.listenAddress = tc.providerAddress + + const wellKnown = "/.well-known/openid-configuration" if tc.deviceAuthUnsupported { - opts = append(opts, testutils.WithHandler( - "/.well-known/openid-configuration", - testutils.OpenIDHandlerWithNoDeviceEndpoint("http://"+address), - )) + cfg.customHandlers = map[string]testutils.EndpointHandler{ + wellKnown: testutils.OpenIDHandlerWithNoDeviceEndpoint("http://" + tc.providerAddress), + } } if tc.unavailableProvider { - opts = append(opts, testutils.WithHandler( - "/.well-known/openid-configuration", - testutils.UnavailableHandler(), - )) + cfg.customHandlers = map[string]testutils.EndpointHandler{ + wellKnown: testutils.UnavailableHandler(), + } } - var cleanup func() - providerURL, cleanup = testutils.StartMockProviderServer(address, nil, opts...) - t.Cleanup(cleanup) } - cfg := &broker.Config{} - cfg.SetIssuerURL(providerURL) - b := newBrokerForTests(t, *cfg, nil) + b := newBrokerForTests(t, cfg) + sessionID, _ := newSessionForTests(t, b, "", tc.sessionMode) if tc.sessionID == "-" { sessionID = "" @@ -329,25 +319,19 @@ func TestSelectAuthenticationMode(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - providerURL := defaultProviderURL - if tc.customHandlers != nil { - var opts []testutils.ProviderServerOption - for path, handler := range tc.customHandlers { - opts = append(opts, testutils.WithHandler(path, handler)) - } - var cleanup func() - providerURL, cleanup = testutils.StartMockProviderServer("", nil, opts...) - defer cleanup() + cfg := &brokerForTestConfig{} + if tc.customHandlers == nil { + // Use the default provider URL if no custom handlers are provided. + cfg.issuerURL = defaultProviderURL + } else { + cfg.customHandlers = tc.customHandlers } + b := newBrokerForTests(t, cfg) sessionType := "auth" if tc.passwdSession { sessionType = "passwd" } - - cfg := &broker.Config{} - cfg.SetIssuerURL(providerURL) - b := newBrokerForTests(t, *cfg, nil) sessionID, _ := newSessionForTests(t, b, "", sessionType) if tc.tokenExists { @@ -461,7 +445,11 @@ func TestIsAuthenticated(t *testing.T) { "/token": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, }, - "Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails": {firstMode: authmodes.Password, token: &tokenOptions{noUserInfo: true}, getUserInfoFails: true}, + "Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails": { + firstMode: authmodes.Password, + token: &tokenOptions{noUserInfo: true}, + getUserInfoFails: true, + }, "Error_when_mode_is_qrcode_and_response_is_invalid": {firstAuthInfo: map[string]any{"response": "not a valid response"}}, "Error_when_mode_is_qrcode_and_link_expires": { @@ -519,25 +507,22 @@ func TestIsAuthenticated(t *testing.T) { err := os.Mkdir(dataDir, 0700) require.NoError(t, err, "Setup: Mkdir should not have returned an error") - providerURL := defaultProviderURL - if tc.customHandlers != nil { - var opts []testutils.ProviderServerOption - for path, handler := range tc.customHandlers { - opts = append(opts, testutils.WithHandler(path, handler)) - } - var cleanup func() - providerURL, cleanup = testutils.StartMockProviderServer(tc.address, nil, opts...) - t.Cleanup(cleanup) + cfg := &brokerForTestConfig{ + Config: broker.Config{DataDir: dataDir}, + getUserInfoFails: tc.getUserInfoFails, + } + if tc.customHandlers == nil { + // Use the default provider URL if no custom handlers are provided. + cfg.issuerURL = defaultProviderURL + } else { + cfg.customHandlers = tc.customHandlers + cfg.listenAddress = tc.address } + b := newBrokerForTests(t, cfg) - cfg := &broker.Config{DataDir: dataDir} - cfg.SetIssuerURL(providerURL) - mockProvider := &testutils.MockProvider{GetUserInfoFails: tc.getUserInfoFails} - b := newBrokerForTests(t, *cfg, mockProvider) sessionID, key := newSessionForTests(t, b, tc.username, tc.sessionMode) if tc.token != nil { - tc.token.issuer = providerURL generateAndStoreCachedInfo(t, *tc.token, b.TokenPathForSession(sessionID)) err = password.HashAndStorePassword(correctPassword, b.PasswordFilepathForSession(sessionID)) require.NoError(t, err, "Setup: HashAndStorePassword should not have returned an error") @@ -661,10 +646,9 @@ func TestIsAuthenticated(t *testing.T) { // Ensure that the directory structure is generic to avoid golden file conflicts if _, err := os.Stat(filepath.Dir(b.TokenPathForSession(sessionID))); err == nil { - toReplace := strings.ReplaceAll(strings.TrimPrefix(providerURL, "http://"), ":", "_") - tokenDir := filepath.Dir(filepath.Dir(b.TokenPathForSession(sessionID))) - newTokenDir := strings.ReplaceAll(tokenDir, toReplace, "provider_url") - err := os.Rename(tokenDir, newTokenDir) + issuerDir := filepath.Dir(filepath.Dir(b.TokenPathForSession(sessionID))) + newIsserDir := filepath.Join(filepath.Dir(issuerDir), "provider_url") + err := os.Rename(issuerDir, newIsserDir) if err != nil { require.ErrorIs(t, err, os.ErrNotExist, "Teardown: Failed to rename token directory") t.Logf("Failed to rename token directory: %v", err) @@ -699,27 +683,26 @@ func TestConcurrentIsAuthenticated(t *testing.T) { username1 := "user1@example.com" username2 := "user2@example.com" - providerURL, cleanup := testutils.StartMockProviderServer("", &testutils.TokenHandlerOptions{ - IDTokenClaims: []map[string]interface{}{ - {"sub": "user1", "name": "user1", "email": username1}, - {"sub": "user2", "name": "user2", "email": username2}, + b := newBrokerForTests(t, &brokerForTestConfig{ + Config: broker.Config{DataDir: dataDir}, + firstCallDelay: tc.firstCallDelay, + secondCallDelay: tc.secondCallDelay, + tokenHandlerOptions: &testutils.TokenHandlerOptions{ + IDTokenClaims: []map[string]interface{}{ + {"sub": "user1", "name": "user1", "email": username1}, + {"sub": "user2", "name": "user2", "email": username2}, + }, }, }) - defer cleanup() - - cfg := &broker.Config{DataDir: dataDir} - cfg.SetIssuerURL(providerURL) - mockProvider := &testutils.MockProvider{FirstCallDelay: tc.firstCallDelay, SecondCallDelay: tc.secondCallDelay} - b := newBrokerForTests(t, *cfg, mockProvider) firstSession, firstKey := newSessionForTests(t, b, username1, "") - firstToken := tokenOptions{username: username1, issuer: providerURL} + firstToken := tokenOptions{username: username1} generateAndStoreCachedInfo(t, firstToken, b.TokenPathForSession(firstSession)) err = password.HashAndStorePassword("password", b.PasswordFilepathForSession(firstSession)) require.NoError(t, err, "Setup: HashAndStorePassword should not have returned an error") secondSession, secondKey := newSessionForTests(t, b, username2, "") - secondToken := tokenOptions{username: username2, issuer: providerURL} + secondToken := tokenOptions{username: username2} generateAndStoreCachedInfo(t, secondToken, b.TokenPathForSession(secondSession)) err = password.HashAndStorePassword("password", b.PasswordFilepathForSession(secondSession)) require.NoError(t, err, "Setup: HashAndStorePassword should not have returned an error") @@ -789,9 +772,7 @@ func TestConcurrentIsAuthenticated(t *testing.T) { // Ensure that the directory structure is generic to avoid golden file conflicts issuerDataDir := filepath.Dir(b.UserDataDirForSession(firstSession)) if _, err := os.Stat(issuerDataDir); err == nil { - toReplace := strings.ReplaceAll(strings.TrimPrefix(providerURL, "http://"), ":", "_") - newIssuerDataDir := strings.ReplaceAll(issuerDataDir, toReplace, "provider_url") - err := os.Rename(issuerDataDir, newIssuerDataDir) + err := os.Rename(issuerDataDir, filepath.Join(filepath.Dir(issuerDataDir), "provider_url")) if err != nil { require.ErrorIs(t, err, os.ErrNotExist, "Teardown: Failed to rename issuer data directory") t.Logf("Failed to rename issuer data directory: %v", err) @@ -834,27 +815,23 @@ func TestFetchUserInfo(t *testing.T) { } dataDir := t.TempDir() - clientID := "test-client-id" - brokerCfg := &broker.Config{DataDir: dataDir} - brokerCfg.SetIssuerURL(defaultProviderURL) - brokerCfg.SetHomeBaseDir(homeDirPath) - brokerCfg.SetClientID(clientID) - var getGroupsFunc func() ([]info.Group, error) + cfg := &brokerForTestConfig{ + Config: broker.Config{DataDir: dataDir}, + issuerURL: defaultProviderURL, + homeBaseDir: homeDirPath, + } + if tc.emptyGroups { + cfg.getGroupsFunc = func() ([]info.Group, error) { + return []info.Group{}, nil + } + } if tc.wantGroupErr { - getGroupsFunc = func() ([]info.Group, error) { + cfg.getGroupsFunc = func() ([]info.Group, error) { return nil, errors.New("error getting groups") } - } else if tc.emptyGroups { - getGroupsFunc = func() ([]info.Group, error) { - return []info.Group{}, nil - } } - - mockProvider := &testutils.MockProvider{GetGroupsFunc: getGroupsFunc} - - b, err := broker.New(*brokerCfg, broker.WithCustomProvider(mockProvider)) - require.NoError(t, err, "Setup: New should not have returned an error") + b := newBrokerForTests(t, cfg) if tc.username == "" { tc.username = "test-user@email.com" @@ -884,12 +861,11 @@ func TestFetchUserInfo(t *testing.T) { func TestCancelIsAuthenticated(t *testing.T) { t.Parallel() - providerURL, cleanup := testutils.StartMockProviderServer("", nil, testutils.WithHandler("/token", testutils.HangingHandler(3*time.Second))) - t.Cleanup(cleanup) - - cfg := &broker.Config{} - cfg.SetIssuerURL(providerURL) - b := newBrokerForTests(t, *cfg, nil) + b := newBrokerForTests(t, &brokerForTestConfig{ + customHandlers: map[string]testutils.EndpointHandler{ + "/token": testutils.HangingHandler(3 * time.Second), + }, + }) sessionID, _ := newSessionForTests(t, b, "", "") updateAuthModes(t, b, sessionID, authmodes.DeviceQr) @@ -911,9 +887,9 @@ func TestCancelIsAuthenticated(t *testing.T) { func TestEndSession(t *testing.T) { t.Parallel() - cfg := &broker.Config{} - cfg.SetIssuerURL(defaultProviderURL) - b := newBrokerForTests(t, *cfg, nil) + b := newBrokerForTests(t, &brokerForTestConfig{ + issuerURL: defaultProviderURL, + }) sessionID, _ := newSessionForTests(t, b, "", "") @@ -967,11 +943,12 @@ func TestUserPreCheck(t *testing.T) { for name, tc := range tests { t.Run(name, func(t *testing.T) { t.Parallel() - cfg := &broker.Config{} - cfg.SetIssuerURL(defaultProviderURL) - cfg.SetHomeBaseDir(tc.homePrefix) - cfg.SetAllowedSSHSuffixes(tc.allowedSuffixes) - b := newBrokerForTests(t, *cfg, nil) + + b := newBrokerForTests(t, &brokerForTestConfig{ + issuerURL: defaultProviderURL, + homeBaseDir: tc.homePrefix, + allowedSSHSuffixes: tc.allowedSuffixes, + }) got, err := b.UserPreCheck(tc.username) if tc.wantErr { diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index 013dc55e15..1dc51621a6 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -20,16 +20,41 @@ import ( "golang.org/x/oauth2" ) -// newBrokerForTests is a helper function to create a new broker for tests with the specified configuration. -// -// Note that the issuerURL is required in the configuration. -func newBrokerForTests(t *testing.T, cfg broker.Config, provider *testutils.MockProvider) (b *broker.Broker) { +type brokerForTestConfig struct { + broker.Config + issuerURL string + homeBaseDir string + allowedSSHSuffixes []string + + getUserInfoFails bool + firstCallDelay int + secondCallDelay int + getGroupsFunc func() ([]info.Group, error) + + listenAddress string + tokenHandlerOptions *testutils.TokenHandlerOptions + customHandlers map[string]testutils.EndpointHandler +} + +// newBrokerForTests is a helper function to easily create a new broker for tests. +func newBrokerForTests(t *testing.T, cfg *brokerForTestConfig) (b *broker.Broker) { t.Helper() - require.NotEmpty(t, cfg.IssuerURL(), "Setup: issuerURL must not be empty") + if cfg.issuerURL != "" { + cfg.SetIssuerURL(cfg.issuerURL) + } + if cfg.homeBaseDir != "" { + cfg.SetHomeBaseDir(cfg.homeBaseDir) + } + if cfg.allowedSSHSuffixes != nil { + cfg.SetAllowedSSHSuffixes(cfg.allowedSSHSuffixes) + } - if provider == nil { - provider = &testutils.MockProvider{} + provider := &testutils.MockProvider{ + GetUserInfoFails: cfg.getUserInfoFails, + FirstCallDelay: cfg.firstCallDelay, + SecondCallDelay: cfg.secondCallDelay, + GetGroupsFunc: cfg.getGroupsFunc, } if cfg.DataDir == "" { @@ -39,10 +64,21 @@ func newBrokerForTests(t *testing.T, cfg broker.Config, provider *testutils.Mock cfg.SetClientID("test-client-id") } - b, err := broker.New( - cfg, - broker.WithCustomProvider(provider), - ) + if cfg.IssuerURL() == "" { + var serverOpts []testutils.ProviderServerOption + for endpoint, handler := range cfg.customHandlers { + serverOpts = append(serverOpts, testutils.WithHandler(endpoint, handler)) + } + providerURL, cleanup := testutils.StartMockProviderServer( + cfg.listenAddress, + cfg.tokenHandlerOptions, + serverOpts..., + ) + t.Cleanup(cleanup) + cfg.SetIssuerURL(providerURL) + } + + b, err := broker.New(cfg.Config, broker.WithCustomProvider(provider)) require.NoError(t, err, "Setup: New should not have returned an error") return b } From 3a5f4b1beab9db12544b3388344f6b0a1db675e0 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 14 Nov 2024 23:43:48 +0100 Subject: [PATCH 0248/1670] refactor: Rename providerURL to issuerURL Lets be consistent and use the OIDC terminology, which is "Issuer" (we append "URL" to make it a bit more descriptive). --- cmd/authd-oidc/daemon/daemon_test.go | 30 ++++++++++++++-------------- internal/broker/broker_test.go | 20 +++++++++---------- internal/broker/helper_test.go | 4 ++-- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/cmd/authd-oidc/daemon/daemon_test.go b/cmd/authd-oidc/daemon/daemon_test.go index 4d6dab1632..0a5be6c746 100644 --- a/cmd/authd-oidc/daemon/daemon_test.go +++ b/cmd/authd-oidc/daemon/daemon_test.go @@ -17,10 +17,10 @@ import ( "github.com/ubuntu/authd-oidc-brokers/internal/testutils" ) -var providerURL string +var issuerURL string func TestHelp(t *testing.T) { - a := daemon.NewForTests(t, nil, providerURL, "--help") + a := daemon.NewForTests(t, nil, issuerURL, "--help") getStdout := captureStdout(t) @@ -29,7 +29,7 @@ func TestHelp(t *testing.T) { } func TestCompletion(t *testing.T) { - a := daemon.NewForTests(t, nil, providerURL, "completion", "bash") + a := daemon.NewForTests(t, nil, issuerURL, "completion", "bash") getStdout := captureStdout(t) @@ -38,7 +38,7 @@ func TestCompletion(t *testing.T) { } func TestVersion(t *testing.T) { - a := daemon.NewForTests(t, nil, providerURL, "version") + a := daemon.NewForTests(t, nil, issuerURL, "version") getStdout := captureStdout(t) @@ -55,7 +55,7 @@ func TestVersion(t *testing.T) { } func TestNoUsageError(t *testing.T) { - a := daemon.NewForTests(t, nil, providerURL, "completion", "bash") + a := daemon.NewForTests(t, nil, issuerURL, "completion", "bash") getStdout := captureStdout(t) err := a.Run() @@ -66,7 +66,7 @@ func TestNoUsageError(t *testing.T) { } func TestUsageError(t *testing.T) { - a := daemon.NewForTests(t, nil, providerURL, "doesnotexist") + a := daemon.NewForTests(t, nil, issuerURL, "doesnotexist") err := a.Run() require.Error(t, err, "Run should return an error, stdout: %v") @@ -93,7 +93,7 @@ func TestCanQuitTwice(t *testing.T) { func TestAppCanQuitWithoutExecute(t *testing.T) { t.Skipf("This test is skipped because it is flaky. There is no way to guarantee Quit has been called before run.") - a := daemon.NewForTests(t, nil, providerURL) + a := daemon.NewForTests(t, nil, issuerURL) requireGoroutineStarted(t, a.Quit) err := a.Run() @@ -141,7 +141,7 @@ func TestAppRunFailsOnComponentsCreationAndQuit(t *testing.T) { }, } - a := daemon.NewForTests(t, &config, providerURL) + a := daemon.NewForTests(t, &config, issuerURL) err := a.Run() require.Error(t, err, "Run should return an error") }) @@ -197,7 +197,7 @@ func TestAppCanSigHupWithoutExecute(t *testing.T) { r, w, err := os.Pipe() require.NoError(t, err, "Setup: pipe shouldn't fail") - a := daemon.NewForTests(t, nil, providerURL) + a := daemon.NewForTests(t, nil, issuerURL) orig := os.Stdout os.Stdout = w @@ -214,7 +214,7 @@ func TestAppCanSigHupWithoutExecute(t *testing.T) { } func TestAppGetRootCmd(t *testing.T) { - a := daemon.NewForTests(t, nil, providerURL) + a := daemon.NewForTests(t, nil, issuerURL) require.NotNil(t, a.RootCmd(), "Returns root command") } @@ -246,8 +246,8 @@ func TestConfigHasPrecedenceOverPathsConfig(t *testing.T) { } overrideBrokerConfPath := filepath.Join(tmpDir, "override", "via", "config", "broker.conf") - daemon.GenerateBrokerConfig(t, overrideBrokerConfPath, providerURL) - a := daemon.NewForTests(t, &config, providerURL, "--config", overrideBrokerConfPath) + daemon.GenerateBrokerConfig(t, overrideBrokerConfPath, issuerURL) + a := daemon.NewForTests(t, &config, issuerURL, "--config", overrideBrokerConfPath) wg := sync.WaitGroup{} wg.Add(1) @@ -277,7 +277,7 @@ func TestAutoDetectConfig(t *testing.T) { }, } - configPath := daemon.GenerateTestConfig(t, &config, providerURL) + configPath := daemon.GenerateTestConfig(t, &config, issuerURL) configNextToBinaryPath := filepath.Join(filepath.Dir(os.Args[0]), t.Name()+".yaml") err := os.Rename(configPath, configNextToBinaryPath) require.NoError(t, err, "Could not relocate authd configuration file in the binary directory") @@ -343,7 +343,7 @@ func requireGoroutineStarted(t *testing.T, f func()) { func startDaemon(t *testing.T, conf *daemon.DaemonConfig) (app *daemon.App, done func()) { t.Helper() - a := daemon.NewForTests(t, conf, providerURL) + a := daemon.NewForTests(t, conf, issuerURL) wg := sync.WaitGroup{} wg.Add(1) @@ -402,7 +402,7 @@ func TestMain(m *testing.M) { defer cleanup() // Start provider mock - providerURL, cleanup = testutils.StartMockProviderServer("", nil) + issuerURL, cleanup = testutils.StartMockProviderServer("", nil) defer cleanup() m.Run() diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index a898de847a..b34dbef351 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -19,7 +19,7 @@ import ( "gopkg.in/yaml.v3" ) -var defaultProviderURL string +var defaultIssuerURL string func TestNew(t *testing.T) { t.Parallel() @@ -44,7 +44,7 @@ func TestNew(t *testing.T) { switch tc.issuer { case "": - tc.issuer = defaultProviderURL + tc.issuer = defaultIssuerURL case "-": tc.issuer = "" } @@ -202,7 +202,7 @@ func TestGetAuthenticationModes(t *testing.T) { cfg := &brokerForTestConfig{} if tc.providerAddress == "" { // Use the default provider URL if no address is provided. - cfg.issuerURL = defaultProviderURL + cfg.issuerURL = defaultIssuerURL } else { cfg.listenAddress = tc.providerAddress @@ -322,7 +322,7 @@ func TestSelectAuthenticationMode(t *testing.T) { cfg := &brokerForTestConfig{} if tc.customHandlers == nil { // Use the default provider URL if no custom handlers are provided. - cfg.issuerURL = defaultProviderURL + cfg.issuerURL = defaultIssuerURL } else { cfg.customHandlers = tc.customHandlers } @@ -513,7 +513,7 @@ func TestIsAuthenticated(t *testing.T) { } if tc.customHandlers == nil { // Use the default provider URL if no custom handlers are provided. - cfg.issuerURL = defaultProviderURL + cfg.issuerURL = defaultIssuerURL } else { cfg.customHandlers = tc.customHandlers cfg.listenAddress = tc.address @@ -818,7 +818,7 @@ func TestFetchUserInfo(t *testing.T) { cfg := &brokerForTestConfig{ Config: broker.Config{DataDir: dataDir}, - issuerURL: defaultProviderURL, + issuerURL: defaultIssuerURL, homeBaseDir: homeDirPath, } if tc.emptyGroups { @@ -836,7 +836,7 @@ func TestFetchUserInfo(t *testing.T) { if tc.username == "" { tc.username = "test-user@email.com" } - tc.token.issuer = defaultProviderURL + tc.token.issuer = defaultIssuerURL sessionID, _, err := b.NewSession(tc.username, "lang", "auth") require.NoError(t, err, "Setup: Failed to create session for the tests") @@ -888,7 +888,7 @@ func TestEndSession(t *testing.T) { t.Parallel() b := newBrokerForTests(t, &brokerForTestConfig{ - issuerURL: defaultProviderURL, + issuerURL: defaultIssuerURL, }) sessionID, _ := newSessionForTests(t, b, "", "") @@ -945,7 +945,7 @@ func TestUserPreCheck(t *testing.T) { t.Parallel() b := newBrokerForTests(t, &brokerForTestConfig{ - issuerURL: defaultProviderURL, + issuerURL: defaultIssuerURL, homeBaseDir: tc.homePrefix, allowedSSHSuffixes: tc.allowedSuffixes, }) @@ -964,7 +964,7 @@ func TestUserPreCheck(t *testing.T) { func TestMain(m *testing.M) { var cleanup func() - defaultProviderURL, cleanup = testutils.StartMockProviderServer("", nil) + defaultIssuerURL, cleanup = testutils.StartMockProviderServer("", nil) defer cleanup() os.Exit(m.Run()) diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index 1dc51621a6..baa0800e1e 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -69,13 +69,13 @@ func newBrokerForTests(t *testing.T, cfg *brokerForTestConfig) (b *broker.Broker for endpoint, handler := range cfg.customHandlers { serverOpts = append(serverOpts, testutils.WithHandler(endpoint, handler)) } - providerURL, cleanup := testutils.StartMockProviderServer( + issuerURL, cleanup := testutils.StartMockProviderServer( cfg.listenAddress, cfg.tokenHandlerOptions, serverOpts..., ) t.Cleanup(cleanup) - cfg.SetIssuerURL(providerURL) + cfg.SetIssuerURL(issuerURL) } b, err := broker.New(cfg.Config, broker.WithCustomProvider(provider)) From 038d181b0a315b40642bc6f064bb750399a26796 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 14 Nov 2024 23:54:27 +0100 Subject: [PATCH 0249/1670] Fix error message --- internal/testutils/golden.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/testutils/golden.go b/internal/testutils/golden.go index 9a693d2711..c7bceff0b4 100644 --- a/internal/testutils/golden.go +++ b/internal/testutils/golden.go @@ -281,7 +281,7 @@ func CheckOrUpdateGoldenFileTree(t *testing.T, path, goldenPath string) { goldenFile, err := os.Stat(goldenFilePath) if errors.Is(err, fs.ErrNotExist) { - require.Failf(t, "Unexpected file %s", p) + require.Failf(t, fmt.Sprintf("Missing golden file %s", goldenFilePath), "File: %s", p) } require.NoError(t, err, "Cannot get golden file %s", goldenFilePath) From c9b9239f42908c7d87875f2a3f4783c080eaee14 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 14 Nov 2024 23:55:36 +0100 Subject: [PATCH 0250/1670] broker: Test refreshing of groups --- internal/broker/broker_test.go | 57 ++++++++++++++++--- internal/broker/helper_test.go | 4 ++ .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json | 1 + .../first_call | 3 + .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json | 1 + .../first_call | 3 + .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json | 1 + .../first_call | 3 + 11 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/data/provider_url/test-user@email.com/password create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_session_is_offline/data/provider_url/test-user@email.com/password create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_session_is_offline/data/provider_url/test-user@email.com/token.json create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_session_is_offline/first_call create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_groups/data/provider_url/test-user@email.com/password create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_groups/data/provider_url/test-user@email.com/token.json create mode 100644 internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_groups/first_call diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index b34dbef351..ab0f09bc55 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -375,14 +375,16 @@ func TestIsAuthenticated(t *testing.T) { correctPassword := "password" tests := map[string]struct { - sessionMode string - username string + sessionMode string + sessionOffline bool + username string - firstMode string - firstChallenge string - firstAuthInfo map[string]any - badFirstKey bool - getUserInfoFails bool + firstMode string + firstChallenge string + firstAuthInfo map[string]any + badFirstKey bool + getUserInfoFails bool + groupsReturnedByProvider []info.Group customHandlers map[string]testutils.EndpointHandler address string @@ -395,6 +397,7 @@ func TestIsAuthenticated(t *testing.T) { invalidAuthData bool dontWaitForFirstCall bool readOnlyDataDir bool + wantGroups []info.Group }{ "Successfully_authenticate_user_with_device_auth_and_newpassword": {firstChallenge: "-", wantSecondCall: true}, "Successfully_authenticate_user_with_password": {firstMode: authmodes.Password, token: &tokenOptions{}}, @@ -423,6 +426,24 @@ func TestIsAuthenticated(t *testing.T) { }, address: "127.0.0.1:31313", }, + "Authenticating_with_password_refreshes_groups": { + firstMode: authmodes.Password, + token: &tokenOptions{}, + groupsReturnedByProvider: []info.Group{{Name: "refreshed-group"}}, + wantGroups: []info.Group{{Name: "refreshed-group"}}, + }, + "Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails": { + firstMode: authmodes.Password, + token: &tokenOptions{groups: []info.Group{{Name: "old-group"}}}, + getUserInfoFails: true, + wantGroups: []info.Group{{Name: "old-group"}}, + }, + "Authenticating_with_password_keeps_old_groups_if_session_is_offline": { + firstMode: authmodes.Password, + token: &tokenOptions{groups: []info.Group{{Name: "old-group"}}}, + sessionOffline: true, + wantGroups: []info.Group{{Name: "old-group"}}, + }, "Error_when_authentication_data_is_invalid": {invalidAuthData: true}, "Error_when_challenge_can_not_be_decrypted": {firstMode: authmodes.Password, badFirstKey: true}, @@ -501,6 +522,12 @@ func TestIsAuthenticated(t *testing.T) { tc.sessionMode = "auth" } + if tc.sessionOffline { + tc.customHandlers = map[string]testutils.EndpointHandler{ + "/.well-known/openid-configuration": testutils.UnavailableHandler(), + } + } + outDir := t.TempDir() dataDir := filepath.Join(outDir, "data") @@ -518,6 +545,11 @@ func TestIsAuthenticated(t *testing.T) { cfg.customHandlers = tc.customHandlers cfg.listenAddress = tc.address } + if tc.groupsReturnedByProvider != nil { + cfg.getGroupsFunc = func() ([]info.Group, error) { + return tc.groupsReturnedByProvider, nil + } + } b := newBrokerForTests(t, cfg) sessionID, key := newSessionForTests(t, b, tc.username, tc.sessionMode) @@ -581,6 +613,17 @@ func TestIsAuthenticated(t *testing.T) { err = os.WriteFile(filepath.Join(outDir, "first_call"), out, 0600) require.NoError(t, err, "Failed to write first response") + + if tc.wantGroups != nil { + type userInfoMsgType struct { + UserInfo info.User `json:"userinfo"` + } + userInfoMsg := userInfoMsgType{} + err = json.Unmarshal([]byte(data), &userInfoMsg) + require.NoError(t, err, "Failed to unmarshal user info message") + userInfo := userInfoMsg.UserInfo + require.ElementsMatch(t, tc.wantGroups, userInfo.Groups, "Groups should match") + } }() if !tc.dontWaitForFirstCall { diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index baa0800e1e..1810074ab0 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -148,6 +148,7 @@ func generateAndStoreCachedInfo(t *testing.T, options tokenOptions, path string) type tokenOptions struct { username string issuer string + groups []info.Group expired bool noRefreshToken bool @@ -211,6 +212,9 @@ func generateCachedInfo(t *testing.T, options tokenOptions) *token.AuthCachedInf {Name: "saved-local-group", UGID: ""}, }, } + if options.groups != nil { + tok.UserInfo.Groups = options.groups + } } if options.invalidClaims { diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/first_call new file mode 100644 index 0000000000..aca00f387f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/first_call @@ -0,0 +1,3 @@ +access: granted +data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"old-group","ugid":""}]}}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_session_is_offline/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_session_is_offline/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_session_is_offline/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_session_is_offline/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_session_is_offline/data/provider_url/test-user@email.com/token.json new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_session_is_offline/data/provider_url/test-user@email.com/token.json @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_session_is_offline/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_session_is_offline/first_call new file mode 100644 index 0000000000..aca00f387f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_session_is_offline/first_call @@ -0,0 +1,3 @@ +access: granted +data: '{"userinfo":{"name":"test-user@email.com","uuid":"saved-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"old-group","ugid":""}]}}' +err: diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_groups/data/provider_url/test-user@email.com/password b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_groups/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_groups/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_groups/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_groups/data/provider_url/test-user@email.com/token.json new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_groups/data/provider_url/test-user@email.com/token.json @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_groups/first_call b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_groups/first_call new file mode 100644 index 0000000000..0d246a68ad --- /dev/null +++ b/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_groups/first_call @@ -0,0 +1,3 @@ +access: granted +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"refreshed-group","ugid":""}]}}' +err: From 62ba9bb8a9fc5abb44f8911c6e4a3e66db4c9a57 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 19 Nov 2024 10:34:29 +0100 Subject: [PATCH 0251/1670] Move golden files below testdata/golden We used to store golden files in: testdata/TopLevelTestName/golden/SubTestName We now store them in: testdata/golden/TopLevelTestName/SubTestName That allows to easily copy/paste the full test name (top-level test name + subtest name) into a `go test --run` command. --- .../provider_url/user1@example.com/password | 0 .../provider_url/user1@example.com/token.json | 0 .../provider_url/user2@example.com/password | 0 .../provider_url/user2@example.com/token.json | 0 .../first_auth | 0 .../second_auth | 0 .../provider_url/user1@example.com/password | 0 .../provider_url/user1@example.com/token.json | 0 .../provider_url/user2@example.com/password | 0 .../provider_url/user2@example.com/token.json | 0 .../first_auth | 0 .../second_auth | 0 .../provider_url/user1@example.com/password | 0 .../provider_url/user1@example.com/token.json | 0 .../provider_url/user2@example.com/password | 0 .../provider_url/user2@example.com/token.json | 0 .../first_auth | 0 .../second_auth | 0 ...r_info_with_default_home_when_not_provided | 0 .../Successfully_fetch_user_info_with_groups | 0 ...uccessfully_fetch_user_info_without_groups | 0 .../Get_device_auth_qr_if_there_is_no_token | 0 ..._already_authenticated_with_device_auth_qr | 0 ...icated_with_password_and_session_is_passwd | 0 ...d_provider_does_not_support_device_auth_qr | 0 ...token_exists_and_provider_is_not_available | 0 ...word_if_token_exists_and_session_is_passwd | 0 ...assword_and_device_auth_qr_if_token_exists | 0 .../provider_url/test-user@email.com/password | 0 .../test-user@email.com/token.json | 0 .../first_call | 0 .../second_call | 0 .../provider_url/test-user@email.com/password | 0 .../test-user@email.com/token.json | 0 .../first_call | 0 .../provider_url/test-user@email.com/password | 0 .../test-user@email.com/token.json | 0 .../first_call | 0 .../provider_url/test-user@email.com/password | 0 .../test-user@email.com/token.json | 0 .../first_call | 0 .../provider_url/test-user@email.com/password | 0 .../test-user@email.com/token.json | 0 .../first_call | 0 .../provider_url/test-user@email.com/password | 0 .../test-user@email.com/token.json | 0 .../first_call | 0 .../provider_url/test-user@email.com/password | 0 .../test-user@email.com/token.json | 0 .../first_call | 0 .../provider_url/test-user@email.com/password | 0 .../test-user@email.com/token.json | 0 .../first_call | 0 .../second_call | 0 .../data/.empty | 0 .../first_call | 0 .../second_call | 0 .../data/.empty | 0 .../first_call | 0 .../data/.empty | 0 .../Error_when_can_not_cache_token/first_call | 0 .../second_call | 0 .../data/.empty | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../second_call | 0 .../provider_url/test-user@email.com/password | 0 .../test-user@email.com/token.json | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../provider_url/test-user@email.com/password | 0 .../test-user@email.com/token.json | 0 .../first_call | 0 .../provider_url/test-user@email.com/password | 0 .../test-user@email.com/token.json | 0 .../first_call | 0 .../provider_url/test-user@email.com/password | 0 .../test-user@email.com/token.json | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../provider_url/test-user@email.com/password | 0 .../test-user@email.com/token.json | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 0 .../provider_url/test-user@email.com/password | 0 .../test-user@email.com/token.json | 0 .../first_call | 0 .../provider_url/test-user@email.com/password | 0 .../test-user@email.com/token.json | 0 .../first_call | 0 .../second_call | 0 .../provider_url/test-user@email.com/password | 0 .../test-user@email.com/token.json | 0 .../first_call | 0 .../config.txt | 0 .../Successfully_parse_config_file/config.txt | 0 .../config.txt | 0 ...word_shows_correct_label_in_passwd_session | 0 .../Successfully_select_device_auth | 0 .../Successfully_select_device_auth_qr | 0 .../Successfully_select_newpassword | 0 .../Successfully_select_password | 0 ...erinfo_with_correct_homedir_after_precheck | 0 ...e_that_matches_at_least_one_allowed_suffix | 0 ...llow_username_with_matching_allowed_suffix | 0 internal/testutils/golden.go | 23 ++++++------------- 126 files changed, 7 insertions(+), 16 deletions(-) rename internal/broker/testdata/{TestConcurrentIsAuthenticated/golden => golden/TestConcurrentIsAuthenticated}/First_auth_starts_and_finishes_before_second/data/provider_url/user1@example.com/password (100%) rename internal/broker/testdata/{TestConcurrentIsAuthenticated/golden => golden/TestConcurrentIsAuthenticated}/First_auth_starts_and_finishes_before_second/data/provider_url/user1@example.com/token.json (100%) rename internal/broker/testdata/{TestConcurrentIsAuthenticated/golden => golden/TestConcurrentIsAuthenticated}/First_auth_starts_and_finishes_before_second/data/provider_url/user2@example.com/password (100%) rename internal/broker/testdata/{TestConcurrentIsAuthenticated/golden => golden/TestConcurrentIsAuthenticated}/First_auth_starts_and_finishes_before_second/data/provider_url/user2@example.com/token.json (100%) rename internal/broker/testdata/{TestConcurrentIsAuthenticated/golden => golden/TestConcurrentIsAuthenticated}/First_auth_starts_and_finishes_before_second/first_auth (100%) rename internal/broker/testdata/{TestConcurrentIsAuthenticated/golden => golden/TestConcurrentIsAuthenticated}/First_auth_starts_and_finishes_before_second/second_auth (100%) rename internal/broker/testdata/{TestConcurrentIsAuthenticated/golden => golden/TestConcurrentIsAuthenticated}/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1@example.com/password (100%) rename internal/broker/testdata/{TestConcurrentIsAuthenticated/golden => golden/TestConcurrentIsAuthenticated}/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1@example.com/token.json (100%) rename internal/broker/testdata/{TestConcurrentIsAuthenticated/golden => golden/TestConcurrentIsAuthenticated}/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2@example.com/password (100%) rename internal/broker/testdata/{TestConcurrentIsAuthenticated/golden => golden/TestConcurrentIsAuthenticated}/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2@example.com/token.json (100%) rename internal/broker/testdata/{TestConcurrentIsAuthenticated/golden => golden/TestConcurrentIsAuthenticated}/First_auth_starts_first_but_second_finishes_first/first_auth (100%) rename internal/broker/testdata/{TestConcurrentIsAuthenticated/golden => golden/TestConcurrentIsAuthenticated}/First_auth_starts_first_but_second_finishes_first/second_auth (100%) rename internal/broker/testdata/{TestConcurrentIsAuthenticated/golden => golden/TestConcurrentIsAuthenticated}/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1@example.com/password (100%) rename internal/broker/testdata/{TestConcurrentIsAuthenticated/golden => golden/TestConcurrentIsAuthenticated}/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1@example.com/token.json (100%) rename internal/broker/testdata/{TestConcurrentIsAuthenticated/golden => golden/TestConcurrentIsAuthenticated}/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2@example.com/password (100%) rename internal/broker/testdata/{TestConcurrentIsAuthenticated/golden => golden/TestConcurrentIsAuthenticated}/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2@example.com/token.json (100%) rename internal/broker/testdata/{TestConcurrentIsAuthenticated/golden => golden/TestConcurrentIsAuthenticated}/First_auth_starts_first_then_second_starts_and_first_finishes/first_auth (100%) rename internal/broker/testdata/{TestConcurrentIsAuthenticated/golden => golden/TestConcurrentIsAuthenticated}/First_auth_starts_first_then_second_starts_and_first_finishes/second_auth (100%) rename internal/broker/testdata/{TestFetchUserInfo/golden => golden/TestFetchUserInfo}/Successfully_fetch_user_info_with_default_home_when_not_provided (100%) rename internal/broker/testdata/{TestFetchUserInfo/golden => golden/TestFetchUserInfo}/Successfully_fetch_user_info_with_groups (100%) rename internal/broker/testdata/{TestFetchUserInfo/golden => golden/TestFetchUserInfo}/Successfully_fetch_user_info_without_groups (100%) rename internal/broker/testdata/{TestGetAuthenticationModes/golden => golden/TestGetAuthenticationModes}/Get_device_auth_qr_if_there_is_no_token (100%) rename internal/broker/testdata/{TestGetAuthenticationModes/golden => golden/TestGetAuthenticationModes}/Get_newpassword_if_already_authenticated_with_device_auth_qr (100%) rename internal/broker/testdata/{TestGetAuthenticationModes/golden => golden/TestGetAuthenticationModes}/Get_newpassword_if_already_authenticated_with_password_and_session_is_passwd (100%) rename internal/broker/testdata/{TestGetAuthenticationModes/golden => golden/TestGetAuthenticationModes}/Get_only_password_if_token_exists_and_provider_does_not_support_device_auth_qr (100%) rename internal/broker/testdata/{TestGetAuthenticationModes/golden => golden/TestGetAuthenticationModes}/Get_only_password_if_token_exists_and_provider_is_not_available (100%) rename internal/broker/testdata/{TestGetAuthenticationModes/golden => golden/TestGetAuthenticationModes}/Get_only_password_if_token_exists_and_session_is_passwd (100%) rename internal/broker/testdata/{TestGetAuthenticationModes/golden => golden/TestGetAuthenticationModes}/Get_password_and_device_auth_qr_if_token_exists (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_still_allowed_if_token_is_missing_scopes/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_still_allowed_if_token_is_missing_scopes/second_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_with_password_keeps_old_groups_if_session_is_offline/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_with_password_keeps_old_groups_if_session_is_offline/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_with_password_keeps_old_groups_if_session_is_offline/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_with_password_refreshes_expired_token/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_with_password_refreshes_groups/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_with_password_refreshes_groups/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_with_password_refreshes_groups/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_with_password_still_allowed_if_server_is_unreachable/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_with_qrcode_reacquires_token/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Authenticating_with_qrcode_reacquires_token/second_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_IsAuthenticated_is_ongoing_for_session/data/.empty (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_IsAuthenticated_is_ongoing_for_session/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_IsAuthenticated_is_ongoing_for_session/second_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_authentication_data_is_invalid/data/.empty (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_authentication_data_is_invalid/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_can_not_cache_token/data/.empty (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_can_not_cache_token/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_can_not_cache_token/second_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_challenge_can_not_be_decrypted/data/.empty (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_challenge_can_not_be_decrypted/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_empty_challenge_is_provided_for_local_password/data/.empty (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_empty_challenge_is_provided_for_local_password/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_empty_challenge_is_provided_for_local_password/second_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_link_code_and_can_not_get_token/data/.empty (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_link_code_and_can_not_get_token/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/data/.empty (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_link_code_and_link_expires/data/.empty (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_link_code_and_link_expires/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_link_code_and_response_is_invalid/data/.empty (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_link_code_and_response_is_invalid/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_newpassword_and_session_has_no_token/data/.empty (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_newpassword_and_session_has_no_token/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_password_and_token_does_not_exist/data/.empty (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_password_and_token_does_not_exist/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_password_and_token_is_invalid/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_password_and_token_refresh_times_out/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_password_but_server_returns_error/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_qrcode_and_can_not_get_token/data/.empty (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_qrcode_and_can_not_get_token/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/data/.empty (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_qrcode_and_link_expires/data/.empty (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_qrcode_and_link_expires/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_qrcode_and_response_is_invalid/data/.empty (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_mode_is_qrcode_and_response_is_invalid/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_provided_wrong_challenge/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_selected_username_does_not_match_the_provider_one/data/.empty (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_selected_username_does_not_match_the_provider_one/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Error_when_token_is_expired_and_refreshing_token_fails/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Successfully_authenticate_user_with_device_auth_and_newpassword/first_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Successfully_authenticate_user_with_device_auth_and_newpassword/second_call (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/{TestIsAuthenticated/golden => golden/TestIsAuthenticated}/Successfully_authenticate_user_with_password/first_call (100%) rename internal/broker/testdata/{TestParseConfig/golden => golden/TestParseConfig}/Do_not_fail_if_values_contain_a_single_template_delimiter/config.txt (100%) rename internal/broker/testdata/{TestParseConfig/golden => golden/TestParseConfig}/Successfully_parse_config_file/config.txt (100%) rename internal/broker/testdata/{TestParseConfig/golden => golden/TestParseConfig}/Successfully_parse_config_file_with_optional_values/config.txt (100%) rename internal/broker/testdata/{TestSelectAuthenticationMode/golden => golden/TestSelectAuthenticationMode}/Selected_newpassword_shows_correct_label_in_passwd_session (100%) rename internal/broker/testdata/{TestSelectAuthenticationMode/golden => golden/TestSelectAuthenticationMode}/Successfully_select_device_auth (100%) rename internal/broker/testdata/{TestSelectAuthenticationMode/golden => golden/TestSelectAuthenticationMode}/Successfully_select_device_auth_qr (100%) rename internal/broker/testdata/{TestSelectAuthenticationMode/golden => golden/TestSelectAuthenticationMode}/Successfully_select_newpassword (100%) rename internal/broker/testdata/{TestSelectAuthenticationMode/golden => golden/TestSelectAuthenticationMode}/Successfully_select_password (100%) rename internal/broker/testdata/{TestUserPreCheck/golden => golden/TestUserPreCheck}/Return_userinfo_with_correct_homedir_after_precheck (100%) rename internal/broker/testdata/{TestUserPreCheck/golden => golden/TestUserPreCheck}/Successfully_allow_username_that_matches_at_least_one_allowed_suffix (100%) rename internal/broker/testdata/{TestUserPreCheck/golden => golden/TestUserPreCheck}/Successfully_allow_username_with_matching_allowed_suffix (100%) diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user1@example.com/password b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_and_finishes_before_second/data/provider_url/user1@example.com/password similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user1@example.com/password rename to internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_and_finishes_before_second/data/provider_url/user1@example.com/password diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user1@example.com/token.json b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_and_finishes_before_second/data/provider_url/user1@example.com/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user1@example.com/token.json rename to internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_and_finishes_before_second/data/provider_url/user1@example.com/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user2@example.com/password b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_and_finishes_before_second/data/provider_url/user2@example.com/password similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user2@example.com/password rename to internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_and_finishes_before_second/data/provider_url/user2@example.com/password diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user2@example.com/token.json b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_and_finishes_before_second/data/provider_url/user2@example.com/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/data/provider_url/user2@example.com/token.json rename to internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_and_finishes_before_second/data/provider_url/user2@example.com/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/first_auth b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_and_finishes_before_second/first_auth similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/first_auth rename to internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_and_finishes_before_second/first_auth diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/second_auth b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_and_finishes_before_second/second_auth similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_and_finishes_before_second/second_auth rename to internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_and_finishes_before_second/second_auth diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1@example.com/password b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1@example.com/password similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1@example.com/password rename to internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1@example.com/password diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1@example.com/token.json b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1@example.com/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1@example.com/token.json rename to internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1@example.com/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2@example.com/password b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2@example.com/password similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2@example.com/password rename to internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2@example.com/password diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2@example.com/token.json b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2@example.com/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2@example.com/token.json rename to internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2@example.com/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/first_auth b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first/first_auth similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/first_auth rename to internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first/first_auth diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/second_auth b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first/second_auth similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_but_second_finishes_first/second_auth rename to internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first/second_auth diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1@example.com/password b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1@example.com/password similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1@example.com/password rename to internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1@example.com/password diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1@example.com/token.json b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1@example.com/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1@example.com/token.json rename to internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1@example.com/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2@example.com/password b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2@example.com/password similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2@example.com/password rename to internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2@example.com/password diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2@example.com/token.json b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2@example.com/token.json similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2@example.com/token.json rename to internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2@example.com/token.json diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/first_auth b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_then_second_starts_and_first_finishes/first_auth similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/first_auth rename to internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_then_second_starts_and_first_finishes/first_auth diff --git a/internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/second_auth b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_then_second_starts_and_first_finishes/second_auth similarity index 100% rename from internal/broker/testdata/TestConcurrentIsAuthenticated/golden/First_auth_starts_first_then_second_starts_and_first_finishes/second_auth rename to internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_then_second_starts_and_first_finishes/second_auth diff --git a/internal/broker/testdata/TestFetchUserInfo/golden/Successfully_fetch_user_info_with_default_home_when_not_provided b/internal/broker/testdata/golden/TestFetchUserInfo/Successfully_fetch_user_info_with_default_home_when_not_provided similarity index 100% rename from internal/broker/testdata/TestFetchUserInfo/golden/Successfully_fetch_user_info_with_default_home_when_not_provided rename to internal/broker/testdata/golden/TestFetchUserInfo/Successfully_fetch_user_info_with_default_home_when_not_provided diff --git a/internal/broker/testdata/TestFetchUserInfo/golden/Successfully_fetch_user_info_with_groups b/internal/broker/testdata/golden/TestFetchUserInfo/Successfully_fetch_user_info_with_groups similarity index 100% rename from internal/broker/testdata/TestFetchUserInfo/golden/Successfully_fetch_user_info_with_groups rename to internal/broker/testdata/golden/TestFetchUserInfo/Successfully_fetch_user_info_with_groups diff --git a/internal/broker/testdata/TestFetchUserInfo/golden/Successfully_fetch_user_info_without_groups b/internal/broker/testdata/golden/TestFetchUserInfo/Successfully_fetch_user_info_without_groups similarity index 100% rename from internal/broker/testdata/TestFetchUserInfo/golden/Successfully_fetch_user_info_without_groups rename to internal/broker/testdata/golden/TestFetchUserInfo/Successfully_fetch_user_info_without_groups diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/Get_device_auth_qr_if_there_is_no_token b/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_device_auth_qr_if_there_is_no_token similarity index 100% rename from internal/broker/testdata/TestGetAuthenticationModes/golden/Get_device_auth_qr_if_there_is_no_token rename to internal/broker/testdata/golden/TestGetAuthenticationModes/Get_device_auth_qr_if_there_is_no_token diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/Get_newpassword_if_already_authenticated_with_device_auth_qr b/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_newpassword_if_already_authenticated_with_device_auth_qr similarity index 100% rename from internal/broker/testdata/TestGetAuthenticationModes/golden/Get_newpassword_if_already_authenticated_with_device_auth_qr rename to internal/broker/testdata/golden/TestGetAuthenticationModes/Get_newpassword_if_already_authenticated_with_device_auth_qr diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/Get_newpassword_if_already_authenticated_with_password_and_session_is_passwd b/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_newpassword_if_already_authenticated_with_password_and_session_is_passwd similarity index 100% rename from internal/broker/testdata/TestGetAuthenticationModes/golden/Get_newpassword_if_already_authenticated_with_password_and_session_is_passwd rename to internal/broker/testdata/golden/TestGetAuthenticationModes/Get_newpassword_if_already_authenticated_with_password_and_session_is_passwd diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/Get_only_password_if_token_exists_and_provider_does_not_support_device_auth_qr b/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_only_password_if_token_exists_and_provider_does_not_support_device_auth_qr similarity index 100% rename from internal/broker/testdata/TestGetAuthenticationModes/golden/Get_only_password_if_token_exists_and_provider_does_not_support_device_auth_qr rename to internal/broker/testdata/golden/TestGetAuthenticationModes/Get_only_password_if_token_exists_and_provider_does_not_support_device_auth_qr diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/Get_only_password_if_token_exists_and_provider_is_not_available b/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_only_password_if_token_exists_and_provider_is_not_available similarity index 100% rename from internal/broker/testdata/TestGetAuthenticationModes/golden/Get_only_password_if_token_exists_and_provider_is_not_available rename to internal/broker/testdata/golden/TestGetAuthenticationModes/Get_only_password_if_token_exists_and_provider_is_not_available diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/Get_only_password_if_token_exists_and_session_is_passwd b/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_only_password_if_token_exists_and_session_is_passwd similarity index 100% rename from internal/broker/testdata/TestGetAuthenticationModes/golden/Get_only_password_if_token_exists_and_session_is_passwd rename to internal/broker/testdata/golden/TestGetAuthenticationModes/Get_only_password_if_token_exists_and_session_is_passwd diff --git a/internal/broker/testdata/TestGetAuthenticationModes/golden/Get_password_and_device_auth_qr_if_token_exists b/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_password_and_device_auth_qr_if_token_exists similarity index 100% rename from internal/broker/testdata/TestGetAuthenticationModes/golden/Get_password_and_device_auth_qr_if_token_exists rename to internal/broker/testdata/golden/TestGetAuthenticationModes/Get_password_and_device_auth_qr_if_token_exists diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_still_allowed_if_token_is_missing_scopes/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_still_allowed_if_token_is_missing_scopes/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_still_allowed_if_token_is_missing_scopes/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_still_allowed_if_token_is_missing_scopes/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_still_allowed_if_token_is_missing_scopes/second_call b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_still_allowed_if_token_is_missing_scopes/second_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_still_allowed_if_token_is_missing_scopes/second_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_still_allowed_if_token_is_missing_scopes/second_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_session_is_offline/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_keeps_old_groups_if_session_is_offline/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_session_is_offline/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_keeps_old_groups_if_session_is_offline/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_session_is_offline/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_keeps_old_groups_if_session_is_offline/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_session_is_offline/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_keeps_old_groups_if_session_is_offline/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_session_is_offline/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_keeps_old_groups_if_session_is_offline/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_keeps_old_groups_if_session_is_offline/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_keeps_old_groups_if_session_is_offline/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_expired_token/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_refreshes_expired_token/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_expired_token/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_refreshes_expired_token/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_groups/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_refreshes_groups/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_groups/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_refreshes_groups/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_groups/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_refreshes_groups/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_groups/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_refreshes_groups/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_groups/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_refreshes_groups/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_refreshes_groups/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_refreshes_groups/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_still_allowed_if_server_is_unreachable/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_still_allowed_if_server_is_unreachable/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_still_allowed_if_server_is_unreachable/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_still_allowed_if_server_is_unreachable/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_qrcode_reacquires_token/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_qrcode_reacquires_token/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_qrcode_reacquires_token/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_qrcode_reacquires_token/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_qrcode_reacquires_token/second_call b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_qrcode_reacquires_token/second_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Authenticating_with_qrcode_reacquires_token/second_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_qrcode_reacquires_token/second_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_IsAuthenticated_is_ongoing_for_session/data/.empty b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_IsAuthenticated_is_ongoing_for_session/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_IsAuthenticated_is_ongoing_for_session/data/.empty rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_IsAuthenticated_is_ongoing_for_session/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_IsAuthenticated_is_ongoing_for_session/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_IsAuthenticated_is_ongoing_for_session/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_IsAuthenticated_is_ongoing_for_session/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_IsAuthenticated_is_ongoing_for_session/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_IsAuthenticated_is_ongoing_for_session/second_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_IsAuthenticated_is_ongoing_for_session/second_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_IsAuthenticated_is_ongoing_for_session/second_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_IsAuthenticated_is_ongoing_for_session/second_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_authentication_data_is_invalid/data/.empty b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_authentication_data_is_invalid/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_authentication_data_is_invalid/data/.empty rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_authentication_data_is_invalid/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_authentication_data_is_invalid/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_authentication_data_is_invalid/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_authentication_data_is_invalid/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_authentication_data_is_invalid/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_can_not_cache_token/data/.empty b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_can_not_cache_token/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_can_not_cache_token/data/.empty rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_can_not_cache_token/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_can_not_cache_token/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_can_not_cache_token/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_can_not_cache_token/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_can_not_cache_token/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_can_not_cache_token/second_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_can_not_cache_token/second_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_can_not_cache_token/second_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_can_not_cache_token/second_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_challenge_can_not_be_decrypted/data/.empty b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_challenge_can_not_be_decrypted/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_challenge_can_not_be_decrypted/data/.empty rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_challenge_can_not_be_decrypted/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_challenge_can_not_be_decrypted/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_challenge_can_not_be_decrypted/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_challenge_can_not_be_decrypted/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_challenge_can_not_be_decrypted/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_empty_challenge_is_provided_for_local_password/data/.empty b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_empty_challenge_is_provided_for_local_password/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_empty_challenge_is_provided_for_local_password/data/.empty rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_empty_challenge_is_provided_for_local_password/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_empty_challenge_is_provided_for_local_password/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_empty_challenge_is_provided_for_local_password/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_empty_challenge_is_provided_for_local_password/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_empty_challenge_is_provided_for_local_password/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_empty_challenge_is_provided_for_local_password/second_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_empty_challenge_is_provided_for_local_password/second_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_empty_challenge_is_provided_for_local_password/second_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_empty_challenge_is_provided_for_local_password/second_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_can_not_get_token/data/.empty b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_link_code_and_can_not_get_token/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_can_not_get_token/data/.empty rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_link_code_and_can_not_get_token/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_can_not_get_token/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_link_code_and_can_not_get_token/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_can_not_get_token/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_link_code_and_can_not_get_token/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/data/.empty b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/data/.empty rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_link_code_and_can_not_get_token_due_to_timeout/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_link_expires/data/.empty b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_link_code_and_link_expires/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_link_expires/data/.empty rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_link_code_and_link_expires/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_link_expires/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_link_code_and_link_expires/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_link_expires/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_link_code_and_link_expires/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_response_is_invalid/data/.empty b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_link_code_and_response_is_invalid/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_response_is_invalid/data/.empty rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_link_code_and_response_is_invalid/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_response_is_invalid/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_link_code_and_response_is_invalid/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_link_code_and_response_is_invalid/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_link_code_and_response_is_invalid/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_newpassword_and_session_has_no_token/data/.empty b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_newpassword_and_session_has_no_token/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_newpassword_and_session_has_no_token/data/.empty rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_newpassword_and_session_has_no_token/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_newpassword_and_session_has_no_token/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_newpassword_and_session_has_no_token/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_newpassword_and_session_has_no_token/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_newpassword_and_session_has_no_token/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_does_not_exist/data/.empty b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_and_token_does_not_exist/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_does_not_exist/data/.empty rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_and_token_does_not_exist/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_does_not_exist/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_and_token_does_not_exist/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_does_not_exist/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_and_token_does_not_exist/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_is_invalid/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_and_token_is_invalid/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_is_invalid/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_and_token_is_invalid/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_refresh_times_out/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_and_token_refresh_times_out/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_and_token_refresh_times_out/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_and_token_refresh_times_out/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_but_server_returns_error/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_but_server_returns_error/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_password_but_server_returns_error/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_but_server_returns_error/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_can_not_get_token/data/.empty b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_qrcode_and_can_not_get_token/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_can_not_get_token/data/.empty rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_qrcode_and_can_not_get_token/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_can_not_get_token/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_qrcode_and_can_not_get_token/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_can_not_get_token/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_qrcode_and_can_not_get_token/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/data/.empty b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/data/.empty rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_qrcode_and_can_not_get_token_due_to_timeout/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_link_expires/data/.empty b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_qrcode_and_link_expires/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_link_expires/data/.empty rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_qrcode_and_link_expires/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_link_expires/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_qrcode_and_link_expires/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_link_expires/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_qrcode_and_link_expires/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_response_is_invalid/data/.empty b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_qrcode_and_response_is_invalid/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_response_is_invalid/data/.empty rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_qrcode_and_response_is_invalid/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_response_is_invalid/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_qrcode_and_response_is_invalid/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_mode_is_qrcode_and_response_is_invalid/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_qrcode_and_response_is_invalid/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_provided_wrong_challenge/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provided_wrong_challenge/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_provided_wrong_challenge/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provided_wrong_challenge/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_selected_username_does_not_match_the_provider_one/data/.empty b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_selected_username_does_not_match_the_provider_one/data/.empty similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_selected_username_does_not_match_the_provider_one/data/.empty rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_selected_username_does_not_match_the_provider_one/data/.empty diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_selected_username_does_not_match_the_provider_one/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_selected_username_does_not_match_the_provider_one/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_selected_username_does_not_match_the_provider_one/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_selected_username_does_not_match_the_provider_one/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Error_when_token_is_expired_and_refreshing_token_fails/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_token_is_expired_and_refreshing_token_fails/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Error_when_token_is_expired_and_refreshing_token_fails/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_token_is_expired_and_refreshing_token_fails/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/golden/TestIsAuthenticated/Successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/golden/TestIsAuthenticated/Successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_device_auth_and_newpassword/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Successfully_authenticate_user_with_device_auth_and_newpassword/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_device_auth_and_newpassword/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Successfully_authenticate_user_with_device_auth_and_newpassword/first_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_device_auth_and_newpassword/second_call b/internal/broker/testdata/golden/TestIsAuthenticated/Successfully_authenticate_user_with_device_auth_and_newpassword/second_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_device_auth_and_newpassword/second_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Successfully_authenticate_user_with_device_auth_and_newpassword/second_call diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/golden/TestIsAuthenticated/Successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/golden/TestIsAuthenticated/Successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_password/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Successfully_authenticate_user_with_password/first_call similarity index 100% rename from internal/broker/testdata/TestIsAuthenticated/golden/Successfully_authenticate_user_with_password/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Successfully_authenticate_user_with_password/first_call diff --git a/internal/broker/testdata/TestParseConfig/golden/Do_not_fail_if_values_contain_a_single_template_delimiter/config.txt b/internal/broker/testdata/golden/TestParseConfig/Do_not_fail_if_values_contain_a_single_template_delimiter/config.txt similarity index 100% rename from internal/broker/testdata/TestParseConfig/golden/Do_not_fail_if_values_contain_a_single_template_delimiter/config.txt rename to internal/broker/testdata/golden/TestParseConfig/Do_not_fail_if_values_contain_a_single_template_delimiter/config.txt diff --git a/internal/broker/testdata/TestParseConfig/golden/Successfully_parse_config_file/config.txt b/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file/config.txt similarity index 100% rename from internal/broker/testdata/TestParseConfig/golden/Successfully_parse_config_file/config.txt rename to internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file/config.txt diff --git a/internal/broker/testdata/TestParseConfig/golden/Successfully_parse_config_file_with_optional_values/config.txt b/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file_with_optional_values/config.txt similarity index 100% rename from internal/broker/testdata/TestParseConfig/golden/Successfully_parse_config_file_with_optional_values/config.txt rename to internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file_with_optional_values/config.txt diff --git a/internal/broker/testdata/TestSelectAuthenticationMode/golden/Selected_newpassword_shows_correct_label_in_passwd_session b/internal/broker/testdata/golden/TestSelectAuthenticationMode/Selected_newpassword_shows_correct_label_in_passwd_session similarity index 100% rename from internal/broker/testdata/TestSelectAuthenticationMode/golden/Selected_newpassword_shows_correct_label_in_passwd_session rename to internal/broker/testdata/golden/TestSelectAuthenticationMode/Selected_newpassword_shows_correct_label_in_passwd_session diff --git a/internal/broker/testdata/TestSelectAuthenticationMode/golden/Successfully_select_device_auth b/internal/broker/testdata/golden/TestSelectAuthenticationMode/Successfully_select_device_auth similarity index 100% rename from internal/broker/testdata/TestSelectAuthenticationMode/golden/Successfully_select_device_auth rename to internal/broker/testdata/golden/TestSelectAuthenticationMode/Successfully_select_device_auth diff --git a/internal/broker/testdata/TestSelectAuthenticationMode/golden/Successfully_select_device_auth_qr b/internal/broker/testdata/golden/TestSelectAuthenticationMode/Successfully_select_device_auth_qr similarity index 100% rename from internal/broker/testdata/TestSelectAuthenticationMode/golden/Successfully_select_device_auth_qr rename to internal/broker/testdata/golden/TestSelectAuthenticationMode/Successfully_select_device_auth_qr diff --git a/internal/broker/testdata/TestSelectAuthenticationMode/golden/Successfully_select_newpassword b/internal/broker/testdata/golden/TestSelectAuthenticationMode/Successfully_select_newpassword similarity index 100% rename from internal/broker/testdata/TestSelectAuthenticationMode/golden/Successfully_select_newpassword rename to internal/broker/testdata/golden/TestSelectAuthenticationMode/Successfully_select_newpassword diff --git a/internal/broker/testdata/TestSelectAuthenticationMode/golden/Successfully_select_password b/internal/broker/testdata/golden/TestSelectAuthenticationMode/Successfully_select_password similarity index 100% rename from internal/broker/testdata/TestSelectAuthenticationMode/golden/Successfully_select_password rename to internal/broker/testdata/golden/TestSelectAuthenticationMode/Successfully_select_password diff --git a/internal/broker/testdata/TestUserPreCheck/golden/Return_userinfo_with_correct_homedir_after_precheck b/internal/broker/testdata/golden/TestUserPreCheck/Return_userinfo_with_correct_homedir_after_precheck similarity index 100% rename from internal/broker/testdata/TestUserPreCheck/golden/Return_userinfo_with_correct_homedir_after_precheck rename to internal/broker/testdata/golden/TestUserPreCheck/Return_userinfo_with_correct_homedir_after_precheck diff --git a/internal/broker/testdata/TestUserPreCheck/golden/Successfully_allow_username_that_matches_at_least_one_allowed_suffix b/internal/broker/testdata/golden/TestUserPreCheck/Successfully_allow_username_that_matches_at_least_one_allowed_suffix similarity index 100% rename from internal/broker/testdata/TestUserPreCheck/golden/Successfully_allow_username_that_matches_at_least_one_allowed_suffix rename to internal/broker/testdata/golden/TestUserPreCheck/Successfully_allow_username_that_matches_at_least_one_allowed_suffix diff --git a/internal/broker/testdata/TestUserPreCheck/golden/Successfully_allow_username_with_matching_allowed_suffix b/internal/broker/testdata/golden/TestUserPreCheck/Successfully_allow_username_with_matching_allowed_suffix similarity index 100% rename from internal/broker/testdata/TestUserPreCheck/golden/Successfully_allow_username_with_matching_allowed_suffix rename to internal/broker/testdata/golden/TestUserPreCheck/Successfully_allow_username_with_matching_allowed_suffix diff --git a/internal/testutils/golden.go b/internal/testutils/golden.go index c7bceff0b4..6a455e3b60 100644 --- a/internal/testutils/golden.go +++ b/internal/testutils/golden.go @@ -134,28 +134,19 @@ func CheckValidGoldenFileName(t *testing.T, name string) { "Invalid golden file name %q. Only alphanumeric characters, underscores, dashes, and dots are allowed", name) } -// TestFamilyPath returns the path of the dir for storing fixtures and other files related to the test. -func TestFamilyPath(t *testing.T) string { - t.Helper() - - // Ensures that only the name of the parent test is used. - super, _, _ := strings.Cut(t.Name(), "/") - - return filepath.Join("testdata", super) -} - // GoldenPath returns the golden path for the provided test. func GoldenPath(t *testing.T) string { t.Helper() - path := filepath.Join(TestFamilyPath(t), "golden") - _, subtestName, found := strings.Cut(t.Name(), "/") - if found { - CheckValidGoldenFileName(t, subtestName) - path = filepath.Join(path, subtestName) + topLevelTest, subtest, subtestFound := strings.Cut(t.Name(), "/") + CheckValidGoldenFileName(t, topLevelTest) + + if !subtestFound { + return filepath.Join("testdata", "golden", topLevelTest) } - return path + CheckValidGoldenFileName(t, subtest) + return filepath.Join("testdata", "golden", topLevelTest, subtest) } // runDelta pipes the unified diff through the `delta` command for word-level diff and coloring. From c48dc9cc7257082b36c35269523b4526878e0fa8 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 19 Nov 2024 10:51:11 +0100 Subject: [PATCH 0252/1670] refactor: Extract package "golden" --- internal/broker/broker_test.go | 13 +++++++------ internal/broker/config_test.go | 4 ++-- internal/testutils/{ => golden}/golden.go | 3 ++- 3 files changed, 11 insertions(+), 9 deletions(-) rename internal/testutils/{ => golden}/golden.go (99%) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index ab0f09bc55..b6712061dc 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -15,6 +15,7 @@ import ( "github.com/ubuntu/authd-oidc-brokers/internal/password" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "github.com/ubuntu/authd-oidc-brokers/internal/testutils" + "github.com/ubuntu/authd-oidc-brokers/internal/testutils/golden" "github.com/ubuntu/authd-oidc-brokers/internal/token" "gopkg.in/yaml.v3" ) @@ -249,7 +250,7 @@ func TestGetAuthenticationModes(t *testing.T) { } require.NoError(t, err, "GetAuthenticationModes should not have returned an error") - testutils.CheckOrUpdateGoldenYAML(t, got) + golden.CheckOrUpdateGoldenYAML(t, got) }) } } @@ -358,7 +359,7 @@ func TestSelectAuthenticationMode(t *testing.T) { } require.NoError(t, err, "SelectAuthenticationMode should not have returned an error") - testutils.CheckOrUpdateGoldenYAML(t, got) + golden.CheckOrUpdateGoldenYAML(t, got) }) } } @@ -698,7 +699,7 @@ func TestIsAuthenticated(t *testing.T) { } } - testutils.CheckOrUpdateGoldenFileTree(t, outDir, testutils.GoldenPath(t)) + golden.CheckOrUpdateGoldenFileTree(t, outDir, golden.GoldenPath(t)) }) } } @@ -821,7 +822,7 @@ func TestConcurrentIsAuthenticated(t *testing.T) { t.Logf("Failed to rename issuer data directory: %v", err) } } - testutils.CheckOrUpdateGoldenFileTree(t, outDir, testutils.GoldenPath(t)) + golden.CheckOrUpdateGoldenFileTree(t, outDir, golden.GoldenPath(t)) }) } } @@ -896,7 +897,7 @@ func TestFetchUserInfo(t *testing.T) { } require.NoError(t, err, "FetchUserInfo should not have returned an error") - testutils.CheckOrUpdateGoldenYAML(t, got) + golden.CheckOrUpdateGoldenYAML(t, got) }) } } @@ -1000,7 +1001,7 @@ func TestUserPreCheck(t *testing.T) { } require.NoError(t, err, "UserPreCheck should not have returned an error") - testutils.CheckOrUpdateGolden(t, got) + golden.CheckOrUpdateGolden(t, got) }) } } diff --git a/internal/broker/config_test.go b/internal/broker/config_test.go index bd02d62fe0..69ecc81a99 100644 --- a/internal/broker/config_test.go +++ b/internal/broker/config_test.go @@ -10,7 +10,7 @@ import ( "unsafe" "github.com/stretchr/testify/require" - "github.com/ubuntu/authd-oidc-brokers/internal/testutils" + "github.com/ubuntu/authd-oidc-brokers/internal/testutils/golden" ) var configTypes = map[string]string{ @@ -106,7 +106,7 @@ func TestParseConfig(t *testing.T) { err = os.WriteFile(filepath.Join(outDir, "config.txt"), []byte(strings.Join(fields, "\n")), 0600) require.NoError(t, err) - testutils.CheckOrUpdateGoldenFileTree(t, outDir, testutils.GoldenPath(t)) + golden.CheckOrUpdateGoldenFileTree(t, outDir, golden.GoldenPath(t)) }) } } diff --git a/internal/testutils/golden.go b/internal/testutils/golden/golden.go similarity index 99% rename from internal/testutils/golden.go rename to internal/testutils/golden/golden.go index 6a455e3b60..4ef75c7857 100644 --- a/internal/testutils/golden.go +++ b/internal/testutils/golden/golden.go @@ -1,4 +1,5 @@ -package testutils +// Package golden provides utilities to compare and update golden files in tests. +package golden import ( "bytes" From c9d4120eb29167512578be9349e2854633ab5e4e Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 19 Nov 2024 10:53:43 +0100 Subject: [PATCH 0253/1670] refactor: Use variadic options argument --- internal/broker/broker_test.go | 4 ++-- internal/broker/config_test.go | 2 +- internal/testutils/golden/golden.go | 27 +++++++++++++++++---------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index b6712061dc..69daf434e6 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -699,7 +699,7 @@ func TestIsAuthenticated(t *testing.T) { } } - golden.CheckOrUpdateGoldenFileTree(t, outDir, golden.GoldenPath(t)) + golden.CheckOrUpdateGoldenFileTree(t, outDir) }) } } @@ -822,7 +822,7 @@ func TestConcurrentIsAuthenticated(t *testing.T) { t.Logf("Failed to rename issuer data directory: %v", err) } } - golden.CheckOrUpdateGoldenFileTree(t, outDir, golden.GoldenPath(t)) + golden.CheckOrUpdateGoldenFileTree(t, outDir) }) } } diff --git a/internal/broker/config_test.go b/internal/broker/config_test.go index 69ecc81a99..2cb38f31b3 100644 --- a/internal/broker/config_test.go +++ b/internal/broker/config_test.go @@ -106,7 +106,7 @@ func TestParseConfig(t *testing.T) { err = os.WriteFile(filepath.Join(outDir, "config.txt"), []byte(strings.Join(fields, "\n")), 0600) require.NoError(t, err) - golden.CheckOrUpdateGoldenFileTree(t, outDir, golden.GoldenPath(t)) + golden.CheckOrUpdateGoldenFileTree(t, outDir) }) } } diff --git a/internal/testutils/golden/golden.go b/internal/testutils/golden/golden.go index 4ef75c7857..d8ddad81b9 100644 --- a/internal/testutils/golden/golden.go +++ b/internal/testutils/golden/golden.go @@ -227,13 +227,20 @@ func checkGoldenFileEqualsString(t *testing.T, got, goldenPath string) { } // CheckOrUpdateGoldenFileTree allows comparing a goldPath directory to p. Those can be updated via the dedicated flag. -func CheckOrUpdateGoldenFileTree(t *testing.T, path, goldenPath string) { +func CheckOrUpdateGoldenFileTree(t *testing.T, path string, options ...GoldenOption) { t.Helper() + opts := goldenOptions{ + path: GoldenPath(t), + } + for _, f := range options { + f(&opts) + } + if update { - t.Logf("updating golden path %s", goldenPath) - err := os.RemoveAll(goldenPath) - require.NoError(t, err, "Cannot remove golden path %s", goldenPath) + t.Logf("updating golden path %s", opts.path) + err := os.RemoveAll(opts.path) + require.NoError(t, err, "Cannot remove golden path %s", opts.path) // check the source directory exists before trying to copy it info, err := os.Stat(path) @@ -246,13 +253,13 @@ func CheckOrUpdateGoldenFileTree(t *testing.T, path, goldenPath string) { // copy file data, err := os.ReadFile(path) require.NoError(t, err, "Cannot read file %s", path) - err = os.WriteFile(goldenPath, data, info.Mode()) + err = os.WriteFile(opts.path, data, info.Mode()) require.NoError(t, err, "Cannot write golden file") } else { err := addEmptyMarker(path) require.NoError(t, err, "Cannot add empty marker to directory %s", path) - err = cp.Copy(path, goldenPath) + err = cp.Copy(path, opts.path) require.NoError(t, err, "Can’t update golden directory") } } @@ -265,7 +272,7 @@ func CheckOrUpdateGoldenFileTree(t *testing.T, path, goldenPath string) { relPath, err := filepath.Rel(path, p) require.NoError(t, err, "Cannot get relative path for %s", p) - goldenFilePath := filepath.Join(goldenPath, relPath) + goldenFilePath := filepath.Join(opts.path, relPath) if de.IsDir() { return nil @@ -293,7 +300,7 @@ func CheckOrUpdateGoldenFileTree(t *testing.T, path, goldenPath string) { require.NoError(t, err, "Cannot walk through directory %s", path) // Check if there are files in the golden directory that are not in the source directory. - err = filepath.WalkDir(goldenPath, func(p string, de fs.DirEntry, err error) error { + err = filepath.WalkDir(opts.path, func(p string, de fs.DirEntry, err error) error { if err != nil { return err } @@ -303,7 +310,7 @@ func CheckOrUpdateGoldenFileTree(t *testing.T, path, goldenPath string) { return nil } - relPath, err := filepath.Rel(goldenPath, p) + relPath, err := filepath.Rel(opts.path, p) require.NoError(t, err, "Cannot get relative path for %s", p) filePath := filepath.Join(path, relPath) @@ -316,7 +323,7 @@ func CheckOrUpdateGoldenFileTree(t *testing.T, path, goldenPath string) { return nil }) - require.NoError(t, err, "Cannot walk through directory %s", goldenPath) + require.NoError(t, err, "Cannot walk through directory %s", opts.path) } const fileForEmptyDir = ".empty" From 78cc7191c44caa0b6c4166630071c488d5f529df Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 19 Nov 2024 10:58:42 +0100 Subject: [PATCH 0254/1670] Support specifying relative path to golden file --- internal/testutils/golden/golden.go | 30 ++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/internal/testutils/golden/golden.go b/internal/testutils/golden/golden.go index d8ddad81b9..e5136f6de6 100644 --- a/internal/testutils/golden/golden.go +++ b/internal/testutils/golden/golden.go @@ -62,12 +62,13 @@ func updateGoldenFile(t *testing.T, path string, data []byte) { func CheckOrUpdateGolden(t *testing.T, got string, options ...GoldenOption) { t.Helper() - opts := goldenOptions{ - path: GoldenPath(t), - } + opts := goldenOptions{} for _, f := range options { f(&opts) } + if !filepath.IsAbs(opts.path) { + opts.path = filepath.Join(GoldenPath(t), opts.path) + } if update { updateGoldenFile(t, opts.path, []byte(got)) @@ -92,12 +93,13 @@ func CheckOrUpdateGoldenYAML[E any](t *testing.T, got E, options ...GoldenOption func LoadWithUpdateFromGolden(t *testing.T, data string, options ...GoldenOption) string { t.Helper() - opts := goldenOptions{ - path: GoldenPath(t), - } + opts := goldenOptions{} for _, f := range options { f(&opts) } + if !filepath.IsAbs(opts.path) { + opts.path = filepath.Join(GoldenPath(t), opts.path) + } if update { updateGoldenFile(t, opts.path, []byte(data)) @@ -139,15 +141,20 @@ func CheckValidGoldenFileName(t *testing.T, name string) { func GoldenPath(t *testing.T) string { t.Helper() + cwd, err := os.Getwd() + require.NoError(t, err, "Cannot get current working directory") + topLevelTest, subtest, subtestFound := strings.Cut(t.Name(), "/") CheckValidGoldenFileName(t, topLevelTest) + path := filepath.Join(cwd, "testdata", "golden", topLevelTest) + if !subtestFound { - return filepath.Join("testdata", "golden", topLevelTest) + return path } CheckValidGoldenFileName(t, subtest) - return filepath.Join("testdata", "golden", topLevelTest, subtest) + return filepath.Join(path, subtest) } // runDelta pipes the unified diff through the `delta` command for word-level diff and coloring. @@ -230,12 +237,13 @@ func checkGoldenFileEqualsString(t *testing.T, got, goldenPath string) { func CheckOrUpdateGoldenFileTree(t *testing.T, path string, options ...GoldenOption) { t.Helper() - opts := goldenOptions{ - path: GoldenPath(t), - } + opts := goldenOptions{} for _, f := range options { f(&opts) } + if !filepath.IsAbs(opts.path) { + opts.path = filepath.Join(GoldenPath(t), opts.path) + } if update { t.Logf("updating golden path %s", opts.path) From 485678122983de422a82c7ad5d12798d5aa0d0ed Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 19 Nov 2024 11:01:46 +0100 Subject: [PATCH 0255/1670] refactor: Remove "Golden" from exported names in golden package The package name is already "golden", so we don't have to mention it again in the exported names. --- internal/broker/broker_test.go | 12 ++++----- internal/broker/config_test.go | 2 +- internal/testutils/golden/golden.go | 42 ++++++++++++++--------------- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 69daf434e6..6ed1f64902 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -250,7 +250,7 @@ func TestGetAuthenticationModes(t *testing.T) { } require.NoError(t, err, "GetAuthenticationModes should not have returned an error") - golden.CheckOrUpdateGoldenYAML(t, got) + golden.CheckOrUpdateYAML(t, got) }) } } @@ -359,7 +359,7 @@ func TestSelectAuthenticationMode(t *testing.T) { } require.NoError(t, err, "SelectAuthenticationMode should not have returned an error") - golden.CheckOrUpdateGoldenYAML(t, got) + golden.CheckOrUpdateYAML(t, got) }) } } @@ -699,7 +699,7 @@ func TestIsAuthenticated(t *testing.T) { } } - golden.CheckOrUpdateGoldenFileTree(t, outDir) + golden.CheckOrUpdateFileTree(t, outDir) }) } } @@ -822,7 +822,7 @@ func TestConcurrentIsAuthenticated(t *testing.T) { t.Logf("Failed to rename issuer data directory: %v", err) } } - golden.CheckOrUpdateGoldenFileTree(t, outDir) + golden.CheckOrUpdateFileTree(t, outDir) }) } } @@ -897,7 +897,7 @@ func TestFetchUserInfo(t *testing.T) { } require.NoError(t, err, "FetchUserInfo should not have returned an error") - golden.CheckOrUpdateGoldenYAML(t, got) + golden.CheckOrUpdateYAML(t, got) }) } } @@ -1001,7 +1001,7 @@ func TestUserPreCheck(t *testing.T) { } require.NoError(t, err, "UserPreCheck should not have returned an error") - golden.CheckOrUpdateGolden(t, got) + golden.CheckOrUpdate(t, got) }) } } diff --git a/internal/broker/config_test.go b/internal/broker/config_test.go index 2cb38f31b3..9707e96aa1 100644 --- a/internal/broker/config_test.go +++ b/internal/broker/config_test.go @@ -106,7 +106,7 @@ func TestParseConfig(t *testing.T) { err = os.WriteFile(filepath.Join(outDir, "config.txt"), []byte(strings.Join(fields, "\n")), 0600) require.NoError(t, err) - golden.CheckOrUpdateGoldenFileTree(t, outDir) + golden.CheckOrUpdateFileTree(t, outDir) }) } } diff --git a/internal/testutils/golden/golden.go b/internal/testutils/golden/golden.go index e5136f6de6..378096edf9 100644 --- a/internal/testutils/golden/golden.go +++ b/internal/testutils/golden/golden.go @@ -37,11 +37,11 @@ type goldenOptions struct { path string } -// GoldenOption is a supported option reference to change the golden files comparison. -type GoldenOption func(*goldenOptions) +// Option is a supported option reference to change the golden files comparison. +type Option func(*goldenOptions) -// WithGoldenPath overrides the default path for golden files used. -func WithGoldenPath(path string) GoldenOption { +// WithPath overrides the default path for golden files used. +func WithPath(path string) Option { return func(o *goldenOptions) { if path != "" { o.path = path @@ -57,9 +57,9 @@ func updateGoldenFile(t *testing.T, path string, data []byte) { require.NoError(t, err, "Cannot write golden file") } -// CheckOrUpdateGolden compares the provided string with the content of the golden file. If the update environment +// CheckOrUpdate compares the provided string with the content of the golden file. If the update environment // variable is set, the golden file is updated with the provided string. -func CheckOrUpdateGolden(t *testing.T, got string, options ...GoldenOption) { +func CheckOrUpdate(t *testing.T, got string, options ...Option) { t.Helper() opts := goldenOptions{} @@ -67,7 +67,7 @@ func CheckOrUpdateGolden(t *testing.T, got string, options ...GoldenOption) { f(&opts) } if !filepath.IsAbs(opts.path) { - opts.path = filepath.Join(GoldenPath(t), opts.path) + opts.path = filepath.Join(Path(t), opts.path) } if update { @@ -77,20 +77,20 @@ func CheckOrUpdateGolden(t *testing.T, got string, options ...GoldenOption) { checkGoldenFileEqualsString(t, got, opts.path) } -// CheckOrUpdateGoldenYAML compares the provided object with the content of the golden file. If the update environment +// CheckOrUpdateYAML compares the provided object with the content of the golden file. If the update environment // variable is set, the golden file is updated with the provided object serialized as YAML. -func CheckOrUpdateGoldenYAML[E any](t *testing.T, got E, options ...GoldenOption) { +func CheckOrUpdateYAML[E any](t *testing.T, got E, options ...Option) { t.Helper() data, err := yaml.Marshal(got) require.NoError(t, err, "Cannot serialize provided object") - CheckOrUpdateGolden(t, string(data), options...) + CheckOrUpdate(t, string(data), options...) } -// LoadWithUpdateFromGolden loads the element from a plaintext golden file. +// LoadWithUpdate loads the element from a plaintext golden file. // It will update the file if the update flag is used prior to loading it. -func LoadWithUpdateFromGolden(t *testing.T, data string, options ...GoldenOption) string { +func LoadWithUpdate(t *testing.T, data string, options ...Option) string { t.Helper() opts := goldenOptions{} @@ -98,7 +98,7 @@ func LoadWithUpdateFromGolden(t *testing.T, data string, options ...GoldenOption f(&opts) } if !filepath.IsAbs(opts.path) { - opts.path = filepath.Join(GoldenPath(t), opts.path) + opts.path = filepath.Join(Path(t), opts.path) } if update { @@ -111,15 +111,15 @@ func LoadWithUpdateFromGolden(t *testing.T, data string, options ...GoldenOption return string(want) } -// LoadWithUpdateFromGoldenYAML load the generic element from a YAML serialized golden file. +// LoadWithUpdateYAML load the generic element from a YAML serialized golden file. // It will update the file if the update flag is used prior to deserializing it. -func LoadWithUpdateFromGoldenYAML[E any](t *testing.T, got E, options ...GoldenOption) E { +func LoadWithUpdateYAML[E any](t *testing.T, got E, options ...Option) E { t.Helper() t.Logf("Serializing object for golden file") data, err := yaml.Marshal(got) require.NoError(t, err, "Cannot serialize provided object") - want := LoadWithUpdateFromGolden(t, string(data), options...) + want := LoadWithUpdate(t, string(data), options...) var wantDeserialized E err = yaml.Unmarshal([]byte(want), &wantDeserialized) @@ -137,8 +137,8 @@ func CheckValidGoldenFileName(t *testing.T, name string) { "Invalid golden file name %q. Only alphanumeric characters, underscores, dashes, and dots are allowed", name) } -// GoldenPath returns the golden path for the provided test. -func GoldenPath(t *testing.T) string { +// Path returns the golden path for the provided test. +func Path(t *testing.T) string { t.Helper() cwd, err := os.Getwd() @@ -233,8 +233,8 @@ func checkGoldenFileEqualsString(t *testing.T, got, goldenPath string) { checkFileContent(t, got, string(goldenContent), "Actual", goldenPath) } -// CheckOrUpdateGoldenFileTree allows comparing a goldPath directory to p. Those can be updated via the dedicated flag. -func CheckOrUpdateGoldenFileTree(t *testing.T, path string, options ...GoldenOption) { +// CheckOrUpdateFileTree allows comparing a goldPath directory to p. Those can be updated via the dedicated flag. +func CheckOrUpdateFileTree(t *testing.T, path string, options ...Option) { t.Helper() opts := goldenOptions{} @@ -242,7 +242,7 @@ func CheckOrUpdateGoldenFileTree(t *testing.T, path string, options ...GoldenOpt f(&opts) } if !filepath.IsAbs(opts.path) { - opts.path = filepath.Join(GoldenPath(t), opts.path) + opts.path = filepath.Join(Path(t), opts.path) } if update { From 16e45fa78fdc1e0fa0d5d9bd4ca4eeacedf37dd3 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 19 Nov 2024 11:03:25 +0100 Subject: [PATCH 0256/1670] Fix unhandled error --- internal/testutils/golden/golden.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/testutils/golden/golden.go b/internal/testutils/golden/golden.go index 378096edf9..c95745b926 100644 --- a/internal/testutils/golden/golden.go +++ b/internal/testutils/golden/golden.go @@ -357,7 +357,9 @@ func addEmptyMarker(p string) error { if err != nil { return err } - f.Close() + if err = f.Close(); err != nil { + return err + } } return nil }) From d889a4fcd6aa84c018ae4a4236abfdae7d7d6562 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 19 Nov 2024 11:04:10 +0100 Subject: [PATCH 0257/1670] Remove import alias --- internal/testutils/golden/golden.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/testutils/golden/golden.go b/internal/testutils/golden/golden.go index c95745b926..53b6fc80af 100644 --- a/internal/testutils/golden/golden.go +++ b/internal/testutils/golden/golden.go @@ -13,7 +13,7 @@ import ( "strings" "testing" - cp "github.com/otiai10/copy" + "github.com/otiai10/copy" "github.com/pmezard/go-difflib/difflib" "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" @@ -267,7 +267,7 @@ func CheckOrUpdateFileTree(t *testing.T, path string, options ...Option) { err := addEmptyMarker(path) require.NoError(t, err, "Cannot add empty marker to directory %s", path) - err = cp.Copy(path, opts.path) + err = copy.Copy(path, opts.path) require.NoError(t, err, "Can’t update golden directory") } } From 6992fedd4b5872467d29f099eb4059e267e7e76b Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 19 Nov 2024 11:32:54 +0100 Subject: [PATCH 0258/1670] golden: Add t.Helper() to helper functions --- internal/testutils/golden/golden.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/testutils/golden/golden.go b/internal/testutils/golden/golden.go index 53b6fc80af..1d4ed602da 100644 --- a/internal/testutils/golden/golden.go +++ b/internal/testutils/golden/golden.go @@ -50,6 +50,8 @@ func WithPath(path string) Option { } func updateGoldenFile(t *testing.T, path string, data []byte) { + t.Helper() + t.Logf("updating golden file %s", path) err := os.MkdirAll(filepath.Dir(path), 0750) require.NoError(t, err, "Cannot create directory for updating golden files") @@ -175,6 +177,8 @@ func runDelta(diff string) (string, error) { // checkFileContent compares the content of the actual and golden files and reports any differences. func checkFileContent(t *testing.T, actual, expected, actualPath, expectedPath string) { + t.Helper() + if actual == expected { return } @@ -218,6 +222,8 @@ func checkFileContent(t *testing.T, actual, expected, actualPath, expectedPath s } func checkGoldenFileEqualsFile(t *testing.T, path, goldenPath string) { + t.Helper() + fileContent, err := os.ReadFile(path) require.NoError(t, err, "Cannot read file %s", path) goldenContent, err := os.ReadFile(goldenPath) @@ -227,6 +233,8 @@ func checkGoldenFileEqualsFile(t *testing.T, path, goldenPath string) { } func checkGoldenFileEqualsString(t *testing.T, got, goldenPath string) { + t.Helper() + goldenContent, err := os.ReadFile(goldenPath) require.NoError(t, err, "Cannot read golden file %s", goldenPath) From 85bf88276eec225061708715c43e09aaeaf681cf Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 19 Nov 2024 14:28:25 +0100 Subject: [PATCH 0259/1670] refactor: Rename goldenOptions to config Avoid duplicating the package name in the struct name but also avoid using the name "options" again which is already used as a parameter name and local variable name in this package. --- internal/testutils/golden/golden.go | 58 ++++++++++++++--------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/internal/testutils/golden/golden.go b/internal/testutils/golden/golden.go index 1d4ed602da..a9b1706d11 100644 --- a/internal/testutils/golden/golden.go +++ b/internal/testutils/golden/golden.go @@ -33,18 +33,18 @@ func init() { } } -type goldenOptions struct { +type config struct { path string } // Option is a supported option reference to change the golden files comparison. -type Option func(*goldenOptions) +type Option func(*config) // WithPath overrides the default path for golden files used. func WithPath(path string) Option { - return func(o *goldenOptions) { + return func(cfg *config) { if path != "" { - o.path = path + cfg.path = path } } } @@ -64,19 +64,19 @@ func updateGoldenFile(t *testing.T, path string, data []byte) { func CheckOrUpdate(t *testing.T, got string, options ...Option) { t.Helper() - opts := goldenOptions{} + cfg := config{} for _, f := range options { - f(&opts) + f(&cfg) } - if !filepath.IsAbs(opts.path) { - opts.path = filepath.Join(Path(t), opts.path) + if !filepath.IsAbs(cfg.path) { + cfg.path = filepath.Join(Path(t), cfg.path) } if update { - updateGoldenFile(t, opts.path, []byte(got)) + updateGoldenFile(t, cfg.path, []byte(got)) } - checkGoldenFileEqualsString(t, got, opts.path) + checkGoldenFileEqualsString(t, got, cfg.path) } // CheckOrUpdateYAML compares the provided object with the content of the golden file. If the update environment @@ -95,19 +95,19 @@ func CheckOrUpdateYAML[E any](t *testing.T, got E, options ...Option) { func LoadWithUpdate(t *testing.T, data string, options ...Option) string { t.Helper() - opts := goldenOptions{} + cfg := config{} for _, f := range options { - f(&opts) + f(&cfg) } - if !filepath.IsAbs(opts.path) { - opts.path = filepath.Join(Path(t), opts.path) + if !filepath.IsAbs(cfg.path) { + cfg.path = filepath.Join(Path(t), cfg.path) } if update { - updateGoldenFile(t, opts.path, []byte(data)) + updateGoldenFile(t, cfg.path, []byte(data)) } - want, err := os.ReadFile(opts.path) + want, err := os.ReadFile(cfg.path) require.NoError(t, err, "Cannot load golden file") return string(want) @@ -245,18 +245,18 @@ func checkGoldenFileEqualsString(t *testing.T, got, goldenPath string) { func CheckOrUpdateFileTree(t *testing.T, path string, options ...Option) { t.Helper() - opts := goldenOptions{} + cfg := config{} for _, f := range options { - f(&opts) + f(&cfg) } - if !filepath.IsAbs(opts.path) { - opts.path = filepath.Join(Path(t), opts.path) + if !filepath.IsAbs(cfg.path) { + cfg.path = filepath.Join(Path(t), cfg.path) } if update { - t.Logf("updating golden path %s", opts.path) - err := os.RemoveAll(opts.path) - require.NoError(t, err, "Cannot remove golden path %s", opts.path) + t.Logf("updating golden path %s", cfg.path) + err := os.RemoveAll(cfg.path) + require.NoError(t, err, "Cannot remove golden path %s", cfg.path) // check the source directory exists before trying to copy it info, err := os.Stat(path) @@ -269,13 +269,13 @@ func CheckOrUpdateFileTree(t *testing.T, path string, options ...Option) { // copy file data, err := os.ReadFile(path) require.NoError(t, err, "Cannot read file %s", path) - err = os.WriteFile(opts.path, data, info.Mode()) + err = os.WriteFile(cfg.path, data, info.Mode()) require.NoError(t, err, "Cannot write golden file") } else { err := addEmptyMarker(path) require.NoError(t, err, "Cannot add empty marker to directory %s", path) - err = copy.Copy(path, opts.path) + err = copy.Copy(path, cfg.path) require.NoError(t, err, "Can’t update golden directory") } } @@ -288,7 +288,7 @@ func CheckOrUpdateFileTree(t *testing.T, path string, options ...Option) { relPath, err := filepath.Rel(path, p) require.NoError(t, err, "Cannot get relative path for %s", p) - goldenFilePath := filepath.Join(opts.path, relPath) + goldenFilePath := filepath.Join(cfg.path, relPath) if de.IsDir() { return nil @@ -316,7 +316,7 @@ func CheckOrUpdateFileTree(t *testing.T, path string, options ...Option) { require.NoError(t, err, "Cannot walk through directory %s", path) // Check if there are files in the golden directory that are not in the source directory. - err = filepath.WalkDir(opts.path, func(p string, de fs.DirEntry, err error) error { + err = filepath.WalkDir(cfg.path, func(p string, de fs.DirEntry, err error) error { if err != nil { return err } @@ -326,7 +326,7 @@ func CheckOrUpdateFileTree(t *testing.T, path string, options ...Option) { return nil } - relPath, err := filepath.Rel(opts.path, p) + relPath, err := filepath.Rel(cfg.path, p) require.NoError(t, err, "Cannot get relative path for %s", p) filePath := filepath.Join(path, relPath) @@ -339,7 +339,7 @@ func CheckOrUpdateFileTree(t *testing.T, path string, options ...Option) { return nil }) - require.NoError(t, err, "Cannot walk through directory %s", opts.path) + require.NoError(t, err, "Cannot walk through directory %s", cfg.path) } const fileForEmptyDir = ".empty" From b4e2b20e192ea56b3c52004199569cd4826eaf4d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 08:20:13 +0000 Subject: [PATCH 0260/1670] deps(go): bump github.com/stretchr/testify from 1.9.0 to 1.10.0 Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.9.0 to 1.10.0. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.9.0...v1.10.0) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 730545a1b7..be2330c97c 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 golang.org/x/crypto v0.29.0 diff --git a/go.sum b/go.sum index a2c6ac3672..8639131f7b 100644 --- a/go.sum +++ b/go.sum @@ -113,8 +113,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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/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/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 h1:qO8m+4mLbo1HRpD5lfhEfr7R1PuqZvbAmjaRzYEy+tM= From 5b9076222d26127d90af55e5388f3554970fdc8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 08:13:40 +0000 Subject: [PATCH 0261/1670] deps(go-tools): bump github.com/golangci/golangci-lint in /tools Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.62.0 to 1.62.2. - [Release notes](https://github.com/golangci/golangci-lint/releases) - [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md) - [Commits](https://github.com/golangci/golangci-lint/compare/v1.62.0...v1.62.2) --- updated-dependencies: - dependency-name: github.com/golangci/golangci-lint dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- tools/go.mod | 18 +++++++++--------- tools/go.sum | 36 ++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index 81810c7483..d6e06d84dd 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -2,7 +2,7 @@ module github.com/ubuntu/authd-oidc-brokers/tools go 1.23.0 -require github.com/golangci/golangci-lint v1.62.0 +require github.com/golangci/golangci-lint v1.62.2 require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect @@ -11,9 +11,9 @@ require ( github.com/Abirdcfly/dupword v0.1.3 // indirect github.com/Antonboom/errname v1.0.0 // indirect github.com/Antonboom/nilnil v1.0.0 // indirect - github.com/Antonboom/testifylint v1.5.0 // indirect + github.com/Antonboom/testifylint v1.5.2 // indirect github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect - github.com/Crocmagnon/fatcontext v0.5.2 // indirect + github.com/Crocmagnon/fatcontext v0.5.3 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect @@ -103,19 +103,19 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mgechev/revive v1.5.0 // indirect + github.com/mgechev/revive v1.5.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moricho/tparallel v0.3.2 // indirect github.com/nakabonne/nestif v0.3.1 // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect - github.com/nunnatsa/ginkgolinter v0.18.0 // indirect + github.com/nunnatsa/ginkgolinter v0.18.3 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/polyfloyd/go-errorlint v1.6.0 // indirect + github.com/polyfloyd/go-errorlint v1.7.0 // indirect github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect @@ -150,7 +150,7 @@ require ( github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect github.com/stretchr/objx v0.5.2 // indirect - github.com/stretchr/testify v1.9.0 // indirect + github.com/stretchr/testify v1.10.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/tdakkota/asciicheck v0.2.0 // indirect github.com/tetafro/godot v1.4.18 // indirect @@ -161,7 +161,7 @@ require ( github.com/ultraware/funlen v0.1.0 // indirect github.com/ultraware/whitespace v0.1.1 // indirect github.com/uudashr/gocognit v1.1.3 // indirect - github.com/uudashr/iface v1.2.0 // indirect + github.com/uudashr/iface v1.2.1 // indirect github.com/xen0n/gosmopolitan v1.2.2 // indirect github.com/yagipy/maintidx v1.0.0 // indirect github.com/yeya24/promlinter v0.3.0 // indirect @@ -174,7 +174,7 @@ require ( go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect - golang.org/x/exp/typeparams v0.0.0-20240909161429-701f63a606c0 // indirect + golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f // indirect golang.org/x/mod v0.22.0 // indirect golang.org/x/sync v0.9.0 // indirect golang.org/x/sys v0.27.0 // indirect diff --git a/tools/go.sum b/tools/go.sum index c8205a75d7..20fd8682dc 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -43,14 +43,14 @@ github.com/Antonboom/errname v1.0.0 h1:oJOOWR07vS1kRusl6YRSlat7HFnb3mSfMl6sDMRoT github.com/Antonboom/errname v1.0.0/go.mod h1:gMOBFzK/vrTiXN9Oh+HFs+e6Ndl0eTFbtsRTSRdXyGI= github.com/Antonboom/nilnil v1.0.0 h1:n+v+B12dsE5tbAqRODXmEKfZv9j2KcTBrp+LkoM4HZk= github.com/Antonboom/nilnil v1.0.0/go.mod h1:fDJ1FSFoLN6yoG65ANb1WihItf6qt9PJVTn/s2IrcII= -github.com/Antonboom/testifylint v1.5.0 h1:dlUIsDMtCrZWUnvkaCz3quJCoIjaGi41GzjPBGkkJ8A= -github.com/Antonboom/testifylint v1.5.0/go.mod h1:wqaJbu0Blb5Wag2wv7Z5xt+CIV+eVLxtGZrlK13z3AE= +github.com/Antonboom/testifylint v1.5.2 h1:4s3Xhuv5AvdIgbd8wOOEeo0uZG7PbDKQyKY5lGoQazk= +github.com/Antonboom/testifylint v1.5.2/go.mod h1:vxy8VJ0bc6NavlYqjZfmp6EfqXMtBgQ4+mhCojwC1P8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Crocmagnon/fatcontext v0.5.2 h1:vhSEg8Gqng8awhPju2w7MKHqMlg4/NI+gSDHtR3xgwA= -github.com/Crocmagnon/fatcontext v0.5.2/go.mod h1:87XhRMaInHP44Q7Tlc7jkgKKB7kZAOPiDkFMdKCC+74= +github.com/Crocmagnon/fatcontext v0.5.3 h1:zCh/wjc9oyeF+Gmp+V60wetm8ph2tlsxocgg/J0hOps= +github.com/Crocmagnon/fatcontext v0.5.3/go.mod h1:XoCQYY1J+XTfyv74qLXvNw4xFunr3L1wkopIIKG7wGM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 h1:/fTUt5vmbkAcMBt4YQiuC23cV0kEsN1MVMNqeOW43cU= @@ -230,8 +230,8 @@ github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUP github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s= github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 h1:/1322Qns6BtQxUZDTAT4SdcoxknUki7IAoK4SAXr8ME= github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9/go.mod h1:Oesb/0uFAyWoaw1U1qS5zyjCg5NP9C9iwjnI4tIsXEE= -github.com/golangci/golangci-lint v1.62.0 h1:/G0g+bi1BhmGJqLdNQkKBWjcim8HjOPc4tsKuHDOhcI= -github.com/golangci/golangci-lint v1.62.0/go.mod h1:jtoOhQcKTz8B6dGNFyfQV3WZkQk+YvBDewDtNpiAJts= +github.com/golangci/golangci-lint v1.62.2 h1:b8K5K9PN+rZN1+mKLtsZHz2XXS9aYKzQ9i25x3Qnxxw= +github.com/golangci/golangci-lint v1.62.2/go.mod h1:ILWWyeFUrctpHVGMa1dg2xZPKoMUTc5OIMgW7HZr34g= github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= github.com/golangci/modinfo v0.3.4 h1:oU5huX3fbxqQXdfspamej74DFX0kyGLkw1ppvXoJ8GA= @@ -369,8 +369,8 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mgechev/revive v1.5.0 h1:oaSmjA7rP8+HyoRuCgC531VHwnLH1AlJdjj+1AnQceQ= -github.com/mgechev/revive v1.5.0/go.mod h1:L6T3H8EoerRO86c7WuGpvohIUmiploGiyoYbtIWFmV8= +github.com/mgechev/revive v1.5.1 h1:hE+QPeq0/wIzJwOphdVyUJ82njdd8Khp4fUIHGZHW3M= +github.com/mgechev/revive v1.5.1/go.mod h1:lC9AhkJIBs5zwx8wkudyHrU+IJkrEKmpCmGMnIJPk4o= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -390,8 +390,8 @@ github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhK github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/nunnatsa/ginkgolinter v0.18.0 h1:ZXO1wKhPg3A6LpbN5dMuqwhfOjN5c3ous8YdKOuqk9k= -github.com/nunnatsa/ginkgolinter v0.18.0/go.mod h1:vPrWafSULmjMGCMsfGA908if95VnHQNAahvSBOjTuWs= +github.com/nunnatsa/ginkgolinter v0.18.3 h1:WgS7X3zzmni3vwHSBhvSgqrRgUecN6PQUcfB0j1noDw= +github.com/nunnatsa/ginkgolinter v0.18.3/go.mod h1:BE1xyB/PNtXXG1azrvrqJW5eFH0hSRylNzFy8QHPwzs= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= @@ -415,8 +415,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polyfloyd/go-errorlint v1.6.0 h1:tftWV9DE7txiFzPpztTAwyoRLKNj9gpVm2cg8/OwcYY= -github.com/polyfloyd/go-errorlint v1.6.0/go.mod h1:HR7u8wuP1kb1NeN1zqTd1ZMlqUKPPHF+Id4vIPvDqVw= +github.com/polyfloyd/go-errorlint v1.7.0 h1:Zp6lzCK4hpBDj8y8a237YK4EPrMXQWvOe3nGoH4pFrU= +github.com/polyfloyd/go-errorlint v1.7.0/go.mod h1:dGWKu85mGHnegQ2SWpEybFityCg3j7ZbwsVUxAOk9gY= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -521,8 +521,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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/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/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tdakkota/asciicheck v0.2.0 h1:o8jvnUANo0qXtnslk2d3nMKTFNlOnJjRrNcj0j9qkHM= @@ -547,8 +547,8 @@ github.com/ultraware/whitespace v0.1.1 h1:bTPOGejYFulW3PkcrqkeQwOd6NKOOXvmGD9bo/ github.com/ultraware/whitespace v0.1.1/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= github.com/uudashr/gocognit v1.1.3 h1:l+a111VcDbKfynh+airAy/DJQKaXh2m9vkoysMPSZyM= github.com/uudashr/gocognit v1.1.3/go.mod h1:aKH8/e8xbTRBwjbCkwZ8qt4l2EpKXl31KMHgSS+lZ2U= -github.com/uudashr/iface v1.2.0 h1:ECJjh5q/1Zmnv/2yFpWV6H3oMg5+Mo+vL0aqw9Gjazo= -github.com/uudashr/iface v1.2.0/go.mod h1:Ux/7d/rAF3owK4m53cTVXL4YoVHKNqnoOeQHn2xrlp0= +github.com/uudashr/iface v1.2.1 h1:vHHyzAUmWZ64Olq6NZT3vg/z1Ws56kyPdBOd5kTXDF8= +github.com/uudashr/iface v1.2.1/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg= github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= @@ -609,8 +609,8 @@ golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWB golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20240909161429-701f63a606c0 h1:bVwtbF629Xlyxk6xLQq2TDYmqP0uiWaet5LwRebuY0k= -golang.org/x/exp/typeparams v0.0.0-20240909161429-701f63a606c0/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f h1:WTyX8eCCyfdqiPYkRGm0MqElSfYFH3yR1+rl/mct9sA= +golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= From 200b926f929d456af54763ac405786d941d39b0e Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 3 Dec 2024 13:20:33 +0100 Subject: [PATCH 0262/1670] Support drop-in config files We plan to use drop-in config files for the allowed_users and owner options (both for migration and generating the config file which sets the owner). --- internal/broker/config.go | 33 ++++++++++++++++++- internal/broker/config_test.go | 25 ++++++++++++++ .../config.txt | 5 +++ 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 internal/broker/testdata/TestParseConfig/golden/Successfully_parse_config_with_drop_in_files/config.txt diff --git a/internal/broker/config.go b/internal/broker/config.go index 772bf32e91..207c541a51 100644 --- a/internal/broker/config.go +++ b/internal/broker/config.go @@ -3,6 +3,9 @@ package broker import ( "errors" "fmt" + "io/fs" + "os" + "path/filepath" "strings" "gopkg.in/ini.v1" @@ -27,11 +30,39 @@ const ( sshSuffixesKey = "ssh_allowed_suffixes" ) +func getDropInFiles(cfgPath string) ([]any, error) { + // Check if a .d directory exists and return the paths to the files in it. + dropInDir := cfgPath + ".d" + files, err := os.ReadDir(dropInDir) + if errors.Is(err, fs.ErrNotExist) { + return nil, nil + } + if err != nil { + return nil, err + } + + var dropInFiles []any + // files is empty if the directory does not exist + for _, file := range files { + if file.IsDir() { + continue + } + dropInFiles = append(dropInFiles, filepath.Join(dropInDir, file.Name())) + } + + return dropInFiles, nil +} + // parseConfigFile parses the config file and returns a map with the configuration keys and values. func parseConfigFile(cfgPath string) (userConfig, error) { cfg := userConfig{} - iniCfg, err := ini.Load(cfgPath) + dropInFiles, err := getDropInFiles(cfgPath) + if err != nil { + return cfg, err + } + + iniCfg, err := ini.Load(cfgPath, dropInFiles...) if err != nil { return cfg, err } diff --git a/internal/broker/config_test.go b/internal/broker/config_test.go index bd02d62fe0..ab261e2a77 100644 --- a/internal/broker/config_test.go +++ b/internal/broker/config_test.go @@ -40,6 +40,17 @@ client_id = client_id = +`, + + "overwrite_lower_precedence": ` +[oidc] +issuer = https://lower-precedence-issuer.url.com +client_id = lower_precedence_client_id +`, + + "overwrite_higher_precedence": ` +[oidc] +issuer = https://higher-precedence-issuer.url.com `, } @@ -53,6 +64,7 @@ func TestParseConfig(t *testing.T) { }{ "Successfully_parse_config_file": {}, "Successfully_parse_config_file_with_optional_values": {configType: "valid+optional"}, + "Successfully_parse_config_with_drop_in_files": {configType: "overwritten-by-drop-in"}, "Do_not_fail_if_values_contain_a_single_template_delimiter": {configType: "singles"}, @@ -79,6 +91,19 @@ func TestParseConfig(t *testing.T) { case "unreadable": err = os.Chmod(confPath, 0000) require.NoError(t, err, "Setup: Failed to make config file unreadable") + case "overwritten-by-drop-in": + dropInDir := confPath + ".d" + err = os.Mkdir(dropInDir, 0700) + require.NoError(t, err, "Setup: Failed to create drop-in directory") + // Create multiple drop-in files to test that they are loaded in the correct order. + err = os.WriteFile(filepath.Join(dropInDir, "00-drop-in.conf"), []byte(configTypes["overwrite_lower_precedence"]), 0600) + require.NoError(t, err, "Setup: Failed to write drop-in file") + err = os.WriteFile(filepath.Join(dropInDir, "01-drop-in.conf"), []byte(configTypes["overwrite_higher_precedence"]), 0600) + require.NoError(t, err, "Setup: Failed to write drop-in file") + // Create the main config file, to test that the options which are not overwritten by the drop-in files + // are still present. + err = os.WriteFile(confPath, []byte(configTypes["valid+optional"]), 0600) + require.NoError(t, err, "Setup: Failed to write config file") } cfg, err := parseConfigFile(confPath) diff --git a/internal/broker/testdata/TestParseConfig/golden/Successfully_parse_config_with_drop_in_files/config.txt b/internal/broker/testdata/TestParseConfig/golden/Successfully_parse_config_with_drop_in_files/config.txt new file mode 100644 index 0000000000..7761f024d6 --- /dev/null +++ b/internal/broker/testdata/TestParseConfig/golden/Successfully_parse_config_with_drop_in_files/config.txt @@ -0,0 +1,5 @@ +clientID=lower_precedence_client_id +clientSecret= +issuerURL=https://higher-precedence-issuer.url.com +homeBaseDir=/home +allowedSSHSuffixes=[] \ No newline at end of file From de237eebaa8c01cf4cfa8b86d74729af9c199615 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 4 Dec 2024 13:19:15 +0100 Subject: [PATCH 0263/1670] Test error cases when parsing drop-in config dir --- internal/broker/config_test.go | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/internal/broker/config_test.go b/internal/broker/config_test.go index ab261e2a77..86ca1a5991 100644 --- a/internal/broker/config_test.go +++ b/internal/broker/config_test.go @@ -59,18 +59,21 @@ func TestParseConfig(t *testing.T) { tests := map[string]struct { configType string + dropInType string wantErr bool }{ "Successfully_parse_config_file": {}, "Successfully_parse_config_file_with_optional_values": {configType: "valid+optional"}, - "Successfully_parse_config_with_drop_in_files": {configType: "overwritten-by-drop-in"}, + "Successfully_parse_config_with_drop_in_files": {dropInType: "valid"}, "Do_not_fail_if_values_contain_a_single_template_delimiter": {configType: "singles"}, - "Error_if_file_does_not_exist": {configType: "inexistent", wantErr: true}, - "Error_if_file_is_unreadable": {configType: "unreadable", wantErr: true}, - "Error_if_file_is_not_updated": {configType: "template", wantErr: true}, + "Error_if_file_does_not_exist": {configType: "inexistent", wantErr: true}, + "Error_if_file_is_unreadable": {configType: "unreadable", wantErr: true}, + "Error_if_file_is_not_updated": {configType: "template", wantErr: true}, + "Error_if_drop_in_directory_is_unreadable": {dropInType: "unreadable-dir", wantErr: true}, + "Error_if_drop_in_file_is_unreadable": {dropInType: "unreadable-file", wantErr: true}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { @@ -91,10 +94,16 @@ func TestParseConfig(t *testing.T) { case "unreadable": err = os.Chmod(confPath, 0000) require.NoError(t, err, "Setup: Failed to make config file unreadable") - case "overwritten-by-drop-in": - dropInDir := confPath + ".d" + } + + dropInDir := confPath + ".d" + if tc.dropInType != "" { err = os.Mkdir(dropInDir, 0700) require.NoError(t, err, "Setup: Failed to create drop-in directory") + } + + switch tc.dropInType { + case "valid": // Create multiple drop-in files to test that they are loaded in the correct order. err = os.WriteFile(filepath.Join(dropInDir, "00-drop-in.conf"), []byte(configTypes["overwrite_lower_precedence"]), 0600) require.NoError(t, err, "Setup: Failed to write drop-in file") @@ -104,6 +113,12 @@ func TestParseConfig(t *testing.T) { // are still present. err = os.WriteFile(confPath, []byte(configTypes["valid+optional"]), 0600) require.NoError(t, err, "Setup: Failed to write config file") + case "unreadable-dir": + err = os.Chmod(dropInDir, 0000) + require.NoError(t, err, "Setup: Failed to make drop-in directory unreadable") + case "unreadable-file": + err = os.WriteFile(filepath.Join(dropInDir, "00-drop-in.conf"), []byte(configTypes["valid"]), 0000) + require.NoError(t, err, "Setup: Failed to make drop-in file unreadable") } cfg, err := parseConfigFile(confPath) From 053b83a63e28a3d70e478861dd9aa03d89d80ade Mon Sep 17 00:00:00 2001 From: Nikos Date: Mon, 25 Nov 2024 15:23:57 +0100 Subject: [PATCH 0264/1670] fix: fix client authn request parameter name --- internal/broker/broker.go | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 4c4d188432..f8a5d406ac 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -352,14 +352,20 @@ func (b *Broker) generateUILayout(session *session, authModeID string) (map[stri var authOpts []oauth2.AuthCodeOption - // workaround to cater for fully RFC compliant oauth2 server which require this - // extra option, public providers tend to have bespoke implementation for passing client - // credentials that completely bypass this - // full explanation in https://github.com/golang/oauth2/issues/320 + // Workaround to cater for RFC compliant oauth2 server. Public providers do not properly + // implement the RFC, (probably) because they assume that device clients are public. + // As described in https://datatracker.ietf.org/doc/html/rfc8628#section-3.1 + // device authentication requests must provide client authentication, similar to that for + // the token endpoint. + // The golang/oauth2 library does not implement this, see https://github.com/golang/oauth2/issues/685. + // We implement a workaround for implementing the client_secret_post client authn method. + // Supporting client_secret_basic would require us to patch the http client used by the + // oauth2 lib. + // Some providers support both of these authentication methods, some implement only one and + // some implement neither. + // TODO @shipperizer: client_authentication methods should be configurable if secret := session.oauth2Config.ClientSecret; secret != "" { - // TODO @shipperizer verificationMethod should be a configurable value - verificationMethod := "client_post" - authOpts = append(authOpts, oauth2.SetAuthURLParam(verificationMethod, secret)) + authOpts = append(authOpts, oauth2.SetAuthURLParam("client_secret", secret)) } response, err := session.oauth2Config.DeviceAuth(ctx, authOpts...) From 02a53c7a96df589de7977d98b611eb7466008ac7 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 5 Dec 2024 17:51:54 +0100 Subject: [PATCH 0265/1670] Mention which providers were tested with client_secret_post auth --- internal/broker/broker.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index f8a5d406ac..548b64cacb 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -363,6 +363,9 @@ func (b *Broker) generateUILayout(session *session, authModeID string) (map[stri // oauth2 lib. // Some providers support both of these authentication methods, some implement only one and // some implement neither. + // This was tested with the following providers: + // - Google: supports client_secret_post + // - Ory Hydra: supports client_secret_post // TODO @shipperizer: client_authentication methods should be configurable if secret := session.oauth2Config.ClientSecret; secret != "" { authOpts = append(authOpts, oauth2.SetAuthURLParam("client_secret", secret)) From 4442bc974e4afb51048c8006ee4723fe76ae4d2f Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 5 Dec 2024 18:02:00 +0100 Subject: [PATCH 0266/1670] Add missing golden file --- .../Successfully_parse_config_with_drop_in_files/config.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_with_drop_in_files/config.txt diff --git a/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_with_drop_in_files/config.txt b/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_with_drop_in_files/config.txt new file mode 100644 index 0000000000..7761f024d6 --- /dev/null +++ b/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_with_drop_in_files/config.txt @@ -0,0 +1,5 @@ +clientID=lower_precedence_client_id +clientSecret= +issuerURL=https://higher-precedence-issuer.url.com +homeBaseDir=/home +allowedSSHSuffixes=[] \ No newline at end of file From f8d1c30cf8c7af290c0c9f8e7e83b2c2141205c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 08:22:39 +0000 Subject: [PATCH 0267/1670] deps(go): bump golang.org/x/crypto from 0.29.0 to 0.30.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.29.0 to 0.30.0. - [Commits](https://github.com/golang/crypto/compare/v0.29.0...v0.30.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index be2330c97c..8ff9dbea37 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/stretchr/testify v1.10.0 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 - golang.org/x/crypto v0.29.0 + golang.org/x/crypto v0.30.0 golang.org/x/oauth2 v0.24.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 @@ -64,7 +64,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.9.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/text v0.20.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect ) diff --git a/go.sum b/go.sum index 8639131f7b..17ae7c9222 100644 --- a/go.sum +++ b/go.sum @@ -133,8 +133,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= +golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -147,8 +147,8 @@ golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -156,16 +156,16 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc 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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From 7d387de10162b70261ced0112626955d7a3b915a Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 10 Dec 2024 00:23:48 +0100 Subject: [PATCH 0268/1670] Fix nil pointer dereference (again) Commit c602d18a0a7f597430558f95f6308aefbb750855 unintentionally removed the nil check that was introduced by 64841d12cca8b77ddf1126e75952069e1c10c40b. --- internal/providers/msentraid/msentraid.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index c488d2f1cb..b4ce839677 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -195,6 +195,10 @@ func getAllUserGroups(client *msgraphsdk.GraphServiceClient) ([]msgraphmodels.Gr if err != nil { return nil, fmt.Errorf("failed to get user groups: %v", err) } + if result == nil { + slog.Debug("Got nil response from Microsoft Graph API for user's groups, assuming that user is not a member of any group.") + return []msgraphmodels.Groupable{}, nil + } groups := result.GetValue() From 43a0d28f86f2b8060ab97325fc40577b13af82a4 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Thu, 12 Dec 2024 12:04:00 +0100 Subject: [PATCH 0269/1670] Remove incorrect os.Exit(m.Run()) calls This is not needed since Go 1.15, where the test runtime registers the test result itself. Also, os.Exit() would skip any defer() being called before, which might pollute the system running the tests. --- internal/broker/broker_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 6ed1f64902..6648dd8333 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -1011,5 +1011,5 @@ func TestMain(m *testing.M) { defaultIssuerURL, cleanup = testutils.StartMockProviderServer("", nil) defer cleanup() - os.Exit(m.Run()) + m.Run() } From 0f0ab2c28bc39fd6b16d23390f6533e44ed425b4 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 12 Dec 2024 15:38:59 +0100 Subject: [PATCH 0270/1670] broker.conf: Use ## for explanatory comments And use a single # without a space after it for commented-out default options. --- conf/broker.conf | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/conf/broker.conf b/conf/broker.conf index 344aecbb05..2ab13eebaf 100644 --- a/conf/broker.conf +++ b/conf/broker.conf @@ -1,16 +1,16 @@ [oidc] issuer = https:// client_id = -# Client secret is needed for some specific auth flows depending on the provider. -# Only enable it if this is needed for your particular configuration. -# client_secret = +## Client secret is needed for some specific auth flows depending on the provider. +## Only enable it if this is needed for your particular configuration. +#client_secret = [users] -# The directory where the home directory will be created for new users. -# Existing users will keep their current directory. -# The user home directory will be created in the format of {home_base_dir}/{username} -# home_base_dir = /home +## The directory where the home directory will be created for new users. +## Existing users will keep their current directory. +## The user home directory will be created in the format of {home_base_dir}/{username} +#home_base_dir = /home -# The username suffixes that are allowed to login via ssh without existing previously in the system. -# The suffixes must be separated by commas. -# ssh_allowed_suffixes = @example.com,@anotherexample.com +## The username suffixes that are allowed to login via ssh without existing previously in the system. +## The suffixes must be separated by commas. +#ssh_allowed_suffixes = @example.com,@anotherexample.com From 7fb28f73c5512f1602e430fcd10151295f4d4f7d Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 12 Dec 2024 16:00:39 +0100 Subject: [PATCH 0271/1670] broker.conf: Rephrase comments and wrap at 72 chars --- conf/broker.conf | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/conf/broker.conf b/conf/broker.conf index 2ab13eebaf..889c43e12a 100644 --- a/conf/broker.conf +++ b/conf/broker.conf @@ -1,16 +1,17 @@ [oidc] issuer = https:// client_id = -## Client secret is needed for some specific auth flows depending on the provider. -## Only enable it if this is needed for your particular configuration. + +## Depending on the identity provider, you may need to provide a +## client secret to authenticate with the provider. #client_secret = [users] -## The directory where the home directory will be created for new users. -## Existing users will keep their current directory. -## The user home directory will be created in the format of {home_base_dir}/{username} +## The directory where the home directories of new users are created. +## Existing users will keep their current home directory. +## The home directories are created in the format / #home_base_dir = /home -## The username suffixes that are allowed to login via ssh without existing previously in the system. -## The suffixes must be separated by commas. +## If configured, only users with a suffix in this list are allowed to +## log in via SSH. The suffixes must be separated by comma. #ssh_allowed_suffixes = @example.com,@anotherexample.com From 575791571d4f912cbfaf001704a58dfeef639bac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 16:36:12 +0000 Subject: [PATCH 0272/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.48.0 to 1.54.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.48.0...v1.54.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 8ff9dbea37..0640ae7b04 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/ubuntu/authd-oidc-brokers go 1.23.0 require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 github.com/coreos/go-oidc/v3 v3.11.0 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/go-jose/go-jose/v4 v4.0.4 @@ -11,7 +11,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.48.0 + github.com/microsoftgraph/msgraph-sdk-go v1.54.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 github.com/otiai10/copy v1.14.0 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 @@ -40,7 +40,7 @@ require ( github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect - github.com/microsoft/kiota-abstractions-go v1.7.0 // indirect + github.com/microsoft/kiota-abstractions-go v1.8.1 // indirect github.com/microsoft/kiota-authentication-azure-go v1.1.0 // indirect github.com/microsoft/kiota-http-go v1.4.4 // indirect github.com/microsoft/kiota-serialization-form-go v1.0.0 // indirect @@ -56,14 +56,14 @@ require ( github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/std-uritemplate/std-uritemplate/go v0.0.57 // indirect + github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.1 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect - golang.org/x/net v0.27.0 // indirect + golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect diff --git a/go.sum b/go.sum index 17ae7c9222..6c9f1538c8 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= github.com/cjlapao/common-go v0.0.39 h1:bAAUrj2B9v0kMzbAOhzjSmiyDy+rd56r2sy7oEiQLlA= @@ -53,8 +53,8 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/microsoft/kiota-abstractions-go v1.7.0 h1:/0OKSSEe94Z1qgpcGE7ZFI9P+4iAnsDQo9v9UOk+R8E= -github.com/microsoft/kiota-abstractions-go v1.7.0/go.mod h1:FI1I2OHg0E7bK5t8DPnw+9C/CHVyLP6XeqDBT+95pTE= +github.com/microsoft/kiota-abstractions-go v1.8.1 h1:0gtK3KERmbKYm5AxJLZ8WPlNR9eACUGWuofFIa01PnA= +github.com/microsoft/kiota-abstractions-go v1.8.1/go.mod h1:YO2QCJyNM9wzvlgGLepw6s9XrPgNHODOYGVDCqQWdLI= github.com/microsoft/kiota-authentication-azure-go v1.1.0 h1:HudH57Enel9zFQ4TEaJw6lMiyZ5RbBdrRHwdU0NP2RY= github.com/microsoft/kiota-authentication-azure-go v1.1.0/go.mod h1:zfPFOiLdEqM77Hua5B/2vpcXrVaGqSWjHSRzlvAWEgc= github.com/microsoft/kiota-http-go v1.4.4 h1:HM0KT/Q7o+JsGatFkkbTIqJL24Jzo5eMI5NNe9N4TQ4= @@ -67,8 +67,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.0.0 h1:3O5sb5Zj+moLBiJy github.com/microsoft/kiota-serialization-multipart-go v1.0.0/go.mod h1:yauLeBTpANk4L03XD985akNysG24SnRJGaveZf+p4so= github.com/microsoft/kiota-serialization-text-go v1.0.0 h1:XOaRhAXy+g8ZVpcq7x7a0jlETWnWrEum0RhmbYrTFnA= github.com/microsoft/kiota-serialization-text-go v1.0.0/go.mod h1:sM1/C6ecnQ7IquQOGUrUldaO5wj+9+v7G2W3sQ3fy6M= -github.com/microsoftgraph/msgraph-sdk-go v1.48.0 h1:JYKXW90/rjMzbD9kIzD+PzoBBbiNh4dRW6riKg+Bm94= -github.com/microsoftgraph/msgraph-sdk-go v1.48.0/go.mod h1:ghZjDbiV52URFLjZGFZckbLqMW1IgWvQKW4AqLgOm5E= +github.com/microsoftgraph/msgraph-sdk-go v1.54.0 h1:IOaII4ZYKjkztrw3OVPTn0r/2R2DJKp6E1id753m3Yc= +github.com/microsoftgraph/msgraph-sdk-go v1.54.0/go.mod h1:6bLNNzPle87+8W0MHYKJV8v3lTm8S6UvePMvMbmBL6E= github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 h1:P1wpmn3xxfPMFJHg+PJPcusErfRkl63h6OdAnpDbkS8= github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1/go.mod h1:vFmWQGWyLlhxCESNLv61vlE4qesBU+eWmEVH7DJSESA= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -103,8 +103,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= -github.com/std-uritemplate/std-uritemplate/go v0.0.57 h1:GHGjptrsmazP4IVDlUprssiEf9ESVkbjx15xQXXzvq4= -github.com/std-uritemplate/std-uritemplate/go v0.0.57/go.mod h1:rG/bqh/ThY4xE5de7Rap3vaDkYUT76B0GPJ0loYeTTc= +github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.1 h1:/m2cTZHpqgofDsrwPqsASI6fSNMNhb+9EmUYtHEV2Uk= +github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.1/go.mod h1:Z5KcoM0YLC7INlNhEezeIZ0TZNYf7WSNO0Lvah4DSeQ= github.com/stretchr/objx v0.1.0/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= @@ -141,8 +141,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 3b4b5d0367fbbd69a95e48ce34bc7215128c87d1 Mon Sep 17 00:00:00 2001 From: Nikos Date: Fri, 6 Dec 2024 12:47:49 +0100 Subject: [PATCH 0273/1670] chore: update comment On the free version google device clients are public, meaning no client_secret is registered. --- internal/broker/broker.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 548b64cacb..2258dfe717 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -364,7 +364,6 @@ func (b *Broker) generateUILayout(session *session, authModeID string) (map[stri // Some providers support both of these authentication methods, some implement only one and // some implement neither. // This was tested with the following providers: - // - Google: supports client_secret_post // - Ory Hydra: supports client_secret_post // TODO @shipperizer: client_authentication methods should be configurable if secret := session.oauth2Config.ClientSecret; secret != "" { From 3fde5389af676c541e7ba5e40341b037255e1a5b Mon Sep 17 00:00:00 2001 From: Nikos Date: Tue, 10 Dec 2024 12:36:52 +0100 Subject: [PATCH 0274/1670] fix: create .d config file on start --- cmd/authd-oidc/daemon/daemon.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmd/authd-oidc/daemon/daemon.go b/cmd/authd-oidc/daemon/daemon.go index 42850374f3..100e596ebd 100644 --- a/cmd/authd-oidc/daemon/daemon.go +++ b/cmd/authd-oidc/daemon/daemon.go @@ -125,6 +125,12 @@ func (a *App) serve(config daemonConfig) error { } } + brokerConfigDir := broker.GetDropInDir(config.Paths.BrokerConf) + if err := ensureDirWithPerms(brokerConfigDir, 0700, os.Geteuid()); err != nil { + close(a.ready) + return fmt.Errorf("error initializing broker configuration directory %q: %v", brokerConfigDir, err) + } + b, err := broker.New(broker.Config{ ConfigFile: config.Paths.BrokerConf, DataDir: config.Paths.DataDir, From c18dd1d51a5c2a70c302d806f75cc919f5255d5d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 08:22:52 +0000 Subject: [PATCH 0275/1670] deps(go): bump golang.org/x/crypto from 0.30.0 to 0.31.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.30.0 to 0.31.0. - [Commits](https://github.com/golang/crypto/compare/v0.30.0...v0.31.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8ff9dbea37..ee6f7b658d 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/stretchr/testify v1.10.0 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 - golang.org/x/crypto v0.30.0 + golang.org/x/crypto v0.31.0 golang.org/x/oauth2 v0.24.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index 17ae7c9222..d633d12198 100644 --- a/go.sum +++ b/go.sum @@ -133,8 +133,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= -golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= From 6fc87bb1fd83c6ce8a68b8a19336aa0f2a27aa26 Mon Sep 17 00:00:00 2001 From: Nikos Date: Fri, 6 Dec 2024 13:48:11 +0100 Subject: [PATCH 0276/1670] fix: add new users configuration keys Add the "allowed_users" and "owner" keys to the users configuration section. --- conf/broker.conf | 28 ++++ internal/broker/broker.go | 16 +- internal/broker/config.go | 57 ++++++- internal/broker/config_test.go | 145 ++++++++++++++++++ .../config.txt | 5 + .../Successfully_parse_config_file/config.txt | 5 + .../config.txt | 5 + .../config.txt | 5 + .../All_are_allowed/broker.conf | 10 ++ .../All_are_allowed/broker.conf.d/.empty | 0 .../broker.conf | 9 ++ .../broker.conf.d/.empty | 0 .../Only_owner_is_allowed/broker.conf | 10 ++ .../broker.conf.d/.empty | 0 .../broker.conf | 9 ++ .../broker.conf.d/.empty | 0 .../broker.conf | 8 + .../broker.conf.d/.empty | 0 .../Set_owner_and_u1_is_allowed/broker.conf | 10 ++ .../broker.conf.d/.empty | 0 .../Unset_owner_and_u1_is_allowed/broker.conf | 9 ++ .../broker.conf.d/.empty | 0 .../Users_u1_and_u2_are_allowed/broker.conf | 9 ++ .../broker.conf.d/.empty | 0 24 files changed, 330 insertions(+), 10 deletions(-) create mode 100644 internal/broker/testdata/golden/TestParseUserConfig/All_are_allowed/broker.conf create mode 100644 internal/broker/testdata/golden/TestParseUserConfig/All_are_allowed/broker.conf.d/.empty create mode 100644 internal/broker/testdata/golden/TestParseUserConfig/By_default_only_owner_is_allowed/broker.conf create mode 100644 internal/broker/testdata/golden/TestParseUserConfig/By_default_only_owner_is_allowed/broker.conf.d/.empty create mode 100644 internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed/broker.conf create mode 100644 internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed/broker.conf.d/.empty create mode 100644 internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed_but_is_empty/broker.conf create mode 100644 internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed_but_is_empty/broker.conf.d/.empty create mode 100644 internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed_but_is_unset/broker.conf create mode 100644 internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed_but_is_unset/broker.conf.d/.empty create mode 100644 internal/broker/testdata/golden/TestParseUserConfig/Set_owner_and_u1_is_allowed/broker.conf create mode 100644 internal/broker/testdata/golden/TestParseUserConfig/Set_owner_and_u1_is_allowed/broker.conf.d/.empty create mode 100644 internal/broker/testdata/golden/TestParseUserConfig/Unset_owner_and_u1_is_allowed/broker.conf create mode 100644 internal/broker/testdata/golden/TestParseUserConfig/Unset_owner_and_u1_is_allowed/broker.conf.d/.empty create mode 100644 internal/broker/testdata/golden/TestParseUserConfig/Users_u1_and_u2_are_allowed/broker.conf create mode 100644 internal/broker/testdata/golden/TestParseUserConfig/Users_u1_and_u2_are_allowed/broker.conf.d/.empty diff --git a/conf/broker.conf b/conf/broker.conf index 889c43e12a..6118d1f3a9 100644 --- a/conf/broker.conf +++ b/conf/broker.conf @@ -15,3 +15,31 @@ client_id = ## If configured, only users with a suffix in this list are allowed to ## log in via SSH. The suffixes must be separated by comma. #ssh_allowed_suffixes = @example.com,@anotherexample.com + +## 'allowed_users' specifies the users who are permitted to log in after +## successfully authenticating with the Identity Provider. +## Values are separated by commas. Supported values: +## - 'OWNER': Grants access to the user specified in the 'owner' option +## (see below). This is the default. +## - 'ALL': Grants access to all users who successfully authenticate +## with the Identity Provider. +## - : Grants access to specific additional users +## (e.g. user1@example.com). +## Example: allowed_users = OWNER,user1@example.com,admin@example.com +#allowed_users = OWNER + +## 'owner' specifies the user assigned the owner role. This user is +## permitted to log in if 'OWNER' is included in the 'allowed_users' +## option. +## +## If this option is left unset, the first user to successfully log in +## via this broker will automatically be assigned the owner role. A +## drop-in configuration file will be created in broker.conf.d/ to set +## the 'owner' option. +## +## To disable automatic assignment, you can either: +## 1. Explicitly set this option to an empty value (e.g. owner = "") +## 2. Remove 'OWNER' from the 'allowed_users' option +## +## Example: owner = user2@example.com +#owner = diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 2258dfe717..2729d97ee1 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -46,11 +46,17 @@ type Config struct { } type userConfig struct { - clientID string - clientSecret string - issuerURL string - homeBaseDir string - allowedSSHSuffixes []string + clientID string + clientSecret string + issuerURL string + + allowedUsers map[string]struct{} + allUsersAllowed bool + ownerAllowed bool + firstUserBecomesOwner bool + owner string + homeBaseDir string + allowedSSHSuffixes []string } // Broker is the real implementation of the broker to track sessions and process oidc calls. diff --git a/internal/broker/config.go b/internal/broker/config.go index 207c541a51..c67a7a8b76 100644 --- a/internal/broker/config.go +++ b/internal/broker/config.go @@ -24,10 +24,19 @@ const ( // usersSection is the section name in the config file for the users and broker specific configuration. usersSection = "users" + // allowedUsersKey is the key in the config file for the users that are allowed to access the machine. + allowedUsersKey = "allowed_users" + // ownerKey is the key in the config file for the owner of the machine. + ownerKey = "owner" // homeDirKey is the key in the config file for the home directory prefix. homeDirKey = "home_base_dir" // SSHSuffixKey is the key in the config file for the SSH allowed suffixes. sshSuffixesKey = "ssh_allowed_suffixes" + + // allUsersKeyword is the keyword for the `allowed_users` key that allows access to all users. + allUsersKeyword = "ALL" + // ownerUserKeyword is the keyword for the `allowed_users` key that allows access to the owner. + ownerUserKeyword = "OWNER" ) func getDropInFiles(cfgPath string) ([]any, error) { @@ -53,6 +62,48 @@ func getDropInFiles(cfgPath string) ([]any, error) { return dropInFiles, nil } +func (uc *userConfig) populateUsersConfig(users *ini.Section) { + if users == nil { + // The default behavior is to allow only the owner + uc.ownerAllowed = true + uc.firstUserBecomesOwner = true + return + } + + uc.homeBaseDir = users.Key(homeDirKey).String() + uc.allowedSSHSuffixes = strings.Split(users.Key(sshSuffixesKey).String(), ",") + + if uc.allowedUsers == nil { + uc.allowedUsers = make(map[string]struct{}) + } + + allowedUsers := users.Key(allowedUsersKey).Strings(",") + if len(allowedUsers) == 0 { + allowedUsers = append(allowedUsers, ownerUserKeyword) + } + + for _, user := range allowedUsers { + if user == allUsersKeyword { + uc.allUsersAllowed = true + continue + } + if user == ownerUserKeyword { + uc.ownerAllowed = true + if !users.HasKey(ownerKey) { + // If owner is unset, then the first user becomes owner + uc.firstUserBecomesOwner = true + } + continue + } + + uc.allowedUsers[user] = struct{}{} + } + + // We need to read the owner key after we call HasKey, because the key is created + // when we call the "Key" function and we can't distinguish between empty and unset. + uc.owner = users.Key(ownerKey).String() +} + // parseConfigFile parses the config file and returns a map with the configuration keys and values. func parseConfigFile(cfgPath string) (userConfig, error) { cfg := userConfig{} @@ -86,11 +137,7 @@ func parseConfigFile(cfgPath string) (userConfig, error) { cfg.clientSecret = oidc.Key(clientSecret).String() } - users := iniCfg.Section(usersSection) - if users != nil { - cfg.homeBaseDir = users.Key(homeDirKey).String() - cfg.allowedSSHSuffixes = strings.Split(users.Key(sshSuffixesKey).String(), ",") - } + cfg.populateUsersConfig(iniCfg.Section(usersSection)) return cfg, nil } diff --git a/internal/broker/config_test.go b/internal/broker/config_test.go index 40d4dd5b18..d3881432a3 100644 --- a/internal/broker/config_test.go +++ b/internal/broker/config_test.go @@ -150,3 +150,148 @@ func TestParseConfig(t *testing.T) { }) } } + +var testParseUserConfigTypes = map[string]string{ + "All_are_allowed": ` +[oidc] +issuer = https://issuer.url.com +client_id = client_id + +[users] +allowed_users = ALL +owner = machine_owner +home_base_dir = /home +allowed_ssh_suffixes = @issuer.url.com +`, + "Only_owner_is_allowed": ` +[oidc] +issuer = https://issuer.url.com +client_id = client_id + +[users] +allowed_users = OWNER +owner = machine_owner +home_base_dir = /home +allowed_ssh_suffixes = @issuer.url.com +`, + "By_default_only_owner_is_allowed": ` +[oidc] +issuer = https://issuer.url.com +client_id = client_id + +[users] +owner = machine_owner +home_base_dir = /home +allowed_ssh_suffixes = @issuer.url.com +`, + "Only_owner_is_allowed_but_is_unset": ` +[oidc] +issuer = https://issuer.url.com +client_id = client_id + +[users] +home_base_dir = /home +allowed_ssh_suffixes = @issuer.url.com +`, + "Only_owner_is_allowed_but_is_empty": ` +[oidc] +issuer = https://issuer.url.com +client_id = client_id + +[users] +owner = +home_base_dir = /home +allowed_ssh_suffixes = @issuer.url.com +`, + "Users_u1_and_u2_are_allowed": ` +[oidc] +issuer = https://issuer.url.com +client_id = client_id + +[users] +allowed_users = u1,u2 +home_base_dir = /home +allowed_ssh_suffixes = @issuer.url.com +`, + "Unset_owner_and_u1_is_allowed": ` +[oidc] +issuer = https://issuer.url.com +client_id = client_id + +[users] +allowed_users = OWNER,u1 +home_base_dir = /home +allowed_ssh_suffixes = @issuer.url.com +`, + "Set_owner_and_u1_is_allowed": ` +[oidc] +issuer = https://issuer.url.com +client_id = client_id + +[users] +allowed_users = OWNER,u1 +owner = machine_owner +home_base_dir = /home +allowed_ssh_suffixes = @issuer.url.com +`, +} + +func TestParseUserConfig(t *testing.T) { + t.Parallel() + tests := map[string]struct { + wantAllUsersAllowed bool + wantOwnerAllowed bool + wantFirstUserBecomesOwner bool + wantOwner string + wantAllowedUsers []string + }{ + "All_are_allowed": {wantAllUsersAllowed: true, wantOwner: "machine_owner"}, + "Only_owner_is_allowed": {wantOwnerAllowed: true, wantOwner: "machine_owner"}, + "By_default_only_owner_is_allowed": {wantOwnerAllowed: true, wantOwner: "machine_owner"}, + "Only_owner_is_allowed_but_is_unset": {wantOwnerAllowed: true, wantFirstUserBecomesOwner: true}, + "Only_owner_is_allowed_but_is_empty": {wantOwnerAllowed: true}, + "Users_u1_and_u2_are_allowed": {wantAllowedUsers: []string{"u1", "u2"}}, + "Unset_owner_and_u1_is_allowed": { + wantOwnerAllowed: true, + wantFirstUserBecomesOwner: true, + wantAllowedUsers: []string{"u1"}, + }, + "Set_owner_and_u1_is_allowed": { + wantOwnerAllowed: true, + wantOwner: "machine_owner", + wantAllowedUsers: []string{"u1"}, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + outDir := t.TempDir() + confPath := filepath.Join(outDir, "broker.conf") + + err := os.WriteFile(confPath, []byte(testParseUserConfigTypes[name]), 0600) + require.NoError(t, err, "Setup: Failed to write config file") + + dropInDir := confPath + ".d" + err = os.Mkdir(dropInDir, 0700) + require.NoError(t, err, "Setup: Failed to create drop-in directory") + + cfg, err := parseConfigFile(confPath) + + // convert the allowed users array to a map + allowedUsersMap := map[string]struct{}{} + for _, k := range tc.wantAllowedUsers { + allowedUsersMap[k] = struct{}{} + } + + require.Equal(t, tc.wantAllUsersAllowed, cfg.allUsersAllowed) + require.Equal(t, tc.wantOwnerAllowed, cfg.ownerAllowed) + require.Equal(t, tc.wantOwner, cfg.owner) + require.Equal(t, tc.wantFirstUserBecomesOwner, cfg.firstUserBecomesOwner) + require.Equal(t, allowedUsersMap, cfg.allowedUsers) + + require.NoError(t, err) + golden.CheckOrUpdateFileTree(t, outDir) + }) + } +} diff --git a/internal/broker/testdata/golden/TestParseConfig/Do_not_fail_if_values_contain_a_single_template_delimiter/config.txt b/internal/broker/testdata/golden/TestParseConfig/Do_not_fail_if_values_contain_a_single_template_delimiter/config.txt index ac876a0035..8005e7b911 100644 --- a/internal/broker/testdata/golden/TestParseConfig/Do_not_fail_if_values_contain_a_single_template_delimiter/config.txt +++ b/internal/broker/testdata/golden/TestParseConfig/Do_not_fail_if_values_contain_a_single_template_delimiter/config.txt @@ -1,5 +1,10 @@ clientID= +allowedUsers=map[] +allUsersAllowed=false +ownerAllowed=true +firstUserBecomesOwner=true +owner= homeBaseDir= allowedSSHSuffixes=[] \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file/config.txt b/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file/config.txt index ba4609b6d2..78d109ab51 100644 --- a/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file/config.txt +++ b/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file/config.txt @@ -1,5 +1,10 @@ clientID=client_id clientSecret= issuerURL=https://issuer.url.com +allowedUsers=map[] +allUsersAllowed=false +ownerAllowed=true +firstUserBecomesOwner=true +owner= homeBaseDir= allowedSSHSuffixes=[] \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file_with_optional_values/config.txt b/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file_with_optional_values/config.txt index 6ad8bf0549..3066f32fc0 100644 --- a/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file_with_optional_values/config.txt +++ b/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file_with_optional_values/config.txt @@ -1,5 +1,10 @@ clientID=client_id clientSecret= issuerURL=https://issuer.url.com +allowedUsers=map[] +allUsersAllowed=false +ownerAllowed=true +firstUserBecomesOwner=true +owner= homeBaseDir=/home allowedSSHSuffixes=[] \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_with_drop_in_files/config.txt b/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_with_drop_in_files/config.txt index 7761f024d6..f651947fc8 100644 --- a/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_with_drop_in_files/config.txt +++ b/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_with_drop_in_files/config.txt @@ -1,5 +1,10 @@ clientID=lower_precedence_client_id clientSecret= issuerURL=https://higher-precedence-issuer.url.com +allowedUsers=map[] +allUsersAllowed=false +ownerAllowed=true +firstUserBecomesOwner=true +owner= homeBaseDir=/home allowedSSHSuffixes=[] \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestParseUserConfig/All_are_allowed/broker.conf b/internal/broker/testdata/golden/TestParseUserConfig/All_are_allowed/broker.conf new file mode 100644 index 0000000000..b777f7fc85 --- /dev/null +++ b/internal/broker/testdata/golden/TestParseUserConfig/All_are_allowed/broker.conf @@ -0,0 +1,10 @@ + +[oidc] +issuer = https://issuer.url.com +client_id = client_id + +[users] +allowed_users = ALL +owner = machine_owner +home_base_dir = /home +allowed_ssh_suffixes = @issuer.url.com diff --git a/internal/broker/testdata/golden/TestParseUserConfig/All_are_allowed/broker.conf.d/.empty b/internal/broker/testdata/golden/TestParseUserConfig/All_are_allowed/broker.conf.d/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/golden/TestParseUserConfig/By_default_only_owner_is_allowed/broker.conf b/internal/broker/testdata/golden/TestParseUserConfig/By_default_only_owner_is_allowed/broker.conf new file mode 100644 index 0000000000..46abf5a379 --- /dev/null +++ b/internal/broker/testdata/golden/TestParseUserConfig/By_default_only_owner_is_allowed/broker.conf @@ -0,0 +1,9 @@ + +[oidc] +issuer = https://issuer.url.com +client_id = client_id + +[users] +owner = machine_owner +home_base_dir = /home +allowed_ssh_suffixes = @issuer.url.com diff --git a/internal/broker/testdata/golden/TestParseUserConfig/By_default_only_owner_is_allowed/broker.conf.d/.empty b/internal/broker/testdata/golden/TestParseUserConfig/By_default_only_owner_is_allowed/broker.conf.d/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed/broker.conf b/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed/broker.conf new file mode 100644 index 0000000000..bcd7c04b05 --- /dev/null +++ b/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed/broker.conf @@ -0,0 +1,10 @@ + +[oidc] +issuer = https://issuer.url.com +client_id = client_id + +[users] +allowed_users = OWNER +owner = machine_owner +home_base_dir = /home +allowed_ssh_suffixes = @issuer.url.com diff --git a/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed/broker.conf.d/.empty b/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed/broker.conf.d/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed_but_is_empty/broker.conf b/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed_but_is_empty/broker.conf new file mode 100644 index 0000000000..8ad17837e3 --- /dev/null +++ b/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed_but_is_empty/broker.conf @@ -0,0 +1,9 @@ + +[oidc] +issuer = https://issuer.url.com +client_id = client_id + +[users] +owner = +home_base_dir = /home +allowed_ssh_suffixes = @issuer.url.com diff --git a/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed_but_is_empty/broker.conf.d/.empty b/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed_but_is_empty/broker.conf.d/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed_but_is_unset/broker.conf b/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed_but_is_unset/broker.conf new file mode 100644 index 0000000000..2f17914dda --- /dev/null +++ b/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed_but_is_unset/broker.conf @@ -0,0 +1,8 @@ + +[oidc] +issuer = https://issuer.url.com +client_id = client_id + +[users] +home_base_dir = /home +allowed_ssh_suffixes = @issuer.url.com diff --git a/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed_but_is_unset/broker.conf.d/.empty b/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed_but_is_unset/broker.conf.d/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/golden/TestParseUserConfig/Set_owner_and_u1_is_allowed/broker.conf b/internal/broker/testdata/golden/TestParseUserConfig/Set_owner_and_u1_is_allowed/broker.conf new file mode 100644 index 0000000000..050e023017 --- /dev/null +++ b/internal/broker/testdata/golden/TestParseUserConfig/Set_owner_and_u1_is_allowed/broker.conf @@ -0,0 +1,10 @@ + +[oidc] +issuer = https://issuer.url.com +client_id = client_id + +[users] +allowed_users = OWNER,u1 +owner = machine_owner +home_base_dir = /home +allowed_ssh_suffixes = @issuer.url.com diff --git a/internal/broker/testdata/golden/TestParseUserConfig/Set_owner_and_u1_is_allowed/broker.conf.d/.empty b/internal/broker/testdata/golden/TestParseUserConfig/Set_owner_and_u1_is_allowed/broker.conf.d/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/golden/TestParseUserConfig/Unset_owner_and_u1_is_allowed/broker.conf b/internal/broker/testdata/golden/TestParseUserConfig/Unset_owner_and_u1_is_allowed/broker.conf new file mode 100644 index 0000000000..890dcb9873 --- /dev/null +++ b/internal/broker/testdata/golden/TestParseUserConfig/Unset_owner_and_u1_is_allowed/broker.conf @@ -0,0 +1,9 @@ + +[oidc] +issuer = https://issuer.url.com +client_id = client_id + +[users] +allowed_users = OWNER,u1 +home_base_dir = /home +allowed_ssh_suffixes = @issuer.url.com diff --git a/internal/broker/testdata/golden/TestParseUserConfig/Unset_owner_and_u1_is_allowed/broker.conf.d/.empty b/internal/broker/testdata/golden/TestParseUserConfig/Unset_owner_and_u1_is_allowed/broker.conf.d/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/golden/TestParseUserConfig/Users_u1_and_u2_are_allowed/broker.conf b/internal/broker/testdata/golden/TestParseUserConfig/Users_u1_and_u2_are_allowed/broker.conf new file mode 100644 index 0000000000..6a2c1bc113 --- /dev/null +++ b/internal/broker/testdata/golden/TestParseUserConfig/Users_u1_and_u2_are_allowed/broker.conf @@ -0,0 +1,9 @@ + +[oidc] +issuer = https://issuer.url.com +client_id = client_id + +[users] +allowed_users = u1,u2 +home_base_dir = /home +allowed_ssh_suffixes = @issuer.url.com diff --git a/internal/broker/testdata/golden/TestParseUserConfig/Users_u1_and_u2_are_allowed/broker.conf.d/.empty b/internal/broker/testdata/golden/TestParseUserConfig/Users_u1_and_u2_are_allowed/broker.conf.d/.empty new file mode 100644 index 0000000000..e69de29bb2 From e13b4d6c9f31f8471b1b1c146379040fb2fb5596 Mon Sep 17 00:00:00 2001 From: Nikos Date: Fri, 6 Dec 2024 16:39:28 +0100 Subject: [PATCH 0277/1670] fix: add owner autoregistration logic If OWNER is in the `allowed_users` list and no owner is registered, a configuration file with the first user to login is generated. --- internal/broker/broker.go | 14 ---- internal/broker/config.go | 73 ++++++++++++++++++- internal/broker/config_test.go | 30 +++++++- .../20-owner-autoregistration.conf.tmpl | 13 ++++ .../golden/TestRegisterOwner/broker.conf | 4 + .../20-owner-autoregistration.conf | 13 ++++ 6 files changed, 130 insertions(+), 17 deletions(-) create mode 100644 internal/broker/templates/20-owner-autoregistration.conf.tmpl create mode 100644 internal/broker/testdata/golden/TestRegisterOwner/broker.conf create mode 100644 internal/broker/testdata/golden/TestRegisterOwner/broker.conf.d/20-owner-autoregistration.conf diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 2729d97ee1..c617e4f7b7 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -45,20 +45,6 @@ type Config struct { userConfig } -type userConfig struct { - clientID string - clientSecret string - issuerURL string - - allowedUsers map[string]struct{} - allUsersAllowed bool - ownerAllowed bool - firstUserBecomesOwner bool - owner string - homeBaseDir string - allowedSSHSuffixes []string -} - // Broker is the real implementation of the broker to track sessions and process oidc calls. type Broker struct { cfg Config diff --git a/internal/broker/config.go b/internal/broker/config.go index c67a7a8b76..c934d7210b 100644 --- a/internal/broker/config.go +++ b/internal/broker/config.go @@ -1,8 +1,10 @@ package broker import ( + "embed" "errors" "fmt" + "html/template" "io/fs" "os" "path/filepath" @@ -37,11 +39,43 @@ const ( allUsersKeyword = "ALL" // ownerUserKeyword is the keyword for the `allowed_users` key that allows access to the owner. ownerUserKeyword = "OWNER" + + // ownerAutoRegistrationConfigPath is the name of the file that will be auto-generated to register the owner. + ownerAutoRegistrationConfigPath = "20-owner-autoregistration.conf" + ownerAutoRegistrationConfigTemplate = "templates/20-owner-autoregistration.conf.tmpl" +) + +var ( + //go:embed templates/20-owner-autoregistration.conf.tmpl + ownerAutoRegistrationConfig embed.FS ) +type templateEnv struct { + Owner string +} + +type userConfig struct { + clientID string + clientSecret string + issuerURL string + + allowedUsers map[string]struct{} + allUsersAllowed bool + ownerAllowed bool + firstUserBecomesOwner bool + owner string + homeBaseDir string + allowedSSHSuffixes []string +} + +// GetDropInDir takes the broker configuration path and returns the drop in dir path. +func GetDropInDir(cfgPath string) string { + return cfgPath + ".d" +} + func getDropInFiles(cfgPath string) ([]any, error) { // Check if a .d directory exists and return the paths to the files in it. - dropInDir := cfgPath + ".d" + dropInDir := GetDropInDir(cfgPath) files, err := os.ReadDir(dropInDir) if errors.Is(err, fs.ErrNotExist) { return nil, nil @@ -141,3 +175,40 @@ func parseConfigFile(cfgPath string) (userConfig, error) { return cfg, nil } + +func (uc *userConfig) shouldRegisterOwner() bool { + return uc.ownerAllowed && uc.firstUserBecomesOwner && uc.owner == "" +} + +func (uc *userConfig) registerOwner(cfgPath, userName string) error { + if cfgPath == "" { + uc.owner = userName + uc.firstUserBecomesOwner = false + return nil + } + + p := filepath.Join(GetDropInDir(cfgPath), ownerAutoRegistrationConfigPath) + + templateName := filepath.Base(ownerAutoRegistrationConfigTemplate) + t, err := template.New(templateName).ParseFS(ownerAutoRegistrationConfig, ownerAutoRegistrationConfigTemplate) + if err != nil { + return fmt.Errorf("failed to open autoregistration template: %v", err) + } + + f, err := os.OpenFile(p, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return fmt.Errorf("failed to create owner registration file: %v", err) + } + defer f.Close() + + if err := t.Execute(f, templateEnv{Owner: userName}); err != nil { + return fmt.Errorf("failed to write owner registration file: %v", err) + } + + // We set the owner after we create the autoregistration file, so that in case of an error + // the owner is not updated. + uc.owner = userName + uc.firstUserBecomesOwner = false + + return nil +} diff --git a/internal/broker/config_test.go b/internal/broker/config_test.go index d3881432a3..2f25a9a843 100644 --- a/internal/broker/config_test.go +++ b/internal/broker/config_test.go @@ -96,7 +96,7 @@ func TestParseConfig(t *testing.T) { require.NoError(t, err, "Setup: Failed to make config file unreadable") } - dropInDir := confPath + ".d" + dropInDir := GetDropInDir(confPath) if tc.dropInType != "" { err = os.Mkdir(dropInDir, 0700) require.NoError(t, err, "Setup: Failed to create drop-in directory") @@ -272,7 +272,7 @@ func TestParseUserConfig(t *testing.T) { err := os.WriteFile(confPath, []byte(testParseUserConfigTypes[name]), 0600) require.NoError(t, err, "Setup: Failed to write config file") - dropInDir := confPath + ".d" + dropInDir := GetDropInDir(confPath) err = os.Mkdir(dropInDir, 0700) require.NoError(t, err, "Setup: Failed to create drop-in directory") @@ -295,3 +295,29 @@ func TestParseUserConfig(t *testing.T) { }) } } + +func TestRegisterOwner(t *testing.T) { + outDir := t.TempDir() + userName := "owner_name" + confPath := filepath.Join(outDir, "broker.conf") + + err := os.WriteFile(confPath, []byte(configTypes["valid"]), 0600) + require.NoError(t, err, "Setup: Failed to write config file") + + dropInDir := GetDropInDir(confPath) + err = os.Mkdir(dropInDir, 0700) + require.NoError(t, err, "Setup: Failed to create drop-in directory") + + cfg := userConfig{firstUserBecomesOwner: true, ownerAllowed: true} + err = cfg.registerOwner(confPath, userName) + require.NoError(t, err) + + require.Equal(t, cfg.owner, userName) + require.Equal(t, cfg.firstUserBecomesOwner, false) + + f, err := os.Open(filepath.Join(dropInDir, "20-owner-autoregistration.conf")) + require.NoError(t, err, "failed to open 20-owner-autoregistration.conf") + defer f.Close() + + golden.CheckOrUpdateFileTree(t, outDir) +} diff --git a/internal/broker/templates/20-owner-autoregistration.conf.tmpl b/internal/broker/templates/20-owner-autoregistration.conf.tmpl new file mode 100644 index 0000000000..4673fe2c2b --- /dev/null +++ b/internal/broker/templates/20-owner-autoregistration.conf.tmpl @@ -0,0 +1,13 @@ +## This file was generated automatically by the broker. DO NOT EDIT. +## +## This file registers the first authenticated user as the owner of +## this device. +## +## The 'owner' option is only considered for authentication if +## 'allowed_users' contains the 'OWNER' keyword. +## +## To register a different owner for the machine on the next +## successful authentication, delete this file. + +[users] +owner = {{ .Owner }} diff --git a/internal/broker/testdata/golden/TestRegisterOwner/broker.conf b/internal/broker/testdata/golden/TestRegisterOwner/broker.conf new file mode 100644 index 0000000000..aa6cfd3e38 --- /dev/null +++ b/internal/broker/testdata/golden/TestRegisterOwner/broker.conf @@ -0,0 +1,4 @@ + +[oidc] +issuer = https://issuer.url.com +client_id = client_id diff --git a/internal/broker/testdata/golden/TestRegisterOwner/broker.conf.d/20-owner-autoregistration.conf b/internal/broker/testdata/golden/TestRegisterOwner/broker.conf.d/20-owner-autoregistration.conf new file mode 100644 index 0000000000..f6c5a43bef --- /dev/null +++ b/internal/broker/testdata/golden/TestRegisterOwner/broker.conf.d/20-owner-autoregistration.conf @@ -0,0 +1,13 @@ +## This file was generated automatically by the broker. DO NOT EDIT. +## +## This file registers the first authenticated user as the owner of +## this device. +## +## The 'owner' option is only considered for authentication if +## 'allowed_users' contains the 'OWNER' keyword. +## +## To register a different owner for the machine on the next +## successful authentication, delete this file. + +[users] +owner = owner_name From b8947e03da99936381fcf284db7248bc890156ef Mon Sep 17 00:00:00 2001 From: Nikos Date: Fri, 6 Dec 2024 14:23:39 +0100 Subject: [PATCH 0278/1670] fix: check if access is allowed for user Add the logic for filtering users based on the allowed_users list. A user is allowed to login if - ALL users are allowed - the user's name is in the list of allowed_users - OWNER is in the allowed_users and the user is the owner of the machine --- internal/broker/broker.go | 34 ++++ internal/broker/broker_test.go | 155 ++++++++++++++++-- internal/broker/export_test.go | 20 +++ internal/broker/helper_test.go | 26 ++- .../provider_url/user1@example.com/password | 1 + .../provider_url/user1@example.com/token.json | 1 + .../provider_url/user2@example.com/password | 1 + .../provider_url/user2@example.com/token.json | 1 + .../first_auth | 3 + .../second_auth | 3 + 10 files changed, 232 insertions(+), 13 deletions(-) create mode 100644 internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/data/provider_url/user1@example.com/password create mode 100644 internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/data/provider_url/user1@example.com/token.json create mode 100644 internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/data/provider_url/user2@example.com/password create mode 100644 internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/data/provider_url/user2@example.com/token.json create mode 100644 internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/first_auth create mode 100644 internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/second_auth diff --git a/internal/broker/broker.go b/internal/broker/broker.go index c617e4f7b7..65556f092d 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -611,6 +611,21 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au } } + if !b.userNameIsAllowed(authInfo.UserInfo.Name) { + return AuthDenied, errorMessage{Message: "permission denied"} + } + + // If the owner is unset and allowed, we auto-generate a config file with the first + // user to log in as the owner. + if b.cfg.userConfig.shouldRegisterOwner() { + if err := b.cfg.registerOwner(b.cfg.ConfigFile, authInfo.UserInfo.Name); err != nil { + // The user is not allowed, if we fail to create the owner-autoregistration file. + // Otherwise the owner might change if the broker is restarted. + slog.Error(fmt.Sprintf("Failed to assign the owner role: %v", err)) + return AuthDenied, errorMessage{Message: "could not register the owner"} + } + } + if session.isOffline { return AuthGranted, userInfoMessage{UserInfo: authInfo.UserInfo} } @@ -627,6 +642,25 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au return AuthGranted, userInfoMessage{UserInfo: authInfo.UserInfo} } +// userNameIsAllowed checks whether the user's username is allowed to access the machine. +func (b *Broker) userNameIsAllowed(userName string) bool { + // The user is allowed to log in if: + // - ALL users are allowed + // - the user's name is in the list of allowed_users + // - the user is the owner of the machine and OWNER is in the allowed_users list + if b.cfg.userConfig.allUsersAllowed { + return true + } + if _, ok := b.cfg.userConfig.allowedUsers[userName]; ok { + return true + } + if !b.cfg.userConfig.ownerAllowed { + return false + } + // If owner is undefined, then the first user to log in is considered the owner + return b.cfg.userConfig.firstUserBecomesOwner || b.cfg.userConfig.owner == userName +} + func (b *Broker) startAuthenticate(sessionID string) (context.Context, error) { session, err := b.getSession(sessionID) if err != nil { diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 6648dd8333..a52cae9f6f 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "path/filepath" + "slices" "testing" "time" @@ -536,8 +537,10 @@ func TestIsAuthenticated(t *testing.T) { require.NoError(t, err, "Setup: Mkdir should not have returned an error") cfg := &brokerForTestConfig{ - Config: broker.Config{DataDir: dataDir}, - getUserInfoFails: tc.getUserInfoFails, + Config: broker.Config{DataDir: dataDir}, + getUserInfoFails: tc.getUserInfoFails, + ownerAllowed: true, + firstUserBecomesOwner: true, } if tc.customHandlers == nil { // Use the default provider URL if no custom handlers are provided. @@ -707,14 +710,36 @@ func TestIsAuthenticated(t *testing.T) { // Due to ordering restrictions, this test can not be run in parallel, otherwise the routines would not be ordered as expected. func TestConcurrentIsAuthenticated(t *testing.T) { tests := map[string]struct { - firstCallDelay int - secondCallDelay int + firstCallDelay int + secondCallDelay int + ownerAllowed bool + allUsersAllowed bool + firstUserBecomesOwner bool timeBetween time.Duration }{ - "First_auth_starts_and_finishes_before_second": {secondCallDelay: 1, timeBetween: 2 * time.Second}, - "First_auth_starts_first_but_second_finishes_first": {firstCallDelay: 3, timeBetween: time.Second}, - "First_auth_starts_first_then_second_starts_and_first_finishes": {firstCallDelay: 2, secondCallDelay: 3, timeBetween: time.Second}, + "First_auth_starts_and_finishes_before_second": { + secondCallDelay: 1, + timeBetween: 2 * time.Second, + allUsersAllowed: true, + }, + "First_auth_starts_first_but_second_finishes_first": { + firstCallDelay: 3, + timeBetween: time.Second, + allUsersAllowed: true, + }, + "First_auth_starts_first_then_second_starts_and_first_finishes": { + firstCallDelay: 2, + secondCallDelay: 3, + timeBetween: time.Second, + allUsersAllowed: true, + }, + "First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner": { + firstCallDelay: 3, + timeBetween: time.Second, + ownerAllowed: true, + firstUserBecomesOwner: true, + }, } for name, tc := range tests { @@ -728,9 +753,12 @@ func TestConcurrentIsAuthenticated(t *testing.T) { username2 := "user2@example.com" b := newBrokerForTests(t, &brokerForTestConfig{ - Config: broker.Config{DataDir: dataDir}, - firstCallDelay: tc.firstCallDelay, - secondCallDelay: tc.secondCallDelay, + Config: broker.Config{DataDir: dataDir}, + allUsersAllowed: tc.allUsersAllowed, + ownerAllowed: tc.ownerAllowed, + firstUserBecomesOwner: tc.firstUserBecomesOwner, + firstCallDelay: tc.firstCallDelay, + secondCallDelay: tc.secondCallDelay, tokenHandlerOptions: &testutils.TokenHandlerOptions{ IDTokenClaims: []map[string]interface{}{ {"sub": "user1", "name": "user1", "email": username1}, @@ -827,6 +855,113 @@ func TestConcurrentIsAuthenticated(t *testing.T) { } } +func TestIsAuthenticatedAllowedUsersConfig(t *testing.T) { + t.Parallel() + tests := map[string]struct { + allowedUsers map[string]struct{} + owner string + ownerAllowed bool + allUsersAllowed bool + firstUserBecomesOwner bool + + allowedUsernames []string + unallowedUsernames []string + }{ + "All_users_are_allowed": { + allUsersAllowed: true, + allowedUsernames: []string{"u1", "u2"}, + }, + "Only_owner_allowed": { + ownerAllowed: true, + owner: "machine_owner", + allowedUsernames: []string{"machine_owner"}, + unallowedUsernames: []string{"u1", "u2"}, + }, + "No_users_allowed": { + unallowedUsernames: []string{"u1", "u2", "machine_owner", "u3"}, + }, + "No_users_allowed_when_owner_is_allowed_but_not_set": { + ownerAllowed: true, + unallowedUsernames: []string{"u1", "u2", "machine_owner", "u3"}, + }, + "Only_first_user_allowed": { + ownerAllowed: true, + firstUserBecomesOwner: true, + allowedUsernames: []string{"random_user"}, + unallowedUsernames: []string{"u1", "u2"}, + }, + "Specific_users_allowed": { + allowedUsers: map[string]struct{}{"u1": {}, "u2": {}}, + allowedUsernames: []string{"u1", "u2"}, + unallowedUsernames: []string{"u3"}, + }, + "Specific_users_and_owner": { + allowedUsers: map[string]struct{}{"u1": {}, "u2": {}}, + ownerAllowed: true, + owner: "machine_owner", + allowedUsernames: []string{"u1", "u2", "machine_owner"}, + unallowedUsernames: []string{"u3"}, + }, + "Owner_is_disabled_even_when_registered": { + allowedUsers: map[string]struct{}{"u1": {}, "u2": {}}, + owner: "machine_owner", + allowedUsernames: []string{"u1", "u2"}, + unallowedUsernames: []string{"machine_owner", "u3"}, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + outDir := t.TempDir() + dataDir := filepath.Join(outDir, "data") + err := os.Mkdir(dataDir, 0700) + require.NoError(t, err, "Setup: Mkdir should not have returned an error") + + idTokenClaims := []map[string]interface{}{} + for _, uname := range tc.allowedUsernames { + idTokenClaims = append(idTokenClaims, map[string]interface{}{"sub": "user", "name": "user", "email": uname}) + } + for _, uname := range tc.unallowedUsernames { + idTokenClaims = append(idTokenClaims, map[string]interface{}{"sub": "user", "name": "user", "email": uname}) + } + + b := newBrokerForTests(t, &brokerForTestConfig{ + Config: broker.Config{DataDir: dataDir}, + allowedUsers: tc.allowedUsers, + owner: tc.owner, + ownerAllowed: tc.ownerAllowed, + allUsersAllowed: tc.allUsersAllowed, + firstUserBecomesOwner: tc.firstUserBecomesOwner, + tokenHandlerOptions: &testutils.TokenHandlerOptions{ + IDTokenClaims: idTokenClaims, + }, + }) + + for _, u := range append(tc.allowedUsernames, tc.unallowedUsernames...) { + sessionID, key := newSessionForTests(t, b, u, "") + token := tokenOptions{username: u} + generateAndStoreCachedInfo(t, token, b.TokenPathForSession(sessionID)) + err = password.HashAndStorePassword("password", b.PasswordFilepathForSession(sessionID)) + require.NoError(t, err, "Setup: HashAndStorePassword should not have returned an error") + + updateAuthModes(t, b, sessionID, authmodes.Password) + + authData := `{"challenge":"` + encryptChallenge(t, "password", key) + `"}` + + access, data, err := b.IsAuthenticated(sessionID, authData) + require.True(t, json.Valid([]byte(data)), "IsAuthenticated returned data must be a valid JSON") + require.NoError(t, err) + if slices.Contains(tc.allowedUsernames, u) { + require.Equal(t, access, broker.AuthGranted, "authentication failed") + } else { + require.Equal(t, access, broker.AuthDenied, "authentication failed") + } + } + }) + } +} + func TestFetchUserInfo(t *testing.T) { t.Parallel() diff --git a/internal/broker/export_test.go b/internal/broker/export_test.go index 6bfc49a24d..0d9a092e6e 100644 --- a/internal/broker/export_test.go +++ b/internal/broker/export_test.go @@ -19,6 +19,26 @@ func (cfg *Config) SetHomeBaseDir(homeBaseDir string) { cfg.homeBaseDir = homeBaseDir } +func (cfg *Config) SetAllowedUsers(allowedUsers map[string]struct{}) { + cfg.allowedUsers = allowedUsers +} + +func (cfg *Config) SetOwner(owner string) { + cfg.owner = owner +} + +func (cfg *Config) SetFirstUserBecomesOwner(firstUserBecomesOwner bool) { + cfg.firstUserBecomesOwner = firstUserBecomesOwner +} + +func (cfg *Config) SetAllUsersAllowed(allUsersAllowed bool) { + cfg.allUsersAllowed = allUsersAllowed +} + +func (cfg *Config) SetOwnerAllowed(ownerAllowed bool) { + cfg.ownerAllowed = ownerAllowed +} + func (cfg *Config) SetAllowedSSHSuffixes(allowedSSHSuffixes []string) { cfg.allowedSSHSuffixes = allowedSSHSuffixes } diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index 1810074ab0..119c5b0882 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -22,9 +22,14 @@ import ( type brokerForTestConfig struct { broker.Config - issuerURL string - homeBaseDir string - allowedSSHSuffixes []string + issuerURL string + allowedUsers map[string]struct{} + allUsersAllowed bool + ownerAllowed bool + firstUserBecomesOwner bool + owner string + homeBaseDir string + allowedSSHSuffixes []string getUserInfoFails bool firstCallDelay int @@ -49,6 +54,21 @@ func newBrokerForTests(t *testing.T, cfg *brokerForTestConfig) (b *broker.Broker if cfg.allowedSSHSuffixes != nil { cfg.SetAllowedSSHSuffixes(cfg.allowedSSHSuffixes) } + if cfg.allowedUsers != nil { + cfg.SetAllowedUsers(cfg.allowedUsers) + } + if cfg.owner != "" { + cfg.SetOwner(cfg.owner) + } + if cfg.firstUserBecomesOwner != false { + cfg.SetFirstUserBecomesOwner(cfg.firstUserBecomesOwner) + } + if cfg.allUsersAllowed != false { + cfg.SetAllUsersAllowed(cfg.allUsersAllowed) + } + if cfg.ownerAllowed != false { + cfg.SetOwnerAllowed(cfg.ownerAllowed) + } provider := &testutils.MockProvider{ GetUserInfoFails: cfg.getUserInfoFails, diff --git a/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/data/provider_url/user1@example.com/password b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/data/provider_url/user1@example.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/data/provider_url/user1@example.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/data/provider_url/user1@example.com/token.json b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/data/provider_url/user1@example.com/token.json new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/data/provider_url/user1@example.com/token.json @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/data/provider_url/user2@example.com/password b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/data/provider_url/user2@example.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/data/provider_url/user2@example.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/data/provider_url/user2@example.com/token.json b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/data/provider_url/user2@example.com/token.json new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/data/provider_url/user2@example.com/token.json @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/first_auth b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/first_auth new file mode 100644 index 0000000000..ffd251e4ae --- /dev/null +++ b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/first_auth @@ -0,0 +1,3 @@ +access: denied +data: '{"message":"authentication failure: permission denied"}' +err: diff --git a/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/second_auth b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/second_auth new file mode 100644 index 0000000000..f0643dd9fe --- /dev/null +++ b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/second_auth @@ -0,0 +1,3 @@ +access: granted +data: '{"userinfo":{"name":"user2@example.com","uuid":"user2","dir":"/home/user2@example.com","shell":"/usr/bin/bash","gecos":"user2@example.com","groups":[{"name":"remote-test-group","ugid":"12345"},{"name":"local-test-group","ugid":""}]}}' +err: From 3ba906c6e29ff0adfa2d4d9c12fdddac02e9b856 Mon Sep 17 00:00:00 2001 From: Nikos Date: Thu, 12 Dec 2024 17:12:19 +0100 Subject: [PATCH 0279/1670] fix: add NormalizeUsername method Each provider should implement a NormalizeUsername method that will be used when comparing usernames. --- internal/broker/broker.go | 11 +++++--- internal/broker/config.go | 18 ++++++++----- internal/broker/config_test.go | 16 ++++++++--- internal/broker/export_test.go | 4 +++ internal/broker/helper_test.go | 5 ++++ internal/providers/msentraid/msentraid.go | 13 ++++++--- .../providers/msentraid/msentraid_test.go | 27 +++++++++++++++++++ internal/providers/noprovider/noprovider.go | 7 ++++- internal/providers/providers.go | 1 + 9 files changed, 84 insertions(+), 18 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 65556f092d..467114bbd5 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -98,15 +98,17 @@ type Option func(*option) func New(cfg Config, args ...Option) (b *Broker, err error) { defer decorate.OnError(&err, "could not create broker") + p := providers.CurrentProvider() + if cfg.ConfigFile != "" { - cfg.userConfig, err = parseConfigFile(cfg.ConfigFile) + cfg.userConfig, err = parseConfigFile(cfg.ConfigFile, p) if err != nil { return nil, fmt.Errorf("could not parse config: %v", err) } } opts := option{ - provider: providers.CurrentProvider(), + provider: p, } for _, arg := range args { arg(&opts) @@ -644,6 +646,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au // userNameIsAllowed checks whether the user's username is allowed to access the machine. func (b *Broker) userNameIsAllowed(userName string) bool { + normalizedUsername := b.provider.NormalizeUsername(userName) // The user is allowed to log in if: // - ALL users are allowed // - the user's name is in the list of allowed_users @@ -651,14 +654,14 @@ func (b *Broker) userNameIsAllowed(userName string) bool { if b.cfg.userConfig.allUsersAllowed { return true } - if _, ok := b.cfg.userConfig.allowedUsers[userName]; ok { + if _, ok := b.cfg.userConfig.allowedUsers[normalizedUsername]; ok { return true } if !b.cfg.userConfig.ownerAllowed { return false } // If owner is undefined, then the first user to log in is considered the owner - return b.cfg.userConfig.firstUserBecomesOwner || b.cfg.userConfig.owner == userName + return b.cfg.userConfig.firstUserBecomesOwner || b.cfg.userConfig.owner == normalizedUsername } func (b *Broker) startAuthenticate(sessionID string) (context.Context, error) { diff --git a/internal/broker/config.go b/internal/broker/config.go index c934d7210b..fee18c9330 100644 --- a/internal/broker/config.go +++ b/internal/broker/config.go @@ -50,6 +50,10 @@ var ( ownerAutoRegistrationConfig embed.FS ) +type provider interface { + NormalizeUsername(username string) string +} + type templateEnv struct { Owner string } @@ -66,6 +70,8 @@ type userConfig struct { owner string homeBaseDir string allowedSSHSuffixes []string + + provider provider } // GetDropInDir takes the broker configuration path and returns the drop in dir path. @@ -130,17 +136,17 @@ func (uc *userConfig) populateUsersConfig(users *ini.Section) { continue } - uc.allowedUsers[user] = struct{}{} + uc.allowedUsers[uc.provider.NormalizeUsername(user)] = struct{}{} } // We need to read the owner key after we call HasKey, because the key is created // when we call the "Key" function and we can't distinguish between empty and unset. - uc.owner = users.Key(ownerKey).String() + uc.owner = uc.provider.NormalizeUsername(users.Key(ownerKey).String()) } // parseConfigFile parses the config file and returns a map with the configuration keys and values. -func parseConfigFile(cfgPath string) (userConfig, error) { - cfg := userConfig{} +func parseConfigFile(cfgPath string, p provider) (userConfig, error) { + cfg := userConfig{provider: p} dropInFiles, err := getDropInFiles(cfgPath) if err != nil { @@ -182,7 +188,7 @@ func (uc *userConfig) shouldRegisterOwner() bool { func (uc *userConfig) registerOwner(cfgPath, userName string) error { if cfgPath == "" { - uc.owner = userName + uc.owner = uc.provider.NormalizeUsername(userName) uc.firstUserBecomesOwner = false return nil } @@ -207,7 +213,7 @@ func (uc *userConfig) registerOwner(cfgPath, userName string) error { // We set the owner after we create the autoregistration file, so that in case of an error // the owner is not updated. - uc.owner = userName + uc.owner = uc.provider.NormalizeUsername(userName) uc.firstUserBecomesOwner = false return nil diff --git a/internal/broker/config_test.go b/internal/broker/config_test.go index 2f25a9a843..5fd28d0f65 100644 --- a/internal/broker/config_test.go +++ b/internal/broker/config_test.go @@ -10,6 +10,7 @@ import ( "unsafe" "github.com/stretchr/testify/require" + "github.com/ubuntu/authd-oidc-brokers/internal/testutils" "github.com/ubuntu/authd-oidc-brokers/internal/testutils/golden" ) @@ -56,6 +57,8 @@ issuer = https://higher-precedence-issuer.url.com func TestParseConfig(t *testing.T) { t.Parallel() + p := &testutils.MockProvider{} + uncheckedFields := map[string]struct{}{"provider": {}} tests := map[string]struct { configType string @@ -121,12 +124,13 @@ func TestParseConfig(t *testing.T) { require.NoError(t, err, "Setup: Failed to make drop-in file unreadable") } - cfg, err := parseConfigFile(confPath) + cfg, err := parseConfigFile(confPath, p) if tc.wantErr { require.Error(t, err) return } require.NoError(t, err) + require.Equal(t, cfg.provider, p) outDir := t.TempDir() // Write the names and values of all fields in the config to a file. We can't use the json or yaml @@ -136,6 +140,9 @@ func TestParseConfig(t *testing.T) { typ := reflect.TypeOf(&cfg).Elem() for i := 0; i < typ.NumField(); i++ { field := typ.Field(i) + if _, ok := uncheckedFields[field.Name]; ok { + continue + } fieldValue := val.Field(i) if field.PkgPath != "" { //nolint: gosec // We are using unsafe to access unexported fields for testing purposes @@ -238,6 +245,8 @@ allowed_ssh_suffixes = @issuer.url.com func TestParseUserConfig(t *testing.T) { t.Parallel() + p := &testutils.MockProvider{} + tests := map[string]struct { wantAllUsersAllowed bool wantOwnerAllowed bool @@ -276,7 +285,7 @@ func TestParseUserConfig(t *testing.T) { err = os.Mkdir(dropInDir, 0700) require.NoError(t, err, "Setup: Failed to create drop-in directory") - cfg, err := parseConfigFile(confPath) + cfg, err := parseConfigFile(confPath, p) // convert the allowed users array to a map allowedUsersMap := map[string]struct{}{} @@ -297,6 +306,7 @@ func TestParseUserConfig(t *testing.T) { } func TestRegisterOwner(t *testing.T) { + p := &testutils.MockProvider{} outDir := t.TempDir() userName := "owner_name" confPath := filepath.Join(outDir, "broker.conf") @@ -308,7 +318,7 @@ func TestRegisterOwner(t *testing.T) { err = os.Mkdir(dropInDir, 0700) require.NoError(t, err, "Setup: Failed to create drop-in directory") - cfg := userConfig{firstUserBecomesOwner: true, ownerAllowed: true} + cfg := userConfig{firstUserBecomesOwner: true, ownerAllowed: true, provider: p} err = cfg.registerOwner(confPath, userName) require.NoError(t, err) diff --git a/internal/broker/export_test.go b/internal/broker/export_test.go index 0d9a092e6e..389679f823 100644 --- a/internal/broker/export_test.go +++ b/internal/broker/export_test.go @@ -43,6 +43,10 @@ func (cfg *Config) SetAllowedSSHSuffixes(allowedSSHSuffixes []string) { cfg.allowedSSHSuffixes = allowedSSHSuffixes } +func (cfg *Config) SetProvider(provider provider) { + cfg.provider = provider +} + func (cfg *Config) ClientID() string { return cfg.clientID } diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index 119c5b0882..c63b06d59d 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -14,6 +14,7 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/require" "github.com/ubuntu/authd-oidc-brokers/internal/broker" + "github.com/ubuntu/authd-oidc-brokers/internal/providers" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "github.com/ubuntu/authd-oidc-brokers/internal/testutils" "github.com/ubuntu/authd-oidc-brokers/internal/token" @@ -30,6 +31,7 @@ type brokerForTestConfig struct { owner string homeBaseDir string allowedSSHSuffixes []string + provider providers.Provider getUserInfoFails bool firstCallDelay int @@ -77,6 +79,9 @@ func newBrokerForTests(t *testing.T, cfg *brokerForTestConfig) (b *broker.Broker GetGroupsFunc: cfg.getGroupsFunc, } + if cfg.provider == nil { + cfg.SetProvider(provider) + } if cfg.DataDir == "" { cfg.DataDir = t.TempDir() } diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index b4ce839677..a5ee0bef8f 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -273,12 +273,17 @@ func (p Provider) CurrentAuthenticationModesOffered( return offeredModes, nil } +// NormalizeUsername parses a username into a normalized version. +func (p Provider) NormalizeUsername(username string) string { + // Microsoft Entra usernames are case-insensitive. We can safely use strings.ToLower here without worrying about + // different Unicode characters that fold to the same lowercase letter, because the Microsoft Entra username policy + // (which we check in VerifyUsername) ensures that the username only contains ASCII characters. + return strings.ToLower(username) +} + // VerifyUsername checks if the authenticated username matches the requested username and that both are valid. func (p Provider) VerifyUsername(requestedUsername, authenticatedUsername string) error { - // Microsoft Entra usernames are case-insensitive. We can safely use strings.EqualFold here without worrying about - // different Unicode characters that fold to the same lowercase letter, because the Microsoft Entra username policy - // (which we checked above) ensures that the username only contains ASCII characters. - if !strings.EqualFold(requestedUsername, authenticatedUsername) { + if p.NormalizeUsername(requestedUsername) != p.NormalizeUsername(authenticatedUsername) { return fmt.Errorf("requested username %q does not match the authenticated user %q", requestedUsername, authenticatedUsername) } diff --git a/internal/providers/msentraid/msentraid_test.go b/internal/providers/msentraid/msentraid_test.go index ce3a12ee4a..34aee2427d 100644 --- a/internal/providers/msentraid/msentraid_test.go +++ b/internal/providers/msentraid/msentraid_test.go @@ -52,6 +52,33 @@ func TestCheckTokenScopes(t *testing.T) { } } +func TestNormalizeUsername(t *testing.T) { + t.Parallel() + tests := map[string]struct { + username string + + wantNormalized string + }{ + "Shouldnt_change_all_lower_case": { + username: "name@email.com", + wantNormalized: "name@email.com", + }, + "Should_convert_all_to_lower_case": { + username: "NAME@email.com", + wantNormalized: "name@email.com", + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + p := msentraid.New() + ret := p.NormalizeUsername(tc.username) + require.Equal(t, tc.wantNormalized, ret) + }) + } +} + func TestVerifyUsername(t *testing.T) { t.Parallel() diff --git a/internal/providers/noprovider/noprovider.go b/internal/providers/noprovider/noprovider.go index e0cbec709d..7d261954da 100644 --- a/internal/providers/noprovider/noprovider.go +++ b/internal/providers/noprovider/noprovider.go @@ -106,9 +106,14 @@ func (p NoProvider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, ), nil } +// NormalizeUsername parses a username into a normalized version. +func (p NoProvider) NormalizeUsername(username string) string { + return username +} + // VerifyUsername checks if the requested username matches the authenticated user. func (p NoProvider) VerifyUsername(requestedUsername, username string) error { - if requestedUsername != username { + if p.NormalizeUsername(requestedUsername) != p.NormalizeUsername(username) { return fmt.Errorf("requested username %q does not match the authenticated user %q", requestedUsername, username) } return nil diff --git a/internal/providers/providers.go b/internal/providers/providers.go index 6e6db807da..c13e170b24 100644 --- a/internal/providers/providers.go +++ b/internal/providers/providers.go @@ -24,5 +24,6 @@ type Provider interface { ) ([]string, error) GetExtraFields(token *oauth2.Token) map[string]interface{} GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken) (info.User, error) + NormalizeUsername(username string) string VerifyUsername(requestedUsername, authenticatedUsername string) error } From 5002225024879f97940576dd0ce25ff414e6610e Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 16 Dec 2024 12:03:58 +0100 Subject: [PATCH 0280/1670] fixup: Fix race condition --- internal/broker/broker.go | 40 +++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 467114bbd5..4cf81985c5 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -49,6 +49,8 @@ type Config struct { type Broker struct { cfg Config + setOwnerMutex sync.Mutex + provider providers.Provider oidcCfg oidc.Config @@ -613,19 +615,16 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au } } - if !b.userNameIsAllowed(authInfo.UserInfo.Name) { - return AuthDenied, errorMessage{Message: "permission denied"} + err = b.maybeRegisterOwner(b.cfg.ConfigFile, authInfo.UserInfo.Name) + if err != nil { + // The user is not allowed if we fail to create the owner-autoregistration file. + // Otherwise the owner might change if the broker is restarted. + slog.Error(fmt.Sprintf("Failed to assign the owner role: %v", err)) + return AuthDenied, errorMessage{Message: "could not register the owner"} } - // If the owner is unset and allowed, we auto-generate a config file with the first - // user to log in as the owner. - if b.cfg.userConfig.shouldRegisterOwner() { - if err := b.cfg.registerOwner(b.cfg.ConfigFile, authInfo.UserInfo.Name); err != nil { - // The user is not allowed, if we fail to create the owner-autoregistration file. - // Otherwise the owner might change if the broker is restarted. - slog.Error(fmt.Sprintf("Failed to assign the owner role: %v", err)) - return AuthDenied, errorMessage{Message: "could not register the owner"} - } + if !b.userNameIsAllowed(authInfo.UserInfo.Name) { + return AuthDenied, errorMessage{Message: "permission denied"} } if session.isOffline { @@ -644,6 +643,19 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au return AuthGranted, userInfoMessage{UserInfo: authInfo.UserInfo} } +func (b *Broker) maybeRegisterOwner(cfgPath string, userName string) error { + // We need to lock here to avoid a race condition where two users log in at the same time, causing both to be + // considered the owner. + b.setOwnerMutex.Lock() + defer b.setOwnerMutex.Unlock() + + if !b.cfg.userConfig.shouldRegisterOwner() { + return nil + } + + return b.cfg.registerOwner(cfgPath, userName) +} + // userNameIsAllowed checks whether the user's username is allowed to access the machine. func (b *Broker) userNameIsAllowed(userName string) bool { normalizedUsername := b.provider.NormalizeUsername(userName) @@ -657,11 +669,7 @@ func (b *Broker) userNameIsAllowed(userName string) bool { if _, ok := b.cfg.userConfig.allowedUsers[normalizedUsername]; ok { return true } - if !b.cfg.userConfig.ownerAllowed { - return false - } - // If owner is undefined, then the first user to log in is considered the owner - return b.cfg.userConfig.firstUserBecomesOwner || b.cfg.userConfig.owner == normalizedUsername + return b.cfg.userConfig.ownerAllowed && b.cfg.userConfig.owner == normalizedUsername } func (b *Broker) startAuthenticate(sessionID string) (context.Context, error) { From fa071ff5fd098857541c4f311726f44fb32e3fa4 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 16 Dec 2024 15:25:27 +0100 Subject: [PATCH 0281/1670] fixup: Differentiate between allowed users during setup and in expected results --- internal/broker/broker_test.go | 71 ++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index a52cae9f6f..e6ec20cdc1 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -857,6 +857,17 @@ func TestConcurrentIsAuthenticated(t *testing.T) { func TestIsAuthenticatedAllowedUsersConfig(t *testing.T) { t.Parallel() + + u1 := "u1" + u2 := "u2" + u3 := "u3" + allUsers := []string{u1, u2, u3} + + idTokenClaims := []map[string]interface{}{} + for _, uname := range allUsers { + idTokenClaims = append(idTokenClaims, map[string]interface{}{"sub": "user", "name": "user", "email": uname}) + } + tests := map[string]struct { allowedUsers map[string]struct{} owner string @@ -864,49 +875,47 @@ func TestIsAuthenticatedAllowedUsersConfig(t *testing.T) { allUsersAllowed bool firstUserBecomesOwner bool - allowedUsernames []string - unallowedUsernames []string + wantAllowedUsers []string + wantUnallowedUsers []string }{ "All_users_are_allowed": { allUsersAllowed: true, - allowedUsernames: []string{"u1", "u2"}, + wantAllowedUsers: allUsers, }, "Only_owner_allowed": { ownerAllowed: true, - owner: "machine_owner", - allowedUsernames: []string{"machine_owner"}, - unallowedUsernames: []string{"u1", "u2"}, + owner: u1, + wantAllowedUsers: []string{u1}, + wantUnallowedUsers: []string{u2, u3}, }, "No_users_allowed": { - unallowedUsernames: []string{"u1", "u2", "machine_owner", "u3"}, + wantUnallowedUsers: allUsers, }, "No_users_allowed_when_owner_is_allowed_but_not_set": { ownerAllowed: true, - unallowedUsernames: []string{"u1", "u2", "machine_owner", "u3"}, + wantUnallowedUsers: allUsers, }, "Only_first_user_allowed": { ownerAllowed: true, firstUserBecomesOwner: true, - allowedUsernames: []string{"random_user"}, - unallowedUsernames: []string{"u1", "u2"}, + wantAllowedUsers: []string{u1}, + wantUnallowedUsers: []string{u2, u3}, }, "Specific_users_allowed": { - allowedUsers: map[string]struct{}{"u1": {}, "u2": {}}, - allowedUsernames: []string{"u1", "u2"}, - unallowedUsernames: []string{"u3"}, + allowedUsers: map[string]struct{}{u1: {}, u2: {}}, + wantAllowedUsers: []string{u1, u2}, + wantUnallowedUsers: []string{u3}, }, "Specific_users_and_owner": { - allowedUsers: map[string]struct{}{"u1": {}, "u2": {}}, ownerAllowed: true, - owner: "machine_owner", - allowedUsernames: []string{"u1", "u2", "machine_owner"}, - unallowedUsernames: []string{"u3"}, + allowedUsers: map[string]struct{}{u1: {}}, + owner: u2, + wantAllowedUsers: []string{u1, u2}, + wantUnallowedUsers: []string{u3}, }, - "Owner_is_disabled_even_when_registered": { - allowedUsers: map[string]struct{}{"u1": {}, "u2": {}}, - owner: "machine_owner", - allowedUsernames: []string{"u1", "u2"}, - unallowedUsernames: []string{"machine_owner", "u3"}, + "Owner_is_set_but_not_allowed": { + owner: u1, + wantUnallowedUsers: allUsers, }, } @@ -918,14 +927,6 @@ func TestIsAuthenticatedAllowedUsersConfig(t *testing.T) { err := os.Mkdir(dataDir, 0700) require.NoError(t, err, "Setup: Mkdir should not have returned an error") - idTokenClaims := []map[string]interface{}{} - for _, uname := range tc.allowedUsernames { - idTokenClaims = append(idTokenClaims, map[string]interface{}{"sub": "user", "name": "user", "email": uname}) - } - for _, uname := range tc.unallowedUsernames { - idTokenClaims = append(idTokenClaims, map[string]interface{}{"sub": "user", "name": "user", "email": uname}) - } - b := newBrokerForTests(t, &brokerForTestConfig{ Config: broker.Config{DataDir: dataDir}, allowedUsers: tc.allowedUsers, @@ -938,7 +939,7 @@ func TestIsAuthenticatedAllowedUsersConfig(t *testing.T) { }, }) - for _, u := range append(tc.allowedUsernames, tc.unallowedUsernames...) { + for _, u := range allUsers { sessionID, key := newSessionForTests(t, b, u, "") token := tokenOptions{username: u} generateAndStoreCachedInfo(t, token, b.TokenPathForSession(sessionID)) @@ -952,11 +953,15 @@ func TestIsAuthenticatedAllowedUsersConfig(t *testing.T) { access, data, err := b.IsAuthenticated(sessionID, authData) require.True(t, json.Valid([]byte(data)), "IsAuthenticated returned data must be a valid JSON") require.NoError(t, err) - if slices.Contains(tc.allowedUsernames, u) { + if slices.Contains(tc.wantAllowedUsers, u) { require.Equal(t, access, broker.AuthGranted, "authentication failed") - } else { + continue + } + if slices.Contains(tc.wantUnallowedUsers, u) { require.Equal(t, access, broker.AuthDenied, "authentication failed") + continue } + t.Fatalf("user %s is not in the allowed or unallowed users list", u) } }) } From 639629c6ef3ec8ac7da6b9e67ec088fd3d0b584f Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 16 Dec 2024 15:31:01 +0100 Subject: [PATCH 0282/1670] fixup: Rename and group test case --- internal/broker/broker_test.go | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index e6ec20cdc1..37d04874a1 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -878,6 +878,18 @@ func TestIsAuthenticatedAllowedUsersConfig(t *testing.T) { wantAllowedUsers []string wantUnallowedUsers []string }{ + "No_users_allowed": { + wantUnallowedUsers: allUsers, + }, + "No_users_allowed_when_owner_is_allowed_but_not_set": { + ownerAllowed: true, + wantUnallowedUsers: allUsers, + }, + "No_users_allowed_when_owner_is_set_but_not_allowed": { + owner: u1, + wantUnallowedUsers: allUsers, + }, + "All_users_are_allowed": { allUsersAllowed: true, wantAllowedUsers: allUsers, @@ -888,13 +900,6 @@ func TestIsAuthenticatedAllowedUsersConfig(t *testing.T) { wantAllowedUsers: []string{u1}, wantUnallowedUsers: []string{u2, u3}, }, - "No_users_allowed": { - wantUnallowedUsers: allUsers, - }, - "No_users_allowed_when_owner_is_allowed_but_not_set": { - ownerAllowed: true, - wantUnallowedUsers: allUsers, - }, "Only_first_user_allowed": { ownerAllowed: true, firstUserBecomesOwner: true, @@ -913,10 +918,6 @@ func TestIsAuthenticatedAllowedUsersConfig(t *testing.T) { wantAllowedUsers: []string{u1, u2}, wantUnallowedUsers: []string{u3}, }, - "Owner_is_set_but_not_allowed": { - owner: u1, - wantUnallowedUsers: allUsers, - }, } for name, tc := range tests { From 41166464926889f05dfd5f69032a063671e26556 Mon Sep 17 00:00:00 2001 From: Nikos Date: Tue, 17 Dec 2024 13:59:54 +0100 Subject: [PATCH 0283/1670] fix: add tests for normalized username --- internal/broker/broker_test.go | 10 +++++++++- internal/testutils/provider.go | 5 +++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 37d04874a1..12e5ba8fdc 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" "slices" + "strings" "testing" "time" @@ -860,7 +861,7 @@ func TestIsAuthenticatedAllowedUsersConfig(t *testing.T) { u1 := "u1" u2 := "u2" - u3 := "u3" + u3 := "U3" allUsers := []string{u1, u2, u3} idTokenClaims := []map[string]interface{}{} @@ -918,6 +919,13 @@ func TestIsAuthenticatedAllowedUsersConfig(t *testing.T) { wantAllowedUsers: []string{u1, u2}, wantUnallowedUsers: []string{u3}, }, + "Usernames_are_normalized": { + ownerAllowed: true, + allowedUsers: map[string]struct{}{u3: {}}, + owner: strings.ToLower(u3), + wantAllowedUsers: []string{u3}, + wantUnallowedUsers: []string{u1, u2}, + }, } for name, tc := range tests { diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index 4d53c7a508..4f55cfaf4a 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -373,6 +373,11 @@ func (p *MockProvider) AuthOptions() []oauth2.AuthCodeOption { return p.NoProvider.AuthOptions() } +// NormalizeUsername parses a username into a normalized version. +func (p *MockProvider) NormalizeUsername(username string) string { + return strings.ToLower(username) +} + // GetUserInfo is a no-op when no specific provider is in use. func (p *MockProvider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken) (info.User, error) { if p.GetUserInfoFails { From ffd7018a5b5805f98e774c658bab110efd9f6feb Mon Sep 17 00:00:00 2001 From: Nikos Date: Tue, 17 Dec 2024 18:52:08 +0100 Subject: [PATCH 0284/1670] fix: move owner mutex to userConfig --- internal/broker/broker.go | 21 +++------------------ internal/broker/config.go | 23 ++++++++++++++++++++--- internal/broker/config_test.go | 7 ++++--- internal/broker/export_test.go | 14 ++++++++++++++ internal/broker/helper_test.go | 1 + 5 files changed, 42 insertions(+), 24 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 4cf81985c5..d6041398bd 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -49,8 +49,6 @@ type Config struct { type Broker struct { cfg Config - setOwnerMutex sync.Mutex - provider providers.Provider oidcCfg oidc.Config @@ -615,8 +613,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au } } - err = b.maybeRegisterOwner(b.cfg.ConfigFile, authInfo.UserInfo.Name) - if err != nil { + if err := b.cfg.registerOwner(b.cfg.ConfigFile, authInfo.UserInfo.Name); err != nil { // The user is not allowed if we fail to create the owner-autoregistration file. // Otherwise the owner might change if the broker is restarted. slog.Error(fmt.Sprintf("Failed to assign the owner role: %v", err)) @@ -643,19 +640,6 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au return AuthGranted, userInfoMessage{UserInfo: authInfo.UserInfo} } -func (b *Broker) maybeRegisterOwner(cfgPath string, userName string) error { - // We need to lock here to avoid a race condition where two users log in at the same time, causing both to be - // considered the owner. - b.setOwnerMutex.Lock() - defer b.setOwnerMutex.Unlock() - - if !b.cfg.userConfig.shouldRegisterOwner() { - return nil - } - - return b.cfg.registerOwner(cfgPath, userName) -} - // userNameIsAllowed checks whether the user's username is allowed to access the machine. func (b *Broker) userNameIsAllowed(userName string) bool { normalizedUsername := b.provider.NormalizeUsername(userName) @@ -669,7 +653,8 @@ func (b *Broker) userNameIsAllowed(userName string) bool { if _, ok := b.cfg.userConfig.allowedUsers[normalizedUsername]; ok { return true } - return b.cfg.userConfig.ownerAllowed && b.cfg.userConfig.owner == normalizedUsername + + return b.cfg.isOwnerAllowed(normalizedUsername) } func (b *Broker) startAuthenticate(sessionID string) (context.Context, error) { diff --git a/internal/broker/config.go b/internal/broker/config.go index fee18c9330..1da3d6c162 100644 --- a/internal/broker/config.go +++ b/internal/broker/config.go @@ -9,6 +9,7 @@ import ( "os" "path/filepath" "strings" + "sync" "gopkg.in/ini.v1" ) @@ -68,6 +69,7 @@ type userConfig struct { ownerAllowed bool firstUserBecomesOwner bool owner string + ownerMutex *sync.RWMutex homeBaseDir string allowedSSHSuffixes []string @@ -103,6 +105,9 @@ func getDropInFiles(cfgPath string) ([]any, error) { } func (uc *userConfig) populateUsersConfig(users *ini.Section) { + uc.ownerMutex.Lock() + defer uc.ownerMutex.Unlock() + if users == nil { // The default behavior is to allow only the owner uc.ownerAllowed = true @@ -146,7 +151,7 @@ func (uc *userConfig) populateUsersConfig(users *ini.Section) { // parseConfigFile parses the config file and returns a map with the configuration keys and values. func parseConfigFile(cfgPath string, p provider) (userConfig, error) { - cfg := userConfig{provider: p} + cfg := userConfig{provider: p, ownerMutex: &sync.RWMutex{}} dropInFiles, err := getDropInFiles(cfgPath) if err != nil { @@ -182,11 +187,23 @@ func parseConfigFile(cfgPath string, p provider) (userConfig, error) { return cfg, nil } -func (uc *userConfig) shouldRegisterOwner() bool { - return uc.ownerAllowed && uc.firstUserBecomesOwner && uc.owner == "" +func (uc *userConfig) isOwnerAllowed(userName string) bool { + uc.ownerMutex.RLock() + defer uc.ownerMutex.RUnlock() + + return uc.ownerAllowed && uc.owner == userName } func (uc *userConfig) registerOwner(cfgPath, userName string) error { + // We need to lock here to avoid a race condition where two users log in at the same time, causing both to be + // considered the owner. + uc.ownerMutex.Lock() + defer uc.ownerMutex.Unlock() + + if shouldRegister := uc.ownerAllowed && uc.firstUserBecomesOwner && uc.owner == ""; !shouldRegister { + return nil + } + if cfgPath == "" { uc.owner = uc.provider.NormalizeUsername(userName) uc.firstUserBecomesOwner = false diff --git a/internal/broker/config_test.go b/internal/broker/config_test.go index 5fd28d0f65..f20e9d501e 100644 --- a/internal/broker/config_test.go +++ b/internal/broker/config_test.go @@ -6,6 +6,7 @@ import ( "path/filepath" "reflect" "strings" + "sync" "testing" "unsafe" @@ -58,7 +59,7 @@ issuer = https://higher-precedence-issuer.url.com func TestParseConfig(t *testing.T) { t.Parallel() p := &testutils.MockProvider{} - uncheckedFields := map[string]struct{}{"provider": {}} + ignoredFields := map[string]struct{}{"provider": {}, "ownerMutex": {}} tests := map[string]struct { configType string @@ -140,7 +141,7 @@ func TestParseConfig(t *testing.T) { typ := reflect.TypeOf(&cfg).Elem() for i := 0; i < typ.NumField(); i++ { field := typ.Field(i) - if _, ok := uncheckedFields[field.Name]; ok { + if _, ok := ignoredFields[field.Name]; ok { continue } fieldValue := val.Field(i) @@ -318,7 +319,7 @@ func TestRegisterOwner(t *testing.T) { err = os.Mkdir(dropInDir, 0700) require.NoError(t, err, "Setup: Failed to create drop-in directory") - cfg := userConfig{firstUserBecomesOwner: true, ownerAllowed: true, provider: p} + cfg := userConfig{firstUserBecomesOwner: true, ownerAllowed: true, provider: p, ownerMutex: &sync.RWMutex{}} err = cfg.registerOwner(confPath, userName) require.NoError(t, err) diff --git a/internal/broker/export_test.go b/internal/broker/export_test.go index 389679f823..9283baeb5d 100644 --- a/internal/broker/export_test.go +++ b/internal/broker/export_test.go @@ -2,11 +2,16 @@ package broker import ( "context" + "sync" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" tokenPkg "github.com/ubuntu/authd-oidc-brokers/internal/token" ) +func (cfg *Config) Init() { + cfg.ownerMutex = &sync.RWMutex{} +} + func (cfg *Config) SetClientID(clientID string) { cfg.clientID = clientID } @@ -24,10 +29,16 @@ func (cfg *Config) SetAllowedUsers(allowedUsers map[string]struct{}) { } func (cfg *Config) SetOwner(owner string) { + cfg.ownerMutex.Lock() + defer cfg.ownerMutex.Unlock() + cfg.owner = owner } func (cfg *Config) SetFirstUserBecomesOwner(firstUserBecomesOwner bool) { + cfg.ownerMutex.Lock() + defer cfg.ownerMutex.Unlock() + cfg.firstUserBecomesOwner = firstUserBecomesOwner } @@ -36,6 +47,9 @@ func (cfg *Config) SetAllUsersAllowed(allUsersAllowed bool) { } func (cfg *Config) SetOwnerAllowed(ownerAllowed bool) { + cfg.ownerMutex.Lock() + defer cfg.ownerMutex.Unlock() + cfg.ownerAllowed = ownerAllowed } diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index c63b06d59d..f605814fc8 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -47,6 +47,7 @@ type brokerForTestConfig struct { func newBrokerForTests(t *testing.T, cfg *brokerForTestConfig) (b *broker.Broker) { t.Helper() + cfg.Init() if cfg.issuerURL != "" { cfg.SetIssuerURL(cfg.issuerURL) } From 6116ba9c70d0fcae9df197d02c0e1c766d5fc9cd Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 18 Dec 2024 21:38:44 +0100 Subject: [PATCH 0285/1670] Ensure broker.conf has permissions 0600 In previous versions, the broker.conf was created with mode 0666 - umask. This is not secure, because the file can contain sensitive information (like the client_secret), so we ensure that the mode is 0600. --- snap/hooks/install | 3 ++- snap/hooks/post-refresh | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100755 snap/hooks/post-refresh diff --git a/snap/hooks/install b/snap/hooks/install index 077b3cbdac..da8db26333 100755 --- a/snap/hooks/install +++ b/snap/hooks/install @@ -1,4 +1,5 @@ #!/bin/sh + set -eu -cp --update=none ${SNAP}/conf/broker.conf.orig ${SNAP_DATA}/broker.conf +install --mode=0600 "${SNAP}/conf/broker.conf.orig" "${SNAP_DATA}/broker.conf" diff --git a/snap/hooks/post-refresh b/snap/hooks/post-refresh new file mode 100755 index 0000000000..bbeb65b3a8 --- /dev/null +++ b/snap/hooks/post-refresh @@ -0,0 +1,10 @@ +#!/bin/sh + +set -eu + +# In previous versions, the broker.conf was created with mode 0777 - umask. +# This is not secure, because the file can contain sensitive information +# (like the client_secret), so we ensure that the mode is 0700. +if [ -f "${SNAP_DATA}/broker.conf" ]; then + chmod 0600 "${SNAP_DATA}/broker.conf" +fi From fc10148fa5e9e3b2cfdf76a9b52e1508e3787904 Mon Sep 17 00:00:00 2001 From: Nikos Date: Fri, 13 Dec 2024 11:47:52 +0100 Subject: [PATCH 0286/1670] fix: add device ownership migration logic The device ownership config, added in 6fc87bb1fd83c6ce8a68b8a19336aa0f2a27aa26, introduced a breaking change. The default behavior before was that ALL users were allowed, but now only the owner is allowed. In order to migrate the config, we set a new snapctl config param. If that is not present, that means that we need to migrate the config. --- .../00-migration-allowed_users.conf | 11 +++++ .../broker.conf.d/10-allowed_users.conf | 28 +++++++++++++ snap/hooks/post-refresh | 41 +++++++++++++++++++ snap/hooks/pre-refresh | 6 +++ snap/snapcraft.yaml | 1 + 5 files changed, 87 insertions(+) create mode 100644 conf/migrations/pre-0.2.0/broker.conf.d/00-migration-allowed_users.conf create mode 100644 conf/migrations/pre-0.2.0/broker.conf.d/10-allowed_users.conf create mode 100644 snap/hooks/pre-refresh diff --git a/conf/migrations/pre-0.2.0/broker.conf.d/00-migration-allowed_users.conf b/conf/migrations/pre-0.2.0/broker.conf.d/00-migration-allowed_users.conf new file mode 100644 index 0000000000..d1b977fdee --- /dev/null +++ b/conf/migrations/pre-0.2.0/broker.conf.d/00-migration-allowed_users.conf @@ -0,0 +1,11 @@ +## This file was generated during the broker upgrade process. DO NOT EDIT. +## +## This file adds the 'allowed_users' option and sets it to 'ALL' +## to preserve backward compatibility, as the default for this +## option is 'OWNER'. +## For more information, refer to 10-allowed_users.conf. +## +## If you want to use the new default setting, simply delete this file. + +[users] +allowed_users = ALL diff --git a/conf/migrations/pre-0.2.0/broker.conf.d/10-allowed_users.conf b/conf/migrations/pre-0.2.0/broker.conf.d/10-allowed_users.conf new file mode 100644 index 0000000000..33377e5227 --- /dev/null +++ b/conf/migrations/pre-0.2.0/broker.conf.d/10-allowed_users.conf @@ -0,0 +1,28 @@ +[users] +## 'allowed_users' specifies the users who are permitted to log in after +## successfully authenticating with the Identity Provider. +## Values are separated by commas. Supported values: +## - 'OWNER': Grants access to the user specified in the 'owner' option +## (see below). This is the default. +## - 'ALL': Grants access to all users who successfully authenticate +## with the Identity Provider. +## - : Grants access to specific additional users +## (e.g. user1@example.com). +## Example: allowed_users = OWNER,user1@example.com,admin@example.com +#allowed_users = OWNER + +## 'owner' specifies the user assigned the owner role. This user is +## permitted to log in if 'OWNER' is included in the 'allowed_users' +## option. +## +## If this option is left unset, the first user to successfully log in +## via this broker will automatically be assigned the owner role. A +## drop-in configuration file will be created in broker.conf.d/ to set +## the 'owner' option. +## +## To disable automatic assignment, you can either: +## 1. Explicitly set this option to an empty value (e.g. owner = "") +## 2. Remove 'OWNER' from the 'allowed_users' option +## +## Example: owner = user2@example.com +#owner = diff --git a/snap/hooks/post-refresh b/snap/hooks/post-refresh index bbeb65b3a8..87b082b9d8 100755 --- a/snap/hooks/post-refresh +++ b/snap/hooks/post-refresh @@ -8,3 +8,44 @@ set -eu if [ -f "${SNAP_DATA}/broker.conf" ]; then chmod 0600 "${SNAP_DATA}/broker.conf" fi + +PREVIOUS_VERSION=$(snapctl get previous-version) +INITIAL_ALLOWED_USERS_VERSION="0.2.0" + +version_less_than() { + [ "$(printf '%s\n' "${@}" | sort -V | head -n1)" = "$1" ] +} + +echo_and_log_to_journal() { + echo "${@}" + logger -t $SNAP_NAME "${@}" +} + +should_transition_to_allowed_users() { + # Transition to allowed users if: + # - previous-version is not set (that means that the previous version is + # older than 0.2.0, i.e. the version where we introduced setting the + # previous-version in the pre-refresh hook). + # - previous-version is set, but it is less than 0.2.0. That should never + # happen, but we check it to give an example how the previous-version + # can be used to transition data from older versions. + [ -z "${PREVIOUS_VERSION}" ] || version_less_than "${PREVIOUS_VERSION}" "${INITIAL_ALLOWED_USERS_VERSION}" +} + +transition_to_allowed_users() { + echo_and_log_to_journal "Transitioning to allowed users" + mkdir -p ${SNAP_DATA}/broker.conf.d + cp --update=none ${SNAP}/conf/migrations/pre-${INITIAL_ALLOWED_USERS_VERSION}/broker.conf.d/* ${SNAP_DATA}/broker.conf.d/ +} + +migrate() { + echo "post-refresh hook" + echo_and_log_to_journal "Previous version: $PREVIOUS_VERSION" + + if should_transition_to_allowed_users; then + transition_to_allowed_users + fi +} + +migrate >> /tmp/snap-refresh.log # TODO: Only for debugging + diff --git a/snap/hooks/pre-refresh b/snap/hooks/pre-refresh new file mode 100644 index 0000000000..50fd4a3582 --- /dev/null +++ b/snap/hooks/pre-refresh @@ -0,0 +1,6 @@ +#!/bin/sh +set -eu + +echo "pre-refresh hook" >> /tmp/snap.log # TODO: Only for debugging + +snapctl set previous-version=${SNAP_VERSION} diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 24c23a0c2d..851577dd97 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -38,6 +38,7 @@ parts: organize: "authd.conf": "conf/authd/oidc.conf" "broker.conf": "conf/broker.conf.orig" + "migrations": "conf/migrations" # Build the snap version from the git repository and current tree state. version: source: . From 2475ad6e9c7369d1ae4381aac9feea6e6b234417 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 18 Dec 2024 19:45:25 +0100 Subject: [PATCH 0287/1670] snap: Make pre-refresh hook executable --- snap/hooks/pre-refresh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 snap/hooks/pre-refresh diff --git a/snap/hooks/pre-refresh b/snap/hooks/pre-refresh old mode 100644 new mode 100755 From 521d37a46fd489fdab3e6bfafb63ff3456641a1d Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 18 Dec 2024 19:49:49 +0100 Subject: [PATCH 0288/1670] Double quote to prevent globbing and word splitting --- snap/hooks/post-refresh | 6 +++--- snap/hooks/pre-refresh | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/snap/hooks/post-refresh b/snap/hooks/post-refresh index 87b082b9d8..667fe5fea6 100755 --- a/snap/hooks/post-refresh +++ b/snap/hooks/post-refresh @@ -18,7 +18,7 @@ version_less_than() { echo_and_log_to_journal() { echo "${@}" - logger -t $SNAP_NAME "${@}" + logger -t "${SNAP_NAME}" "${@}" } should_transition_to_allowed_users() { @@ -34,8 +34,8 @@ should_transition_to_allowed_users() { transition_to_allowed_users() { echo_and_log_to_journal "Transitioning to allowed users" - mkdir -p ${SNAP_DATA}/broker.conf.d - cp --update=none ${SNAP}/conf/migrations/pre-${INITIAL_ALLOWED_USERS_VERSION}/broker.conf.d/* ${SNAP_DATA}/broker.conf.d/ + mkdir -p "${SNAP_DATA}/broker.conf.d" + cp --update=none "${SNAP}/conf/migrations/pre-${INITIAL_ALLOWED_USERS_VERSION}/broker.conf.d/"* "${SNAP_DATA}/broker.conf.d/" } migrate() { diff --git a/snap/hooks/pre-refresh b/snap/hooks/pre-refresh index 50fd4a3582..538b1ccc75 100755 --- a/snap/hooks/pre-refresh +++ b/snap/hooks/pre-refresh @@ -3,4 +3,4 @@ set -eu echo "pre-refresh hook" >> /tmp/snap.log # TODO: Only for debugging -snapctl set previous-version=${SNAP_VERSION} +snapctl set previous-version="${SNAP_VERSION}" From 7482faae9835818ad4f8c86a7b015c5f79a844a3 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 18 Dec 2024 19:52:21 +0100 Subject: [PATCH 0289/1670] Only log to the journal There's no benefit in logging to /tmp/snap-refresh.log as well. Also, prefix the messages logged to the journal with the hook name. --- snap/hooks/post-refresh | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/snap/hooks/post-refresh b/snap/hooks/post-refresh index 667fe5fea6..2aa65958d9 100755 --- a/snap/hooks/post-refresh +++ b/snap/hooks/post-refresh @@ -12,13 +12,12 @@ fi PREVIOUS_VERSION=$(snapctl get previous-version) INITIAL_ALLOWED_USERS_VERSION="0.2.0" -version_less_than() { - [ "$(printf '%s\n' "${@}" | sort -V | head -n1)" = "$1" ] +log() { + logger -t "${SNAP_NAME}" "post-refresh: $*" } -echo_and_log_to_journal() { - echo "${@}" - logger -t "${SNAP_NAME}" "${@}" +version_less_than() { + [ "$(printf '%s\n' "${@}" | sort -V | head -n1)" = "$1" ] } should_transition_to_allowed_users() { @@ -33,19 +32,13 @@ should_transition_to_allowed_users() { } transition_to_allowed_users() { - echo_and_log_to_journal "Transitioning to allowed users" + log "Transitioning to allowed users" mkdir -p "${SNAP_DATA}/broker.conf.d" cp --update=none "${SNAP}/conf/migrations/pre-${INITIAL_ALLOWED_USERS_VERSION}/broker.conf.d/"* "${SNAP_DATA}/broker.conf.d/" } -migrate() { - echo "post-refresh hook" - echo_and_log_to_journal "Previous version: $PREVIOUS_VERSION" - - if should_transition_to_allowed_users; then - transition_to_allowed_users - fi -} - -migrate >> /tmp/snap-refresh.log # TODO: Only for debugging +log "Previous version: $PREVIOUS_VERSION" +if should_transition_to_allowed_users; then + transition_to_allowed_users +fi From d73a5dbb1d55720ec106b86cd59b9d2d578c2b95 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 18 Dec 2024 19:56:42 +0100 Subject: [PATCH 0290/1670] snap: Add debug output to pre-refresh It might be useful for debugging to print the previous version set in the pre-refresh hook. --- snap/hooks/post-refresh | 2 +- snap/hooks/pre-refresh | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/snap/hooks/post-refresh b/snap/hooks/post-refresh index 2aa65958d9..5fefbd4dc5 100755 --- a/snap/hooks/post-refresh +++ b/snap/hooks/post-refresh @@ -37,7 +37,7 @@ transition_to_allowed_users() { cp --update=none "${SNAP}/conf/migrations/pre-${INITIAL_ALLOWED_USERS_VERSION}/broker.conf.d/"* "${SNAP_DATA}/broker.conf.d/" } -log "Previous version: $PREVIOUS_VERSION" +log "previous-version: $PREVIOUS_VERSION" if should_transition_to_allowed_users; then transition_to_allowed_users diff --git a/snap/hooks/pre-refresh b/snap/hooks/pre-refresh index 538b1ccc75..1ef840dd4c 100755 --- a/snap/hooks/pre-refresh +++ b/snap/hooks/pre-refresh @@ -1,6 +1,8 @@ #!/bin/sh set -eu -echo "pre-refresh hook" >> /tmp/snap.log # TODO: Only for debugging +if [ -n "${DEBUG:-}" ]; then + logger -t "${SNAP_NAME}" "pre-refresh: Setting previous-version to ${SNAP_VERSION}" +fi snapctl set previous-version="${SNAP_VERSION}" From 2b37681b96c8e88bc684cce6edba95bf922c6a8f Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 18 Dec 2024 19:59:38 +0100 Subject: [PATCH 0291/1670] Make log message a bit more readable --- snap/hooks/post-refresh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/snap/hooks/post-refresh b/snap/hooks/post-refresh index 5fefbd4dc5..de28e364f6 100755 --- a/snap/hooks/post-refresh +++ b/snap/hooks/post-refresh @@ -37,7 +37,11 @@ transition_to_allowed_users() { cp --update=none "${SNAP}/conf/migrations/pre-${INITIAL_ALLOWED_USERS_VERSION}/broker.conf.d/"* "${SNAP_DATA}/broker.conf.d/" } -log "previous-version: $PREVIOUS_VERSION" +if [ -z "${PREVIOUS_VERSION}" ]; then + log "previous-version: " +else + log "previous-version: $PREVIOUS_VERSION" +fi if should_transition_to_allowed_users; then transition_to_allowed_users From 8ce6730d4181c17de6ede9cecf888d550774c99d Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 18 Dec 2024 21:07:55 +0100 Subject: [PATCH 0292/1670] Create broker.conf and broker.conf.d with permissions 0700 --- snap/hooks/install | 2 +- snap/hooks/post-refresh | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/snap/hooks/install b/snap/hooks/install index da8db26333..8085039c3f 100755 --- a/snap/hooks/install +++ b/snap/hooks/install @@ -2,4 +2,4 @@ set -eu -install --mode=0600 "${SNAP}/conf/broker.conf.orig" "${SNAP_DATA}/broker.conf" +install --mode=0700 "${SNAP}/conf/broker.conf.orig" "${SNAP_DATA}/broker.conf" diff --git a/snap/hooks/post-refresh b/snap/hooks/post-refresh index de28e364f6..a02ea39c2f 100755 --- a/snap/hooks/post-refresh +++ b/snap/hooks/post-refresh @@ -33,8 +33,7 @@ should_transition_to_allowed_users() { transition_to_allowed_users() { log "Transitioning to allowed users" - mkdir -p "${SNAP_DATA}/broker.conf.d" - cp --update=none "${SNAP}/conf/migrations/pre-${INITIAL_ALLOWED_USERS_VERSION}/broker.conf.d/"* "${SNAP_DATA}/broker.conf.d/" + install -D --target-directory --mode=0700"${SNAP_DATA}/broker.conf.d" "${SNAP}/conf/migrations/pre-${INITIAL_ALLOWED_USERS_VERSION}/broker.conf.d/"* } if [ -z "${PREVIOUS_VERSION}" ]; then From 155883135dbff2395faf84716cc2aeddfaec64bc Mon Sep 17 00:00:00 2001 From: Nikos Date: Thu, 19 Dec 2024 09:16:59 +0100 Subject: [PATCH 0293/1670] fix: add space, improve formatting --- snap/hooks/post-refresh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/snap/hooks/post-refresh b/snap/hooks/post-refresh index a02ea39c2f..02b746b14d 100755 --- a/snap/hooks/post-refresh +++ b/snap/hooks/post-refresh @@ -33,7 +33,8 @@ should_transition_to_allowed_users() { transition_to_allowed_users() { log "Transitioning to allowed users" - install -D --target-directory --mode=0700"${SNAP_DATA}/broker.conf.d" "${SNAP}/conf/migrations/pre-${INITIAL_ALLOWED_USERS_VERSION}/broker.conf.d/"* + install -D --target-directory "${SNAP_DATA}/broker.conf.d" --mode=0700 \ + "${SNAP}/conf/migrations/pre-${INITIAL_ALLOWED_USERS_VERSION}/broker.conf.d/"* } if [ -z "${PREVIOUS_VERSION}" ]; then From 6419b91ac6da87987f5de07b0e90f3f8a751ba7b Mon Sep 17 00:00:00 2001 From: Nikos Date: Thu, 19 Dec 2024 12:11:01 +0100 Subject: [PATCH 0294/1670] fix: check for equal in version_less_than `version_less_than` would return true if the two versions were equal --- snap/hooks/post-refresh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/hooks/post-refresh b/snap/hooks/post-refresh index 02b746b14d..3e25dd0e3f 100755 --- a/snap/hooks/post-refresh +++ b/snap/hooks/post-refresh @@ -17,7 +17,7 @@ log() { } version_less_than() { - [ "$(printf '%s\n' "${@}" | sort -V | head -n1)" = "$1" ] + [ "$1" = "$2" ] && return 1 || [ "$(printf '%s\n' "${@}" | sort -V | head -n1)" = "$1" ] } should_transition_to_allowed_users() { From 4dfc8e89c39768cb5a41599e6ffc3bd1f4c1f349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 19 Dec 2024 07:07:08 +0100 Subject: [PATCH 0295/1670] daemon: Always close the ready channel on error We were not closing it in case broker.New failed, so let's just use a deferred function to ensure that this always happens, unless we're done it already explicitly --- cmd/authd-oidc/daemon/daemon.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/cmd/authd-oidc/daemon/daemon.go b/cmd/authd-oidc/daemon/daemon.go index 100e596ebd..8d687a9cdc 100644 --- a/cmd/authd-oidc/daemon/daemon.go +++ b/cmd/authd-oidc/daemon/daemon.go @@ -116,18 +116,26 @@ func New(name string) *App { // serve creates new dbus service on the system bus. This call is blocking until we quit it. func (a *App) serve(config daemonConfig) error { ctx := context.Background() + // Ensure that the a.ready channel is closed when the function returns, which is what Quit() waits for before exiting. + readyPtr := &a.ready + closeFunc := func() { + if readyPtr == nil { + return + } + close(*readyPtr) + readyPtr = nil + } + defer closeFunc() // When the data directory is SNAP_DATA, it has permission 0755, else we want to create it with 0700. if err := ensureDirWithPerms(config.Paths.DataDir, 0700, os.Geteuid()); err != nil { if err := ensureDirWithPerms(config.Paths.DataDir, 0755, os.Geteuid()); err != nil { - close(a.ready) return fmt.Errorf("error initializing data directory %q: %v", config.Paths.DataDir, err) } } brokerConfigDir := broker.GetDropInDir(config.Paths.BrokerConf) if err := ensureDirWithPerms(brokerConfigDir, 0700, os.Geteuid()); err != nil { - close(a.ready) return fmt.Errorf("error initializing broker configuration directory %q: %v", brokerConfigDir, err) } @@ -142,7 +150,6 @@ func (a *App) serve(config daemonConfig) error { s, err := dbusservice.New(ctx, b) if err != nil { - close(a.ready) return err } @@ -150,12 +157,11 @@ func (a *App) serve(config daemonConfig) error { daemon, err := daemon.New(ctx, s, daemonopts...) if err != nil { _ = s.Stop() - close(a.ready) return err } a.daemon = daemon - close(a.ready) + closeFunc() return daemon.Serve(ctx) } From 19b72b1e5f2506a602d986d090d043dcc4522990 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 08:36:26 +0000 Subject: [PATCH 0296/1670] deps(go): bump golang.org/x/oauth2 from 0.24.0 to 0.25.0 Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.24.0 to 0.25.0. - [Commits](https://github.com/golang/oauth2/compare/v0.24.0...v0.25.0) --- updated-dependencies: - dependency-name: golang.org/x/oauth2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a9e4b71e3c..f9179393ba 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 golang.org/x/crypto v0.31.0 - golang.org/x/oauth2 v0.24.0 + golang.org/x/oauth2 v0.25.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 4fa4274282..484add7043 100644 --- a/go.sum +++ b/go.sum @@ -143,8 +143,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= -golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= -golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= +golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= From 551444fdf8ed4eb2f76347a11969ad83db686505 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 08:36:29 +0000 Subject: [PATCH 0297/1670] deps(go): bump github.com/coreos/go-oidc/v3 from 3.11.0 to 3.12.0 Bumps [github.com/coreos/go-oidc/v3](https://github.com/coreos/go-oidc) from 3.11.0 to 3.12.0. - [Release notes](https://github.com/coreos/go-oidc/releases) - [Commits](https://github.com/coreos/go-oidc/compare/v3.11.0...v3.12.0) --- updated-dependencies: - dependency-name: github.com/coreos/go-oidc/v3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a9e4b71e3c..41f9b982c4 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.23.0 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 - github.com/coreos/go-oidc/v3 v3.11.0 + github.com/coreos/go-oidc/v3 v3.12.0 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/go-jose/go-jose/v4 v4.0.4 github.com/godbus/dbus/v5 v5.1.0 diff --git a/go.sum b/go.sum index 4fa4274282..98b2350876 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xP github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= github.com/cjlapao/common-go v0.0.39 h1:bAAUrj2B9v0kMzbAOhzjSmiyDy+rd56r2sy7oEiQLlA= github.com/cjlapao/common-go v0.0.39/go.mod h1:M3dzazLjTjEtZJbbxoA5ZDiGCiHmpwqW9l4UWaddwOA= -github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI= -github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= +github.com/coreos/go-oidc/v3 v3.12.0 h1:sJk+8G2qq94rDI6ehZ71Bol3oUHy63qNYmkiSjrc/Jo= +github.com/coreos/go-oidc/v3 v3.12.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= From 0e898fa11191b4cd0bdecec3eac8d1e82c177432 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 08:36:37 +0000 Subject: [PATCH 0298/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.54.0 to 1.56.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.54.0...v1.56.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index a9e4b71e3c..6b6876a8eb 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.54.0 + github.com/microsoftgraph/msgraph-sdk-go v1.56.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 github.com/otiai10/copy v1.14.0 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 @@ -44,7 +44,7 @@ require ( github.com/microsoft/kiota-authentication-azure-go v1.1.0 // indirect github.com/microsoft/kiota-http-go v1.4.4 // indirect github.com/microsoft/kiota-serialization-form-go v1.0.0 // indirect - github.com/microsoft/kiota-serialization-json-go v1.0.8 // indirect + github.com/microsoft/kiota-serialization-json-go v1.0.9 // indirect github.com/microsoft/kiota-serialization-multipart-go v1.0.0 // indirect github.com/microsoft/kiota-serialization-text-go v1.0.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect diff --git a/go.sum b/go.sum index 4fa4274282..16d6f8221a 100644 --- a/go.sum +++ b/go.sum @@ -61,14 +61,14 @@ github.com/microsoft/kiota-http-go v1.4.4 h1:HM0KT/Q7o+JsGatFkkbTIqJL24Jzo5eMI5N github.com/microsoft/kiota-http-go v1.4.4/go.mod h1:Kup5nMDD3a9sjdgRKHCqZWqtrv3FbprjcPaGjLR6FzM= github.com/microsoft/kiota-serialization-form-go v1.0.0 h1:UNdrkMnLFqUCccQZerKjblsyVgifS11b3WCx+eFEsAI= github.com/microsoft/kiota-serialization-form-go v1.0.0/go.mod h1:h4mQOO6KVTNciMF6azi1J9QB19ujSw3ULKcSNyXXOMA= -github.com/microsoft/kiota-serialization-json-go v1.0.8 h1:+aViv9k6wqaw1Fx6P49fl5GIB1hN3b6CG0McNTcUYBc= -github.com/microsoft/kiota-serialization-json-go v1.0.8/go.mod h1:O8+v11U0EUwHlCz7hrW38KxDmdhKAHfv4Q89uvsBalY= +github.com/microsoft/kiota-serialization-json-go v1.0.9 h1:lJivec0G0tI6T8McBTnucyyYXczXytwcu1pt0UjWSBY= +github.com/microsoft/kiota-serialization-json-go v1.0.9/go.mod h1:AxrS/Gbmr8y/hIp2pJcpTup/2wCE8ED+VEXkf/9xKb4= github.com/microsoft/kiota-serialization-multipart-go v1.0.0 h1:3O5sb5Zj+moLBiJympbXNaeV07K0d46IfuEd5v9+pBs= github.com/microsoft/kiota-serialization-multipart-go v1.0.0/go.mod h1:yauLeBTpANk4L03XD985akNysG24SnRJGaveZf+p4so= github.com/microsoft/kiota-serialization-text-go v1.0.0 h1:XOaRhAXy+g8ZVpcq7x7a0jlETWnWrEum0RhmbYrTFnA= github.com/microsoft/kiota-serialization-text-go v1.0.0/go.mod h1:sM1/C6ecnQ7IquQOGUrUldaO5wj+9+v7G2W3sQ3fy6M= -github.com/microsoftgraph/msgraph-sdk-go v1.54.0 h1:IOaII4ZYKjkztrw3OVPTn0r/2R2DJKp6E1id753m3Yc= -github.com/microsoftgraph/msgraph-sdk-go v1.54.0/go.mod h1:6bLNNzPle87+8W0MHYKJV8v3lTm8S6UvePMvMbmBL6E= +github.com/microsoftgraph/msgraph-sdk-go v1.56.0 h1:UDdhFrn/2syJrUshZz9vDri8WRcXqPSxVNeC7VI5jcA= +github.com/microsoftgraph/msgraph-sdk-go v1.56.0/go.mod h1:q/0JXFg3C3AJO8he4MkbdGtnzQ4XIw3b6Z2hbqjqAdA= github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 h1:P1wpmn3xxfPMFJHg+PJPcusErfRkl63h6OdAnpDbkS8= github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1/go.mod h1:vFmWQGWyLlhxCESNLv61vlE4qesBU+eWmEVH7DJSESA= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= From 1009b519bbdbf07e4f99694d85ea87d65e540ff7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 08:36:40 +0000 Subject: [PATCH 0299/1670] deps(go): bump github.com/otiai10/copy from 1.14.0 to 1.14.1 Bumps [github.com/otiai10/copy](https://github.com/otiai10/copy) from 1.14.0 to 1.14.1. - [Release notes](https://github.com/otiai10/copy/releases) - [Commits](https://github.com/otiai10/copy/compare/v1.14.0...v1.14.1) --- updated-dependencies: - dependency-name: github.com/otiai10/copy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 3 ++- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index a9e4b71e3c..fceb94a2a3 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/k0kubun/pp v3.0.1+incompatible github.com/microsoftgraph/msgraph-sdk-go v1.54.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 - github.com/otiai10/copy v1.14.0 + github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 @@ -48,6 +48,7 @@ require ( github.com/microsoft/kiota-serialization-multipart-go v1.0.0 // indirect github.com/microsoft/kiota-serialization-text-go v1.0.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/otiai10/mint v1.6.3 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect diff --git a/go.sum b/go.sum index 4fa4274282..86fd3dd8d1 100644 --- a/go.sum +++ b/go.sum @@ -73,10 +73,10 @@ github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 h1:P1wpmn3xxfPMFJHg+PJPcusE github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1/go.mod h1:vFmWQGWyLlhxCESNLv61vlE4qesBU+eWmEVH7DJSESA= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= -github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= -github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= -github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= +github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= +github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I= +github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs= +github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= From 95aebe84155d5251e438573a85b581bd79588e1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 08:44:45 +0000 Subject: [PATCH 0300/1670] deps(go-tools): bump github.com/golangci/golangci-lint in /tools Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.62.2 to 1.63.4. - [Release notes](https://github.com/golangci/golangci-lint/releases) - [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md) - [Commits](https://github.com/golangci/golangci-lint/compare/v1.62.2...v1.63.4) --- updated-dependencies: - dependency-name: github.com/golangci/golangci-lint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- tools/go.mod | 75 ++++++++++--------- tools/go.sum | 204 +++++++++++++++++++++++++++++---------------------- 2 files changed, 156 insertions(+), 123 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index d6e06d84dd..d30c859085 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -2,15 +2,15 @@ module github.com/ubuntu/authd-oidc-brokers/tools go 1.23.0 -require github.com/golangci/golangci-lint v1.62.2 +require github.com/golangci/golangci-lint v1.63.4 require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 4d63.com/gochecknoglobals v0.2.1 // indirect - github.com/4meepo/tagalign v1.3.4 // indirect + github.com/4meepo/tagalign v1.4.1 // indirect github.com/Abirdcfly/dupword v0.1.3 // indirect github.com/Antonboom/errname v1.0.0 // indirect - github.com/Antonboom/nilnil v1.0.0 // indirect + github.com/Antonboom/nilnil v1.0.1 // indirect github.com/Antonboom/testifylint v1.5.2 // indirect github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect github.com/Crocmagnon/fatcontext v0.5.3 // indirect @@ -18,27 +18,28 @@ require ( github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect - github.com/alecthomas/go-check-sumtype v0.2.0 // indirect + github.com/alecthomas/go-check-sumtype v0.3.1 // indirect github.com/alexkohler/nakedret/v2 v2.0.5 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect + github.com/alingse/nilnesserr v0.1.1 // indirect github.com/ashanbrown/forbidigo v1.6.0 // indirect - github.com/ashanbrown/makezero v1.1.1 // indirect + github.com/ashanbrown/makezero v1.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bkielbasa/cyclop v1.2.3 // indirect github.com/blizzy78/varnamelen v0.8.0 // indirect - github.com/bombsimon/wsl/v4 v4.4.1 // indirect + github.com/bombsimon/wsl/v4 v4.5.0 // indirect github.com/breml/bidichk v0.3.2 // indirect github.com/breml/errchkjson v0.4.0 // indirect - github.com/butuzov/ireturn v0.3.0 // indirect - github.com/butuzov/mirror v1.2.0 // indirect + github.com/butuzov/ireturn v0.3.1 // indirect + github.com/butuzov/mirror v1.3.0 // indirect github.com/catenacyber/perfsprint v0.7.1 // indirect github.com/ccojocar/zxcvbn-go v1.0.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/charithe/durationcheck v0.0.10 // indirect github.com/chavacava/garif v0.1.0 // indirect - github.com/ckaznocha/intrange v0.2.1 // indirect - github.com/curioswitch/go-reassign v0.2.0 // indirect + github.com/ckaznocha/intrange v0.3.0 // indirect + github.com/curioswitch/go-reassign v0.3.0 // indirect github.com/daixiang0/gci v0.13.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect @@ -58,15 +59,14 @@ require ( github.com/go-toolsmith/strparse v1.1.0 // indirect github.com/go-toolsmith/typep v1.1.0 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect - github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect + github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect github.com/golangci/go-printf-func-name v0.1.0 // indirect - github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 // indirect + github.com/golangci/gofmt v0.0.0-20241223200906-057b0627d9b9 // indirect github.com/golangci/misspell v0.6.0 // indirect - github.com/golangci/modinfo v0.3.4 // indirect github.com/golangci/plugin-module-register v0.1.1 // indirect github.com/golangci/revgrep v0.5.3 // indirect github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect @@ -76,14 +76,16 @@ require ( github.com/gostaticanalysis/comment v1.4.2 // indirect github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect github.com/gostaticanalysis/nilerr v0.1.1 // indirect + github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect github.com/hashicorp/go-version v1.7.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jgautheron/goconst v1.7.1 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect - github.com/jjti/go-spancheck v0.6.2 // indirect - github.com/julz/importas v0.1.0 // indirect + github.com/jjti/go-spancheck v0.6.4 // indirect + github.com/julz/importas v0.2.0 // indirect github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect github.com/kisielk/errcheck v1.8.0 // indirect github.com/kkHAIKE/contextcheck v1.1.5 // indirect @@ -91,8 +93,11 @@ require ( github.com/kunwardeep/paralleltest v1.0.10 // indirect github.com/kyoh86/exportloopref v0.1.11 // indirect github.com/lasiar/canonicalheader v1.1.2 // indirect - github.com/ldez/gomoddirectives v0.2.4 // indirect - github.com/ldez/tagliatelle v0.5.0 // indirect + github.com/ldez/exptostd v0.3.1 // indirect + github.com/ldez/gomoddirectives v0.6.0 // indirect + github.com/ldez/grignotin v0.7.0 // indirect + github.com/ldez/tagliatelle v0.7.1 // indirect + github.com/ldez/usetesting v0.4.2 // indirect github.com/leonklingele/grouper v1.1.2 // indirect github.com/macabu/inamedparam v0.1.3 // indirect github.com/magiconair/properties v1.8.6 // indirect @@ -110,7 +115,7 @@ require ( github.com/nakabonne/nestif v0.3.1 // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect - github.com/nunnatsa/ginkgolinter v0.18.3 // indirect + github.com/nunnatsa/ginkgolinter v0.18.4 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect @@ -125,15 +130,15 @@ require ( github.com/quasilyte/gogrep v0.5.0 // indirect github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect - github.com/raeperd/recvcheck v0.1.2 // indirect + github.com/raeperd/recvcheck v0.2.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/ryancurrah/gomodguard v1.3.5 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect - github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect - github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect + github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect - github.com/sashamelentyev/usestdlibvars v1.27.0 // indirect + github.com/sashamelentyev/usestdlibvars v1.28.0 // indirect github.com/securego/gosec/v2 v2.21.4 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect github.com/sirupsen/logrus v1.9.3 // indirect @@ -148,20 +153,20 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.12.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect - github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect + github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/testify v1.10.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect - github.com/tdakkota/asciicheck v0.2.0 // indirect - github.com/tetafro/godot v1.4.18 // indirect - github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect + github.com/tdakkota/asciicheck v0.3.0 // indirect + github.com/tetafro/godot v1.4.20 // indirect + github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 // indirect github.com/timonwong/loggercheck v0.10.1 // indirect - github.com/tomarrell/wrapcheck/v2 v2.9.0 // indirect + github.com/tomarrell/wrapcheck/v2 v2.10.0 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect - github.com/ultraware/funlen v0.1.0 // indirect - github.com/ultraware/whitespace v0.1.1 // indirect - github.com/uudashr/gocognit v1.1.3 // indirect - github.com/uudashr/iface v1.2.1 // indirect + github.com/ultraware/funlen v0.2.0 // indirect + github.com/ultraware/whitespace v0.2.0 // indirect + github.com/uudashr/gocognit v1.2.0 // indirect + github.com/uudashr/iface v1.3.0 // indirect github.com/xen0n/gosmopolitan v1.2.2 // indirect github.com/yagipy/maintidx v1.0.0 // indirect github.com/yeya24/promlinter v0.3.0 // indirect @@ -176,10 +181,10 @@ require ( golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f // indirect golang.org/x/mod v0.22.0 // indirect - golang.org/x/sync v0.9.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/text v0.18.0 // indirect - golang.org/x/tools v0.27.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.20.0 // indirect + golang.org/x/tools v0.28.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/tools/go.sum b/tools/go.sum index 20fd8682dc..a698f87148 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -35,14 +35,14 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/4meepo/tagalign v1.3.4 h1:P51VcvBnf04YkHzjfclN6BbsopfJR5rxs1n+5zHt+w8= -github.com/4meepo/tagalign v1.3.4/go.mod h1:M+pnkHH2vG8+qhE5bVc/zeP7HS/j910Fwa9TUSyZVI0= +github.com/4meepo/tagalign v1.4.1 h1:GYTu2FaPGOGb/xJalcqHeD4il5BiCywyEYZOA55P6J4= +github.com/4meepo/tagalign v1.4.1/go.mod h1:2H9Yu6sZ67hmuraFgfZkNcg5Py9Ch/Om9l2K/2W1qS4= github.com/Abirdcfly/dupword v0.1.3 h1:9Pa1NuAsZvpFPi9Pqkd93I7LIYRURj+A//dFd5tgBeE= github.com/Abirdcfly/dupword v0.1.3/go.mod h1:8VbB2t7e10KRNdwTVoxdBaxla6avbhGzb8sCTygUMhw= github.com/Antonboom/errname v1.0.0 h1:oJOOWR07vS1kRusl6YRSlat7HFnb3mSfMl6sDMRoTBA= github.com/Antonboom/errname v1.0.0/go.mod h1:gMOBFzK/vrTiXN9Oh+HFs+e6Ndl0eTFbtsRTSRdXyGI= -github.com/Antonboom/nilnil v1.0.0 h1:n+v+B12dsE5tbAqRODXmEKfZv9j2KcTBrp+LkoM4HZk= -github.com/Antonboom/nilnil v1.0.0/go.mod h1:fDJ1FSFoLN6yoG65ANb1WihItf6qt9PJVTn/s2IrcII= +github.com/Antonboom/nilnil v1.0.1 h1:C3Tkm0KUxgfO4Duk3PM+ztPncTFlOf0b2qadmS0s4xs= +github.com/Antonboom/nilnil v1.0.1/go.mod h1:CH7pW2JsRNFgEh8B2UaPZTEPhCMuFowP/e8Udp9Nnb0= github.com/Antonboom/testifylint v1.5.2 h1:4s3Xhuv5AvdIgbd8wOOEeo0uZG7PbDKQyKY5lGoQazk= github.com/Antonboom/testifylint v1.5.2/go.mod h1:vxy8VJ0bc6NavlYqjZfmp6EfqXMtBgQ4+mhCojwC1P8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -59,12 +59,12 @@ github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+ github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ= -github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= -github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= -github.com/alecthomas/go-check-sumtype v0.2.0 h1:Bo+e4DFf3rs7ME9w/0SU/g6nmzJaphduP8Cjiz0gbwY= -github.com/alecthomas/go-check-sumtype v0.2.0/go.mod h1:WyYPfhfkdhyrdaligV6svFopZV8Lqdzn5pyVBaV6jhQ= -github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= -github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU= +github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -76,10 +76,12 @@ github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pO github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= +github.com/alingse/nilnesserr v0.1.1 h1:7cYuJewpy9jFNMEA72Q1+3Nm3zKHzg+Q28D5f2bBFUA= +github.com/alingse/nilnesserr v0.1.1/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY= github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= -github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= -github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= +github.com/ashanbrown/makezero v1.2.0 h1:/2Lp1bypdmK9wDIq7uWBlDF1iMUpIIS4A+pF6C9IEUU= +github.com/ashanbrown/makezero v1.2.0/go.mod h1:dxlPhHbDMC6N6xICzFBSK+4njQDdK8euNO0qjQMtGY4= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -90,16 +92,16 @@ github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5 github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo= github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= -github.com/bombsimon/wsl/v4 v4.4.1 h1:jfUaCkN+aUpobrMO24zwyAMwMAV5eSziCkOKEauOLdw= -github.com/bombsimon/wsl/v4 v4.4.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo= +github.com/bombsimon/wsl/v4 v4.5.0 h1:iZRsEvDdyhd2La0FVi5k6tYehpOR/R7qIUjmKk7N74A= +github.com/bombsimon/wsl/v4 v4.5.0/go.mod h1:NOQ3aLF4nD7N5YPXMruR6ZXDOAqLoM0GEpLwTdvmOSc= github.com/breml/bidichk v0.3.2 h1:xV4flJ9V5xWTqxL+/PMFF6dtJPvZLPsyixAoPe8BGJs= github.com/breml/bidichk v0.3.2/go.mod h1:VzFLBxuYtT23z5+iVkamXO386OB+/sVwZOpIj6zXGos= github.com/breml/errchkjson v0.4.0 h1:gftf6uWZMtIa/Is3XJgibewBm2ksAQSY/kABDNFTAdk= github.com/breml/errchkjson v0.4.0/go.mod h1:AuBOSTHyLSaaAFlWsRSuRBIroCh3eh7ZHh5YeelDIk8= -github.com/butuzov/ireturn v0.3.0 h1:hTjMqWw3y5JC3kpnC5vXmFJAWI/m31jaCYQqzkS6PL0= -github.com/butuzov/ireturn v0.3.0/go.mod h1:A09nIiwiqzN/IoVo9ogpa0Hzi9fex1kd9PSD6edP5ZA= -github.com/butuzov/mirror v1.2.0 h1:9YVK1qIjNspaqWutSv8gsge2e/Xpq1eqEkslEUHy5cs= -github.com/butuzov/mirror v1.2.0/go.mod h1:DqZZDtzm42wIAIyHXeN8W/qb1EPlb9Qn/if9icBOpdQ= +github.com/butuzov/ireturn v0.3.1 h1:mFgbEI6m+9W8oP/oDdfA34dLisRFCj2G6o/yiI1yZrY= +github.com/butuzov/ireturn v0.3.1/go.mod h1:ZfRp+E7eJLC0NQmk1Nrm1LOrn/gQlOykv+cVPdiXH5M= +github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc= +github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= github.com/catenacyber/perfsprint v0.7.1 h1:PGW5G/Kxn+YrN04cRAZKC+ZuvlVwolYMrIyyTJ/rMmc= github.com/catenacyber/perfsprint v0.7.1/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= @@ -115,13 +117,13 @@ github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+U github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/ckaznocha/intrange v0.2.1 h1:M07spnNEQoALOJhwrImSrJLaxwuiQK+hA2DeajBlwYk= -github.com/ckaznocha/intrange v0.2.1/go.mod h1:7NEhVyf8fzZO5Ds7CRaqPEm52Ut83hsTiL5zbER/HYk= +github.com/ckaznocha/intrange v0.3.0 h1:VqnxtK32pxgkhJgYQEeOArVidIPg+ahLP7WBOXZd5ZY= +github.com/ckaznocha/intrange v0.3.0/go.mod h1:+I/o2d2A1FBHgGELbGxzIcyd3/9l9DuwjM8FsbSS3Lo= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= -github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= +github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= +github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= github.com/daixiang0/gci v0.13.5 h1:kThgmH1yBmZSBCh1EJVxQ7JsHpm5Oms0AMed/0LaH4c= github.com/daixiang0/gci v0.13.5/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -129,6 +131,8 @@ 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/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= 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= @@ -188,8 +192,8 @@ github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUN github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U= -github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= +github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY= +github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= @@ -228,14 +232,12 @@ github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9 github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU= github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s= -github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 h1:/1322Qns6BtQxUZDTAT4SdcoxknUki7IAoK4SAXr8ME= -github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9/go.mod h1:Oesb/0uFAyWoaw1U1qS5zyjCg5NP9C9iwjnI4tIsXEE= -github.com/golangci/golangci-lint v1.62.2 h1:b8K5K9PN+rZN1+mKLtsZHz2XXS9aYKzQ9i25x3Qnxxw= -github.com/golangci/golangci-lint v1.62.2/go.mod h1:ILWWyeFUrctpHVGMa1dg2xZPKoMUTc5OIMgW7HZr34g= +github.com/golangci/gofmt v0.0.0-20241223200906-057b0627d9b9 h1:t5wybL6RtO83VwoMOb7U/Peqe3gGKQlPIC66wXmnkvM= +github.com/golangci/gofmt v0.0.0-20241223200906-057b0627d9b9/go.mod h1:Ag3L7sh7E28qAp/5xnpMMTuGYqxLZoSaEHZDkZB1RgU= +github.com/golangci/golangci-lint v1.63.4 h1:bJQFQ3hSfUto597dkL7ipDzOxsGEpiWdLiZ359OWOBI= +github.com/golangci/golangci-lint v1.63.4/go.mod h1:Hx0B7Lg5/NXbaOHem8+KU+ZUIzMI6zNj/7tFwdnn10I= github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= -github.com/golangci/modinfo v0.3.4 h1:oU5huX3fbxqQXdfspamej74DFX0kyGLkw1ppvXoJ8GA= -github.com/golangci/modinfo v0.3.4/go.mod h1:wytF1M5xl9u0ij8YSvhkEVPP3M5Mc7XLl1pxH3B2aUM= github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= github.com/golangci/revgrep v0.5.3 h1:3tL7c1XBMtWHHqVpS5ChmiAAoe4PF/d5+ULzV9sLAzs= @@ -285,13 +287,19 @@ github.com/gostaticanalysis/forcetypeassert v0.1.0/go.mod h1:qZEedyP/sY1lTGV1uJ3 github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk= github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= -github.com/gostaticanalysis/testutil v0.4.0 h1:nhdCmubdmDF6VEatUNjgUZBJKWRqugoISdUv3PPQgHY= -github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU= +github.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+3Zh0sUGqw8= +github.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs= +github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo= +github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= @@ -303,8 +311,8 @@ github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5 github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= -github.com/jjti/go-spancheck v0.6.2 h1:iYtoxqPMzHUPp7St+5yA8+cONdyXD3ug6KK15n7Pklk= -github.com/jjti/go-spancheck v0.6.2/go.mod h1:+X7lvIrR5ZdUTkxFYqzJ0abr8Sb5LOo80uOhWNqIrYA= +github.com/jjti/go-spancheck v0.6.4 h1:Tl7gQpYf4/TMU7AT84MN83/6PutY21Nb9fuQjFTpRRc= +github.com/jjti/go-spancheck v0.6.4/go.mod h1:yAEYdKJ2lRkDA8g7X+oKUHXOWVAXSBJRv04OhF+QUjk= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -314,8 +322,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= -github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= +github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ= +github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY= github.com/karamaru-alpha/copyloopvar v1.1.0 h1:x7gNyKcC2vRBO1H2Mks5u1VxQtYvFiym7fCjIP8RPos= github.com/karamaru-alpha/copyloopvar v1.1.0/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= github.com/kisielk/errcheck v1.8.0 h1:ZX/URYa7ilESY19ik/vBmCn6zdGQLxACwjAcWbHlYlg= @@ -341,10 +349,16 @@ github.com/kyoh86/exportloopref v0.1.11 h1:1Z0bcmTypkL3Q4k+IDHMWTcnCliEZcaPiIe0/ github.com/kyoh86/exportloopref v0.1.11/go.mod h1:qkV4UF1zGl6EkF1ox8L5t9SwyeBAZ3qLMd6up458uqA= github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= -github.com/ldez/gomoddirectives v0.2.4 h1:j3YjBIjEBbqZ0NKtBNzr8rtMHTOrLPeiwTkfUJZ3alg= -github.com/ldez/gomoddirectives v0.2.4/go.mod h1:oWu9i62VcQDYp9EQ0ONTfqLNh+mDLWWDO+SO0qSQw5g= -github.com/ldez/tagliatelle v0.5.0 h1:epgfuYt9v0CG3fms0pEgIMNPuFf/LpPIfjk4kyqSioo= -github.com/ldez/tagliatelle v0.5.0/go.mod h1:rj1HmWiL1MiKQuOONhd09iySTEkUuE/8+5jtPYz9xa4= +github.com/ldez/exptostd v0.3.1 h1:90yWWoAKMFHeovTK8uzBms9Ppp8Du/xQ20DRO26Ymrw= +github.com/ldez/exptostd v0.3.1/go.mod h1:iZBRYaUmcW5jwCR3KROEZ1KivQQp6PHXbDPk9hqJKCQ= +github.com/ldez/gomoddirectives v0.6.0 h1:Jyf1ZdTeiIB4dd+2n4qw+g4aI9IJ6JyfOZ8BityWvnA= +github.com/ldez/gomoddirectives v0.6.0/go.mod h1:TuwOGYoPAoENDWQpe8DMqEm5nIfjrxZXmxX/CExWyZ4= +github.com/ldez/grignotin v0.7.0 h1:vh0dI32WhHaq6LLPZ38g7WxXuZ1+RzyrJ7iPG9JMa8c= +github.com/ldez/grignotin v0.7.0/go.mod h1:uaVTr0SoZ1KBii33c47O1M8Jp3OP3YDwhZCmzT9GHEk= +github.com/ldez/tagliatelle v0.7.1 h1:bTgKjjc2sQcsgPiT902+aadvMjCeMHrY7ly2XKFORIk= +github.com/ldez/tagliatelle v0.7.1/go.mod h1:3zjxUpsNB2aEZScWiZTHrAXOl1x25t3cRmzfK1mlo2I= +github.com/ldez/usetesting v0.4.2 h1:J2WwbrFGk3wx4cZwSMiCQQ00kjGR0+tuuyW0Lqm4lwA= +github.com/ldez/usetesting v0.4.2/go.mod h1:eEs46T3PpQ+9RgN9VjpY6qWdiw2/QmfiDeWmdZdrjIQ= github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk= @@ -390,8 +404,8 @@ github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhK github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/nunnatsa/ginkgolinter v0.18.3 h1:WgS7X3zzmni3vwHSBhvSgqrRgUecN6PQUcfB0j1noDw= -github.com/nunnatsa/ginkgolinter v0.18.3/go.mod h1:BE1xyB/PNtXXG1azrvrqJW5eFH0hSRylNzFy8QHPwzs= +github.com/nunnatsa/ginkgolinter v0.18.4 h1:zmX4KUR+6fk/vhUFt8DOP6KwznekhkmVSzzVJve2vyM= +github.com/nunnatsa/ginkgolinter v0.18.4/go.mod h1:AMEane4QQ6JwFz5GgjI5xLUM9S/CylO+UyM97fN2iBI= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= @@ -451,8 +465,8 @@ github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= -github.com/raeperd/recvcheck v0.1.2 h1:SjdquRsRXJc26eSonWIo8b7IMtKD3OAT2Lb5G3ZX1+4= -github.com/raeperd/recvcheck v0.1.2/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= +github.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI= +github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -464,14 +478,14 @@ github.com/ryancurrah/gomodguard v1.3.5 h1:cShyguSwUEeC0jS7ylOiG/idnd1TpJ1LfHGpV github.com/ryancurrah/gomodguard v1.3.5/go.mod h1:MXlEPQRxgfPQa62O8wzK3Ozbkv9Rkqr+wKjSxTdsNJE= github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= -github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/NsJFwFK7Uxc= -github.com/sanposhiho/wastedassign/v2 v2.0.7/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= -github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= -github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= +github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0= +github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= -github.com/sashamelentyev/usestdlibvars v1.27.0 h1:t/3jZpSXtRPRf2xr0m63i32ZrusyurIGT9E5wAvXQnI= -github.com/sashamelentyev/usestdlibvars v1.27.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= +github.com/sashamelentyev/usestdlibvars v1.28.0 h1:jZnudE2zKCtYlGzLVreNp5pmCdOxXUzwsMDBkR21cyQ= +github.com/sashamelentyev/usestdlibvars v1.28.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= github.com/securego/gosec/v2 v2.21.4 h1:Le8MSj0PDmOnHJgUATjD96PaXRvCpKC+DGJvwyy0Mlk= github.com/securego/gosec/v2 v2.21.4/go.mod h1:Jtb/MwRQfRxCXyCm1rfM1BEiiiTfUOdyzzAhlr6lUTA= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= @@ -505,8 +519,8 @@ github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= -github.com/stbenjam/no-sprintf-host-port v0.1.1 h1:tYugd/yrm1O0dV+ThCbaKZh195Dfm07ysF0U6JQXczc= -github.com/stbenjam/no-sprintf-host-port v0.1.1/go.mod h1:TLhvtIvONRzdmkFiio4O8LHsN9N74I+PhRquPsxpL0I= +github.com/stbenjam/no-sprintf-host-port v0.2.0 h1:i8pxvGrt1+4G0czLr/WnmyH7zbZ8Bg8etvARQ1rpyl4= +github.com/stbenjam/no-sprintf-host-port v0.2.0/go.mod h1:eL0bQ9PasS0hsyTyfTjjG+E80QIyPnBVQbYZyv20Jfk= 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= @@ -525,30 +539,30 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/tdakkota/asciicheck v0.2.0 h1:o8jvnUANo0qXtnslk2d3nMKTFNlOnJjRrNcj0j9qkHM= -github.com/tdakkota/asciicheck v0.2.0/go.mod h1:Qb7Y9EgjCLJGup51gDHFzbI08/gbGhL/UVhYIPWG2rg= +github.com/tdakkota/asciicheck v0.3.0 h1:LqDGgZdholxZMaJgpM6b0U9CFIjDCbFdUF00bDnBKOQ= +github.com/tdakkota/asciicheck v0.3.0/go.mod h1:KoJKXuX/Z/lt6XzLo8WMBfQGzak0SrAKZlvRr4tg8Ac= github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= -github.com/tetafro/godot v1.4.18 h1:ouX3XGiziKDypbpXqShBfnNLTSjR8r3/HVzrtJ+bHlI= -github.com/tetafro/godot v1.4.18/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= -github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 h1:quvGphlmUVU+nhpFa4gg4yJyTRJ13reZMDHrKwYw53M= -github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966/go.mod h1:27bSVNWSBOHm+qRp1T9qzaIpsWEP6TbUnei/43HK+PQ= +github.com/tetafro/godot v1.4.20 h1:z/p8Ek55UdNvzt4TFn2zx2KscpW4rWqcnUrdmvWJj7E= +github.com/tetafro/godot v1.4.20/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= +github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 h1:y4mJRFlM6fUyPhoXuFg/Yu02fg/nIPFMOY8tOqppoFg= +github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= github.com/timonwong/loggercheck v0.10.1 h1:uVZYClxQFpw55eh+PIoqM7uAOHMrhVcDoWDery9R8Lg= github.com/timonwong/loggercheck v0.10.1/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= -github.com/tomarrell/wrapcheck/v2 v2.9.0 h1:801U2YCAjLhdN8zhZ/7tdjB3EnAoRlJHt/s+9hijLQ4= -github.com/tomarrell/wrapcheck/v2 v2.9.0/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= +github.com/tomarrell/wrapcheck/v2 v2.10.0 h1:SzRCryzy4IrAH7bVGG4cK40tNUhmVmMDuJujy4XwYDg= +github.com/tomarrell/wrapcheck/v2 v2.10.0/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= -github.com/ultraware/funlen v0.1.0 h1:BuqclbkY6pO+cvxoq7OsktIXZpgBSkYTQtmwhAK81vI= -github.com/ultraware/funlen v0.1.0/go.mod h1:XJqmOQja6DpxarLj6Jj1U7JuoS8PvL4nEqDaQhy22p4= -github.com/ultraware/whitespace v0.1.1 h1:bTPOGejYFulW3PkcrqkeQwOd6NKOOXvmGD9bo/Gk8VQ= -github.com/ultraware/whitespace v0.1.1/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= -github.com/uudashr/gocognit v1.1.3 h1:l+a111VcDbKfynh+airAy/DJQKaXh2m9vkoysMPSZyM= -github.com/uudashr/gocognit v1.1.3/go.mod h1:aKH8/e8xbTRBwjbCkwZ8qt4l2EpKXl31KMHgSS+lZ2U= -github.com/uudashr/iface v1.2.1 h1:vHHyzAUmWZ64Olq6NZT3vg/z1Ws56kyPdBOd5kTXDF8= -github.com/uudashr/iface v1.2.1/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg= +github.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI= +github.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA= +github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g= +github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= +github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA= +github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU= +github.com/uudashr/iface v1.3.0 h1:zwPch0fs9tdh9BmL5kcgSpvnObV+yHjO4JjVBl8IA10= +github.com/uudashr/iface v1.3.0/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg= github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= @@ -594,7 +608,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U 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.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -633,12 +648,13 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -675,12 +691,15 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= +golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= 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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -700,8 +719,10 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -735,7 +756,6 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -750,19 +770,24 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc 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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -773,8 +798,11 @@ 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.6.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.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -825,20 +853,20 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= -golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= +golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From b2eede5d1f7aa15fccfe219dc796f82a19633901 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 7 Jan 2025 11:33:45 +0100 Subject: [PATCH 0301/1670] Use the msgraph host from the OpenID provider metadata GCC High tenants use a different msgraph host. The hostname is part of the provider metadata that's retrieved during OpenID Connect Discovery via the .well-known/openid-configuration path. This commit uses that hostname from the retrieved provider metadata in order to support GCC High tenants. --- internal/broker/broker.go | 10 ++++++- internal/providers/msentraid/msentraid.go | 33 ++++++++++++++++++--- internal/providers/noprovider/noprovider.go | 7 ++++- internal/providers/providers.go | 3 +- internal/testutils/provider.go | 7 ++++- internal/token/token.go | 9 +++--- 6 files changed, 57 insertions(+), 12 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index d6041398bd..181f5c9b38 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -520,6 +520,13 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au } authInfo = token.NewAuthCachedInfo(t, rawIDToken, b.provider) + + authInfo.ProviderMetadata, err = b.provider.GetMetadata(session.oidcServer) + if err != nil { + slog.Error(err.Error()) + return AuthDenied, errorMessage{Message: "could not get provider metadata"} + } + authInfo.UserInfo, err = b.fetchUserInfo(ctx, session, &authInfo) if err != nil { slog.Error(err.Error()) @@ -781,6 +788,7 @@ func (b *Broker) refreshToken(ctx context.Context, oauth2Config oauth2.Config, o } t := token.NewAuthCachedInfo(oauthToken, rawIDToken, b.provider) + t.ProviderMetadata = oldToken.ProviderMetadata t.UserInfo = oldToken.UserInfo return t, nil } @@ -795,7 +803,7 @@ func (b *Broker) fetchUserInfo(ctx context.Context, session *session, t *token.A return info.User{}, fmt.Errorf("could not verify token: %v", err) } - userInfo, err = b.provider.GetUserInfo(ctx, t.Token, idToken) + userInfo, err = b.provider.GetUserInfo(ctx, t.Token, idToken, t.ProviderMetadata) if err != nil { return info.User{}, fmt.Errorf("could not get user info: %w", err) } diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index a5ee0bef8f..80fd2e8e05 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -28,7 +28,11 @@ func init() { pp.ColoringEnabled = false } -const localGroupPrefix = "linux-" +const ( + localGroupPrefix = "linux-" + defaultMSGraphHost = "graph.microsoft.com" + msgraphAPIVersion = "v1.0" +) // Provider is the Microsoft Entra ID provider implementation. type Provider struct { @@ -86,14 +90,34 @@ func (p Provider) GetExtraFields(token *oauth2.Token) map[string]interface{} { } } +// GetMetadata returns relevant metadata about the provider. +func (p Provider) GetMetadata(provider *oidc.Provider) (map[string]interface{}, error) { + var claims struct { + MSGraphHost string `json:"msgraph_host"` + } + + if err := provider.Claims(&claims); err != nil { + return nil, fmt.Errorf("failed to get provider claims: %v", err) + } + + return map[string]interface{}{ + "msgraph_host": claims.MSGraphHost, + }, nil +} + // GetUserInfo is a no-op when no specific provider is in use. -func (p Provider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken) (info.User, error) { +func (p Provider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken, providerMetadata map[string]interface{}) (info.User, error) { + msgraphHost := providerMetadata["msgraph_host"] + if msgraphHost == nil { + msgraphHost = defaultMSGraphHost + } + userClaims, err := p.userClaims(idToken) if err != nil { return info.User{}, err } - userGroups, err := p.getGroups(accessToken) + userGroups, err := p.getGroups(accessToken, msgraphHost.(string)) if err != nil { return info.User{}, err } @@ -126,7 +150,7 @@ func (p Provider) userClaims(idToken *oidc.IDToken) (claims, error) { } // getGroups access the Microsoft Graph API to get the groups the user is a member of. -func (p Provider) getGroups(token *oauth2.Token) ([]info.Group, error) { +func (p Provider) getGroups(token *oauth2.Token, msgraphHost string) ([]info.Group, error) { slog.Debug("Getting user groups from Microsoft Graph API") // Check if the token has the GroupMember.Read.All scope @@ -148,6 +172,7 @@ func (p Provider) getGroups(token *oauth2.Token) ([]info.Group, error) { if err != nil { return nil, fmt.Errorf("failed to create GraphRequestAdapter: %v", err) } + adapter.SetBaseUrl(fmt.Sprintf("https://%s/%s", msgraphHost, msgraphAPIVersion)) client := msgraphsdk.NewGraphServiceClient(adapter) diff --git a/internal/providers/noprovider/noprovider.go b/internal/providers/noprovider/noprovider.go index 7d261954da..47ccf130d3 100644 --- a/internal/providers/noprovider/noprovider.go +++ b/internal/providers/noprovider/noprovider.go @@ -84,8 +84,13 @@ func (p NoProvider) GetExtraFields(token *oauth2.Token) map[string]interface{} { return nil } +// GetMetadata is a no-op when no specific provider is in use. +func (p NoProvider) GetMetadata(provider *oidc.Provider) (map[string]interface{}, error) { + return nil, nil +} + // GetUserInfo is a no-op when no specific provider is in use. -func (p NoProvider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken) (info.User, error) { +func (p NoProvider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken, providerMetadata map[string]interface{}) (info.User, error) { userClaims, err := p.userClaims(idToken) if err != nil { return info.User{}, err diff --git a/internal/providers/providers.go b/internal/providers/providers.go index c13e170b24..ccc8cfa9b5 100644 --- a/internal/providers/providers.go +++ b/internal/providers/providers.go @@ -23,7 +23,8 @@ type Provider interface { currentAuthStep int, ) ([]string, error) GetExtraFields(token *oauth2.Token) map[string]interface{} - GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken) (info.User, error) + GetMetadata(provider *oidc.Provider) (map[string]interface{}, error) + GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken, providerMetadata map[string]interface{}) (info.User, error) NormalizeUsername(username string) string VerifyUsername(requestedUsername, authenticatedUsername string) error } diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index 4f55cfaf4a..d383e506ba 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -378,8 +378,13 @@ func (p *MockProvider) NormalizeUsername(username string) string { return strings.ToLower(username) } +// GetMetadata is a no-op when no specific provider is in use. +func (p *MockProvider) GetMetadata(provider *oidc.Provider) (map[string]interface{}, error) { + return nil, nil +} + // GetUserInfo is a no-op when no specific provider is in use. -func (p *MockProvider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken) (info.User, error) { +func (p *MockProvider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken, providerMetadata map[string]interface{}) (info.User, error) { if p.GetUserInfoFails { return info.User{}, errors.New("error requested in the mock") } diff --git a/internal/token/token.go b/internal/token/token.go index 917d0e111b..baa2692a84 100644 --- a/internal/token/token.go +++ b/internal/token/token.go @@ -14,10 +14,11 @@ import ( // AuthCachedInfo represents the token that will be saved on disk for offline authentication. type AuthCachedInfo struct { - Token *oauth2.Token - ExtraFields map[string]interface{} - RawIDToken string - UserInfo info.User + Token *oauth2.Token + ExtraFields map[string]interface{} + RawIDToken string + ProviderMetadata map[string]interface{} + UserInfo info.User } // NewAuthCachedInfo creates a new AuthCachedInfo. It sets the provided token and rawIDToken and the provider-specific From 7870515c9fa4609d7dd9a11db8a8d6254a7c5c28 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 7 Jan 2025 15:15:01 +0100 Subject: [PATCH 0302/1670] Create broker.conf with permissions 0600 --- snap/hooks/install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/hooks/install b/snap/hooks/install index 8085039c3f..da8db26333 100755 --- a/snap/hooks/install +++ b/snap/hooks/install @@ -2,4 +2,4 @@ set -eu -install --mode=0700 "${SNAP}/conf/broker.conf.orig" "${SNAP_DATA}/broker.conf" +install --mode=0600 "${SNAP}/conf/broker.conf.orig" "${SNAP_DATA}/broker.conf" From 0bc41514fc0bab9a7a4aa8df1580773cd83ea7a5 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 7 Jan 2025 15:15:34 +0100 Subject: [PATCH 0303/1670] Fix installation of snap failing with "install: Permission denied" For an unclear reason, the `install` command fails with a permission denied error. --- snap/hooks/install | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/snap/hooks/install b/snap/hooks/install index da8db26333..ca17529d9c 100755 --- a/snap/hooks/install +++ b/snap/hooks/install @@ -2,4 +2,5 @@ set -eu -install --mode=0600 "${SNAP}/conf/broker.conf.orig" "${SNAP_DATA}/broker.conf" +cp --update=none "${SNAP}/conf/broker.conf.orig" "${SNAP_DATA}/broker.conf" +chmod 0600 "${SNAP_DATA}/broker.conf" From 7e3c5b547f94aa9a833ebf6080bae63b76695213 Mon Sep 17 00:00:00 2001 From: Didier Roche Date: Tue, 7 Jan 2025 15:26:37 +0100 Subject: [PATCH 0304/1670] Auto update of dedicated google branch. This update should be in the main branch to trigger automatically. --- .github/workflows/auto-updates.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto-updates.yml b/.github/workflows/auto-updates.yml index 87715a6cdc..47a545dc20 100644 --- a/.github/workflows/auto-updates.yml +++ b/.github/workflows/auto-updates.yml @@ -18,7 +18,7 @@ jobs: name: Update snap branches strategy: matrix: - branch_name: ["msentraid"] + branch_name: ["google","msentraid"] runs-on: ubuntu-latest steps: - name: Install dependencies From 947ddff6a0cfad33332f764802dee26c45ef4371 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 7 Jan 2025 16:05:30 +0100 Subject: [PATCH 0305/1670] Fix snap post-refresh hook Same as issue as already fixed for the install hook in 0bc41514fc0bab9a7a4aa8df1580773cd83ea7a5. --- snap/hooks/post-refresh | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/snap/hooks/post-refresh b/snap/hooks/post-refresh index 3e25dd0e3f..3915c6310d 100755 --- a/snap/hooks/post-refresh +++ b/snap/hooks/post-refresh @@ -33,8 +33,15 @@ should_transition_to_allowed_users() { transition_to_allowed_users() { log "Transitioning to allowed users" - install -D --target-directory "${SNAP_DATA}/broker.conf.d" --mode=0700 \ - "${SNAP}/conf/migrations/pre-${INITIAL_ALLOWED_USERS_VERSION}/broker.conf.d/"* + src_dir="${SNAP}/conf/migrations/pre-${INITIAL_ALLOWED_USERS_VERSION}/broker.conf.d" + dest_dir="${SNAP_DATA}/broker.conf.d" + # shellcheck disable=SC2174 # it's fine that --mode only applies to the deepest directory, because the SNAP_DATA + # directory is created by snapd with the correct permissions. + mkdir -p --mode=0700 "${dest_dir}" + for f in "${src_dir}"/*; do + cp --update=none "${f}" "${dest_dir}" + chmod 0600 "${dest_dir}/$(basename "${f}")" + done } if [ -z "${PREVIOUS_VERSION}" ]; then From 1a0705fb6c55c2aa5e200a834acd45bf4000d2c8 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 7 Jan 2025 17:26:51 +0100 Subject: [PATCH 0306/1670] Fix comment --- internal/providers/msentraid/msentraid.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 80fd2e8e05..9cbe24c788 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -105,7 +105,8 @@ func (p Provider) GetMetadata(provider *oidc.Provider) (map[string]interface{}, }, nil } -// GetUserInfo is a no-op when no specific provider is in use. +// GetUserInfo returns the user info from the ID token and the groups the user is a member of, which are retrieved via +// the Microsoft Graph API. func (p Provider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken, providerMetadata map[string]interface{}) (info.User, error) { msgraphHost := providerMetadata["msgraph_host"] if msgraphHost == nil { From 49247cdd48ff6b7467c132203ce92719f18050c0 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 7 Jan 2025 14:15:53 +0100 Subject: [PATCH 0307/1670] Hide --config flag With 7a4d98054cb7d3d719e3a6f0700ccb6dc67e69b6, we automatically generate config files in the `.d` directory of the config file. We don't want to create those in `$PWD/mybroker.conf.d` when `--config mybroker.conf` is used. To avoid additional complexity, we decided to keep the current behavior but hide the flag from the usage message, so that it's only used for debugging and testing. --- cmd/authd-oidc/daemon/config.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/authd-oidc/daemon/config.go b/cmd/authd-oidc/daemon/config.go index 39f4d951bf..4c9dca319a 100644 --- a/cmd/authd-oidc/daemon/config.go +++ b/cmd/authd-oidc/daemon/config.go @@ -82,7 +82,11 @@ func initViperConfig(name string, cmd *cobra.Command, vip *viper.Viper) (err err // installConfigFlag installs a --config option. func installConfigFlag(cmd *cobra.Command) *string { - return cmd.PersistentFlags().StringP("config", "c", "", "use a specific configuration file") + flag := cmd.PersistentFlags().StringP("config", "c", "", "use a specific configuration file") + if err := cmd.PersistentFlags().MarkHidden("config"); err != nil { + slog.Warn(fmt.Sprintf("Failed to hide --config flag: %v", err)) + } + return flag } // SetVerboseMode change ErrorFormat and logs between very, middly and non verbose. From 1c46b4f71b5082475bd60ba5bc24b319f51a2c9b Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 7 Jan 2025 18:19:16 +0100 Subject: [PATCH 0308/1670] Also hide --paths-config flag The --paths-config flag should also only be used for debugging and testing. --- cmd/authd-oidc/daemon/daemon.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/authd-oidc/daemon/daemon.go b/cmd/authd-oidc/daemon/daemon.go index 8d687a9cdc..e1977b8d7c 100644 --- a/cmd/authd-oidc/daemon/daemon.go +++ b/cmd/authd-oidc/daemon/daemon.go @@ -106,6 +106,9 @@ func New(name string) *App { installConfigFlag(&a.rootCmd) // FIXME: This option is for the viper path configuration. We should merge --config and this one in the future. a.rootCmd.PersistentFlags().StringP("paths-config", "", "", "use a specific paths configuration file") + if err := a.rootCmd.PersistentFlags().MarkHidden("paths-config"); err != nil { + slog.Warn(fmt.Sprintf("Failed to hide --paths-config flag: %v", err)) + } // subcommands a.installVersion() From fe84e4e431eb57d9b1c9c5700e36b4885d40307d Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 8 Jan 2025 14:54:01 +0100 Subject: [PATCH 0309/1670] Add semver tool This tool can be used to compare semantic versions in the snap post-refresh hook to decide whether we need to do migrations or not. --- tools/semver/semver.go | 89 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 tools/semver/semver.go diff --git a/tools/semver/semver.go b/tools/semver/semver.go new file mode 100644 index 0000000000..78cdf44791 --- /dev/null +++ b/tools/semver/semver.go @@ -0,0 +1,89 @@ +// Package semver implements comparison of semantic version strings. +package main + +import ( + "fmt" + "os" + "strings" + + "golang.org/x/mod/semver" +) + +func main() { + if len(os.Args) < 2 { + usage() + os.Exit(1) + } + + switch os.Args[1] { + case "check": + if len(os.Args) != 3 { + fmt.Fprintf(os.Stderr, "Error: 'check' requires exactly one version argument\n") + usage() + os.Exit(1) + } + checkVersion(os.Args[2]) + + case "compare": + if len(os.Args) != 4 { + fmt.Fprintf(os.Stderr, "Error: 'compare' requires exactly two version arguments\n") + usage() + os.Exit(1) + } + compareVersions(os.Args[2], os.Args[3]) + + default: + fmt.Fprintf(os.Stderr, "Error: unknown command %q\n", os.Args[1]) + usage() + os.Exit(1) + } +} + +func usage() { + fmt.Fprintf(os.Stderr, `Usage: %[1]s [arguments] + +Commands: + check Check if a version is valid + compare Compare two versions + +Examples: + %[1]s check 1.2.3 # Prints "valid" or "invalid" + %[1]s compare 1.2.3 2.0.0 # Prints "less", "equal", or "greater" +`, os.Args[0]) +} + +func addVPrefix(version string) string { + if !strings.HasPrefix(version, "v") { + return "v" + version + } + return version +} + +func checkVersion(version string) { + v := addVPrefix(version) + if semver.IsValid(v) { + fmt.Println("valid") + return + } + fmt.Println("invalid") + os.Exit(1) +} + +func compareVersions(ver1, ver2 string) { + v1 := addVPrefix(ver1) + v2 := addVPrefix(ver2) + + if !semver.IsValid(v1) || !semver.IsValid(v2) { + fmt.Fprintf(os.Stderr, "Error: invalid semantic version format\n") + os.Exit(1) + } + + switch semver.Compare(v1, v2) { + case -1: + fmt.Println("less") + case 0: + fmt.Println("equal") + case 1: + fmt.Println("greater") + } +} From 55659349a4339e5cb38a82991a30fe3d1ef13318 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 8 Jan 2025 15:12:21 +0100 Subject: [PATCH 0310/1670] Use correct semantic version comparison in post-refresh hook This correctly detects that a pre-release version 0.2.0-pre1 is lower than 0.2.0. --- snap/hooks/post-refresh | 3 ++- snap/snapcraft.yaml | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/snap/hooks/post-refresh b/snap/hooks/post-refresh index 3915c6310d..a0c1bf5772 100755 --- a/snap/hooks/post-refresh +++ b/snap/hooks/post-refresh @@ -17,7 +17,8 @@ log() { } version_less_than() { - [ "$1" = "$2" ] && return 1 || [ "$(printf '%s\n' "${@}" | sort -V | head -n1)" = "$1" ] + output=$(semver compare "$1" "$2") || exit 1 + [ "${output}" = "less" ] } should_transition_to_allowed_users() { diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 851577dd97..bb16214994 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -39,6 +39,14 @@ parts: "authd.conf": "conf/authd/oidc.conf" "broker.conf": "conf/broker.conf.orig" "migrations": "conf/migrations" + # SemVer comparison helper + semver: + source: tools + source-type: local + plugin: go + override-build: | + go mod download all + go build -o ${GOBIN}/semver semver/semver.go # Build the snap version from the git repository and current tree state. version: source: . @@ -46,3 +54,5 @@ parts: build-packages: - git # The script needs Git. override-build: ./snap/get_version + after: + - semver From e1658aa7f6351b9dce92a9e3251fe512924a2a0b Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 8 Jan 2025 15:13:40 +0100 Subject: [PATCH 0311/1670] Add special handling for invalid semver versions We have releases on the edge channel of the authd-msentraid and authd-google snaps which start with "0.1+" or "notag+". Those already ship the allowed users configuration, so we do not want to migrate those. --- snap/hooks/post-refresh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/snap/hooks/post-refresh b/snap/hooks/post-refresh index a0c1bf5772..5f15f3dc28 100755 --- a/snap/hooks/post-refresh +++ b/snap/hooks/post-refresh @@ -21,7 +21,24 @@ version_less_than() { [ "${output}" = "less" ] } +valid_semver() { + output=$(semver check "$1") + if [ "$?" -ne 0 ] && [ "${output}" != "invalid" ]; then + exit 1 + fi + [ "${output}" = "valid" ] +} + should_transition_to_allowed_users() { + # Do not transition if the previous version is set but not a valid + # semantic version. That's the case for snaps published to the edge + # channel before the 0.2.0 release, which already ship the allowed users + # configuration. + # TODO: We can remove this check once all users have updated to 0.2.0. + if [ -n "${PREVIOUS_VERSION}" ] && ! valid_semver "${PREVIOUS_VERSION}"; then + return 1 + fi + # Transition to allowed users if: # - previous-version is not set (that means that the previous version is # older than 0.2.0, i.e. the version where we introduced setting the From c0fcbb0c0ac1d6d6cdf28a7d9ea00b6507296705 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 8 Jan 2025 15:30:17 +0100 Subject: [PATCH 0312/1670] Separate the "dirty" annotation with a . instead of + The golang.org/x/mod/semver which we use in our semver tool considers versions with two + in them as invalid. --- snap/get_version | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snap/get_version b/snap/get_version index 9635bf5240..e918f9c3df 100755 --- a/snap/get_version +++ b/snap/get_version @@ -13,7 +13,7 @@ set -eu # a. + for main branch. # b. +. for other branches. # -# Any of those version will be annoted with +dirty if there are local changes. +# Any of those version will be annoted with .dirty if there are local changes. # set_version will markup the version in the snapcraft.yaml file after amending it with a dirty markup if necessary. # $1: version: the version to set. @@ -32,7 +32,7 @@ annotate_with_dirty() { # check if current tree content is dirty. is_dirty=$(git -C "${SNAPCRAFT_PART_SRC}" status --porcelain) if [ -n "${is_dirty}" ]; then - version="${version}+dirty" + version="${version}.dirty" fi echo "${version}" From 9b58b71b8afcba2204113475382b79cedd9ce79f Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 8 Jan 2025 15:33:25 +0100 Subject: [PATCH 0313/1670] Double quote to prevent globbing and word splitting --- snap/get_version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/get_version b/snap/get_version index e918f9c3df..4b2e382820 100755 --- a/snap/get_version +++ b/snap/get_version @@ -75,7 +75,7 @@ if [ -n "${tag}" ] && [ "$(git describe --tags --exact-match 2>/dev/null)" = "${ fi # Current commit is not tagged, append commit(s) sha. -version="${version}+$(git -C ${SNAPCRAFT_PART_SRC} rev-parse --short=7 HEAD)" +version="${version}+$(git -C "${SNAPCRAFT_PART_SRC}" rev-parse --short=7 HEAD)" # Main branch will be set as is. if [ "${current_branch}" = "main" ]; then From 5fa568270e1ab6f9d2188182c177b6e89bdc7a1f Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 8 Jan 2025 16:02:04 +0100 Subject: [PATCH 0314/1670] snap: Check if the version is a valid semantic version --- snap/get_version | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/snap/get_version b/snap/get_version index 4b2e382820..3bb0388dd7 100755 --- a/snap/get_version +++ b/snap/get_version @@ -88,4 +88,10 @@ last_commit_on_main=$(git -C "${SNAPCRAFT_PART_SRC}" merge-base main HEAD) last_commit_on_main=$(git -C "${SNAPCRAFT_PART_SRC}" rev-parse --short=7 "${last_commit_on_main}") version="${version}.${last_commit_on_main}" +# Check if the version is a valid semantic version. +if ! semver check "${version}"; then + echo "Version ${version} is not a valid semantic version." + exit 1 +fi + set_version "${version}" From 61760388a7c9d2fc8d69d0e55e92b6cb6985a427 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 8 Jan 2025 16:02:26 +0100 Subject: [PATCH 0315/1670] Use valid semantic versions in comment --- snap/get_version | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snap/get_version b/snap/get_version index 3bb0388dd7..192ea912ae 100755 --- a/snap/get_version +++ b/snap/get_version @@ -5,8 +5,8 @@ set -eu # When considering a tag, if starting with "-" it will have its prefix removed. # For instance: -# * 0.1 -> 0.1 -# * msentraid-0.1 -> 0.1 on msentraid branch. +# * 0.1.0 -> 0.1.0 +# * msentraid-0.1.0 -> 0.1.0 on msentraid branch. # 1. If current commit is tagged, the version is directly the tag name. # 2. If current commit is not tagged, the version is: From 1d98bf26fda178f0e76f5942b8d6c152819e05e6 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 8 Jan 2025 16:04:40 +0100 Subject: [PATCH 0316/1670] Set INITIAL_ALLOWED_USERS_VERSION to 0.2.0-pre1 We use that tag for the current commit, so that any releases to the edge channels will have that version and will therefore not be migrated (because we already ship the allowed users configuration). --- snap/hooks/post-refresh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/hooks/post-refresh b/snap/hooks/post-refresh index 5f15f3dc28..ba18d5a9c9 100755 --- a/snap/hooks/post-refresh +++ b/snap/hooks/post-refresh @@ -10,7 +10,7 @@ if [ -f "${SNAP_DATA}/broker.conf" ]; then fi PREVIOUS_VERSION=$(snapctl get previous-version) -INITIAL_ALLOWED_USERS_VERSION="0.2.0" +INITIAL_ALLOWED_USERS_VERSION="0.2.0-pre1" log() { logger -t "${SNAP_NAME}" "post-refresh: $*" From 2ad4188730ab4ee394f9a225a5f7e63fd60cfc13 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 8 Jan 2025 16:11:50 +0100 Subject: [PATCH 0317/1670] Fix comment --- snap/hooks/post-refresh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/hooks/post-refresh b/snap/hooks/post-refresh index ba18d5a9c9..de7858ca3b 100755 --- a/snap/hooks/post-refresh +++ b/snap/hooks/post-refresh @@ -4,7 +4,7 @@ set -eu # In previous versions, the broker.conf was created with mode 0777 - umask. # This is not secure, because the file can contain sensitive information -# (like the client_secret), so we ensure that the mode is 0700. +# (like the client_secret), so we ensure that the mode is 0600. if [ -f "${SNAP_DATA}/broker.conf" ]; then chmod 0600 "${SNAP_DATA}/broker.conf" fi From 5fe782024e8676a82016dab735b21a9c7b2955b3 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 8 Jan 2025 16:14:50 +0100 Subject: [PATCH 0318/1670] Add a note to avoid issues like UDENG-5714 in the future --- snap/hooks/post-refresh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/snap/hooks/post-refresh b/snap/hooks/post-refresh index de7858ca3b..9f7a90fd60 100755 --- a/snap/hooks/post-refresh +++ b/snap/hooks/post-refresh @@ -10,6 +10,11 @@ if [ -f "${SNAP_DATA}/broker.conf" ]; then fi PREVIOUS_VERSION=$(snapctl get previous-version) + +# Important: If you add new migrations, make sure to tag the commit which +# first introduces the change, so that pre-release versions which already +# contain the change (and which automatically uploaded to the edge channel) +# will not be migrated. INITIAL_ALLOWED_USERS_VERSION="0.2.0-pre1" log() { From 7dd001068798aeebde50c2763b86a783dd75da36 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 9 Jan 2025 18:02:56 +0100 Subject: [PATCH 0319/1670] Support showing INFO messages without DEBUG messages Currently, we only show WARN messages when verbose mode is disabled, and when there's at least one "-v" flag, we show both INFO and DEBUG messages. With this commit, we change that behavior to the same as authd: When there is one "-v" flag, we show INFO messages, when there's at least two, we also show DEBUG messages. --- cmd/authd-oidc/daemon/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/authd-oidc/daemon/config.go b/cmd/authd-oidc/daemon/config.go index 4c9dca319a..66cc6ed2ea 100644 --- a/cmd/authd-oidc/daemon/config.go +++ b/cmd/authd-oidc/daemon/config.go @@ -96,7 +96,7 @@ func setVerboseMode(level int) { case 0: log.SetLevel(consts.DefaultLevelLog) case 1: - log.SetLevel(slog.LevelDebug) + log.SetLevel(slog.LevelInfo) case 3: //reportCaller = true fallthrough From 61621432fd9f44fd38d6a097e05f93ad774a57bb Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 9 Jan 2025 17:43:12 +0100 Subject: [PATCH 0320/1670] Fix get_version script Building from main or any other branch that's not "msentraid" or "google" failed because it used "notag" which is not a valid semantic version. It now first tries to use the highest version tag prefixed with the branch name (e.g. "msentraid-0.2.0" when on the msentraid branch) and if there is none, just uses the highest version tag on the branch which starts with a number (to avoid an invalid SemVer when tag "msentraid-0.2.0" is on a branch that's not "msentraid"). --- snap/get_version | 62 ++++++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/snap/get_version b/snap/get_version index 192ea912ae..b5a095ecdd 100755 --- a/snap/get_version +++ b/snap/get_version @@ -1,21 +1,25 @@ #!/bin/sh set -eu -# Script to build the version to set to the snap. +# This scripts sets the version of the snap that's being based on the +# git tags on the current branch. -# When considering a tag, if starting with "-" it will have its prefix removed. -# For instance: +# If there's a tag prefixed with the current branch name, that tag is +# used and the prefix is stripped. For example: +# * msentraid-0.1.0 -> 0.1.0 +# +# Else, the highest version tag that starts with a number (in contrast +# to a prefix like "msentraid-") is used. For example: # * 0.1.0 -> 0.1.0 -# * msentraid-0.1.0 -> 0.1.0 on msentraid branch. - -# 1. If current commit is tagged, the version is directly the tag name. +# +# 1. If current commit is tagged, that tag is used as the version as is. # 2. If current commit is not tagged, the version is: -# a. + for main branch. -# b. +. for other branches. +# * When on main: + +# * Else: +. # -# Any of those version will be annoted with .dirty if there are local changes. +# The version is appended with ".dirty" if there are uncommitted changes. -# set_version will markup the version in the snapcraft.yaml file after amending it with a dirty markup if necessary. +# set_version appends ".dirty" if needed and then sets the version of the snap # $1: version: the version to set. set_version() { version="${1}" @@ -24,7 +28,8 @@ set_version() { craftctl set version="${version}" } -# annotate_with_dirty may amend the version with a dirty markup if there are local changes. +# annotate_with_dirty appends ".dirty" to the version if there are +# uncommitted changes. # $1: version: the version to annotate. annotate_with_dirty() { version="${1}" @@ -38,37 +43,42 @@ annotate_with_dirty() { echo "${version}" } -# strip_branch_tag_prefix will remove the branch name prefix from the tag name. +# strip_branch_tag_prefix removes any non-numeric prefix ending with a +# dash (e.g. "msentraid-") from the tag name. We use this to remove the +# branch name prefix from the tag name, but we do not just strip the +# current branch name because we also want to support branching of a new +# branch and use the latest tag from that branch (for example when +# branching of the msentraid branch to test a fix, then that branch +# should still use a valid version). # $1: tag: the tag name to strip the prefix from. -# $2: current_branch: the branch name to strip from the tag. strip_branch_tag_prefix() { tag="${1}" - current_branch="${2}" - echo "${tag#"${current_branch}-"}" + echo "${tag}" | sed 's/^[^0-9-]*-//' } - current_branch=$(git -C "${SNAPCRAFT_PART_SRC}" branch --show-current) -# Try to get most recent tag on that branch not coming from main. -# Main will just get the most recent tag merged into it. -tag_cmd_suffix="" -if [ "${current_branch}" != "main" ]; then - tag_cmd_suffix="--no-merged=main" -fi +# Get the highest version tag which is prefixed with the current branch name. +tag=$(git tag --sort=-v:refname --merged="${current_branch}" | grep "^${current_branch}-" | head -1) -# Get most recent tag on that branch not coming from the other branch. -tag=$(git tag --sort=-v:refname --merged="${current_branch}" ${tag_cmd_suffix} | head -1) +# If there is no tag prefixed with the current branch name, use the most +# recent tag that does not have a non-numerical prefix (that's the case +# when we're building a snap for testing on a branch that's not +# "msentraid" or "google"). +if [ -z "${tag}" ]; then + tag=$(git tag --sort=-v:refname --merged="${current_branch}" | grep -E '^[0-9]+' | head -1) +fi version="${tag}" if [ -z "${version}" ]; then # No tag found, use "notag" as version. version="notag" fi -version=$(strip_branch_tag_prefix "${version}" "${current_branch}") +version=$(strip_branch_tag_prefix "${version}") -# If the most recent tag is on the current commit, taking it as is once transformed as a version. +# If the highest version tag is on the current commit, use it as is after +# stripping the prefix. if [ -n "${tag}" ] && [ "$(git describe --tags --exact-match 2>/dev/null)" = "${tag}" ]; then set_version "${version}" exit 0 From 2f3ea8f7be246d233b622faeca1741d30448cea1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 08:43:42 +0000 Subject: [PATCH 0321/1670] deps(go): bump github.com/Azure/azure-sdk-for-go/sdk/azcore Bumps [github.com/Azure/azure-sdk-for-go/sdk/azcore](https://github.com/Azure/azure-sdk-for-go) from 1.16.0 to 1.17.0. - [Release notes](https://github.com/Azure/azure-sdk-for-go/releases) - [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/release.md) - [Commits](https://github.com/Azure/azure-sdk-for-go/compare/sdk/azcore/v1.16.0...sdk/azcore/v1.17.0) --- updated-dependencies: - dependency-name: github.com/Azure/azure-sdk-for-go/sdk/azcore dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index b19f237211..0c43e4d120 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/ubuntu/authd-oidc-brokers go 1.23.0 require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 github.com/coreos/go-oidc/v3 v3.12.0 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/go-jose/go-jose/v4 v4.0.4 @@ -20,7 +20,7 @@ require ( github.com/stretchr/testify v1.10.0 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 - golang.org/x/crypto v0.31.0 + golang.org/x/crypto v0.32.0 golang.org/x/oauth2 v0.25.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 @@ -64,8 +64,8 @@ require ( go.opentelemetry.io/otel/trace v1.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect - golang.org/x/net v0.29.0 // indirect + golang.org/x/net v0.34.0 // indirect golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect ) diff --git a/go.sum b/go.sum index af2e769eab..c9bd5d96b4 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= github.com/cjlapao/common-go v0.0.39 h1:bAAUrj2B9v0kMzbAOhzjSmiyDy+rd56r2sy7oEiQLlA= @@ -133,16 +133,16 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -156,8 +156,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc 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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 98864c0f9e40a6b318da8c579a89ba9d67a2ec91 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 08:43:46 +0000 Subject: [PATCH 0322/1670] deps(go): bump golang.org/x/crypto from 0.31.0 to 0.32.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.31.0 to 0.32.0. - [Commits](https://github.com/golang/crypto/compare/v0.31.0...v0.32.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index b19f237211..735431304a 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/stretchr/testify v1.10.0 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 - golang.org/x/crypto v0.31.0 + golang.org/x/crypto v0.32.0 golang.org/x/oauth2 v0.25.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 @@ -66,6 +66,6 @@ require ( golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect ) diff --git a/go.sum b/go.sum index af2e769eab..5e9b3b3009 100644 --- a/go.sum +++ b/go.sum @@ -133,8 +133,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -156,8 +156,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc 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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 2660f398ed7834361cf991f0887c1cf59866e012 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jan 2025 08:43:54 +0000 Subject: [PATCH 0323/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.56.0 to 1.57.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.56.0...v1.57.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b19f237211..403c562f5a 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.56.0 + github.com/microsoftgraph/msgraph-sdk-go v1.57.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 diff --git a/go.sum b/go.sum index af2e769eab..d70e152b2f 100644 --- a/go.sum +++ b/go.sum @@ -67,8 +67,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.0.0 h1:3O5sb5Zj+moLBiJy github.com/microsoft/kiota-serialization-multipart-go v1.0.0/go.mod h1:yauLeBTpANk4L03XD985akNysG24SnRJGaveZf+p4so= github.com/microsoft/kiota-serialization-text-go v1.0.0 h1:XOaRhAXy+g8ZVpcq7x7a0jlETWnWrEum0RhmbYrTFnA= github.com/microsoft/kiota-serialization-text-go v1.0.0/go.mod h1:sM1/C6ecnQ7IquQOGUrUldaO5wj+9+v7G2W3sQ3fy6M= -github.com/microsoftgraph/msgraph-sdk-go v1.56.0 h1:UDdhFrn/2syJrUshZz9vDri8WRcXqPSxVNeC7VI5jcA= -github.com/microsoftgraph/msgraph-sdk-go v1.56.0/go.mod h1:q/0JXFg3C3AJO8he4MkbdGtnzQ4XIw3b6Z2hbqjqAdA= +github.com/microsoftgraph/msgraph-sdk-go v1.57.0 h1:Em6OfzJmHEF06tJSIGaAXAyvu7UVOw9pPC/c8Zb4Ma8= +github.com/microsoftgraph/msgraph-sdk-go v1.57.0/go.mod h1:q/0JXFg3C3AJO8he4MkbdGtnzQ4XIw3b6Z2hbqjqAdA= github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 h1:P1wpmn3xxfPMFJHg+PJPcusErfRkl63h6OdAnpDbkS8= github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1/go.mod h1:vFmWQGWyLlhxCESNLv61vlE4qesBU+eWmEVH7DJSESA= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= From 47f8d3b8897378aadd94ca076bb2872d1dde3f93 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 12 Nov 2024 22:59:25 +0100 Subject: [PATCH 0324/1670] ci: Install git-delta for nicer diffs in error messages The diffs of mismatching golden files are passed through git-delta since https://github.com/ubuntu/authd-oidc-brokers/pull/207. --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8b6cc09ed9..b64f0557e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,12 @@ jobs: with: go-version-file: go.mod + - name: Install dependencies + run: | + set -eu + sudo apt update + sudo apt install -y git-delta + - name: Prepare tests artifacts path run: | set -eu From 2132a8694681cd67992d92bc44532652ac0c7b57 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 12 Nov 2024 23:08:33 +0100 Subject: [PATCH 0325/1670] ci: Run tests on Noble instead of Jammy The ubuntu-latest label still resolves to Jammy, where git-delta is not packaged. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b64f0557e8..b149d7ec72 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: go-tests: name: "Go: Tests" - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 # ubuntu-latest-runner steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 From 213d0578fb1fcc630e33a3a5fb499ed8f7360626 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 14 Jan 2025 15:38:14 +0100 Subject: [PATCH 0326/1670] Use log package from authd --- cmd/authd-oidc/daemon/config.go | 16 +++---- cmd/authd-oidc/daemon/daemon.go | 8 ++-- cmd/authd-oidc/main.go | 5 +- go.mod | 4 +- go.sum | 10 +++- internal/broker/broker.go | 58 +++++++++++------------ internal/broker/encrypt.go | 6 ++- internal/consts/consts.go | 6 ++- internal/daemon/daemon.go | 12 ++--- internal/dbusservice/localbus.go | 4 +- internal/log/log.go | 20 -------- internal/providers/msentraid/msentraid.go | 16 +++---- internal/token/migration.go | 15 +++--- 13 files changed, 86 insertions(+), 94 deletions(-) delete mode 100644 internal/log/log.go diff --git a/cmd/authd-oidc/daemon/config.go b/cmd/authd-oidc/daemon/config.go index 66cc6ed2ea..1a7cf15079 100644 --- a/cmd/authd-oidc/daemon/config.go +++ b/cmd/authd-oidc/daemon/config.go @@ -1,9 +1,9 @@ package daemon import ( + "context" "errors" "fmt" - "log/slog" "os" "path/filepath" "strings" @@ -11,7 +11,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/ubuntu/authd-oidc-brokers/internal/consts" - "github.com/ubuntu/authd-oidc-brokers/internal/log" + "github.com/ubuntu/authd/log" "github.com/ubuntu/decorate" ) @@ -40,7 +40,7 @@ func initViperConfig(name string, cmd *cobra.Command, vip *viper.Viper) (err err vip.AddConfigPath(filepath.Join("/etc", name)) // Add the executable path to the config search path. if binPath, err := os.Executable(); err != nil { - slog.Warn(fmt.Sprintf("Failed to get current executable path, not adding it as a config dir: %v", err)) + log.Warningf(context.Background(), "Failed to get current executable path, not adding it as a config dir: %v", err) } else { vip.AddConfigPath(filepath.Dir(binPath)) } @@ -49,12 +49,12 @@ func initViperConfig(name string, cmd *cobra.Command, vip *viper.Viper) (err err if err := vip.ReadInConfig(); err != nil { var e viper.ConfigFileNotFoundError if errors.As(err, &e) { - slog.Info(fmt.Sprintf("No configuration file: %v.\nWe will only use the defaults, env variables or flags.", e)) + log.Infof(context.Background(), "No configuration file: %v.\nWe will only use the defaults, env variables or flags.", e) } else { return fmt.Errorf("invalid configuration file: %w", err) } } else { - slog.Info(fmt.Sprintf("Using configuration file: %v", vip.ConfigFileUsed())) + log.Infof(context.Background(), "Using configuration file: %v", vip.ConfigFileUsed()) } // Handle environment. @@ -84,7 +84,7 @@ func initViperConfig(name string, cmd *cobra.Command, vip *viper.Viper) (err err func installConfigFlag(cmd *cobra.Command) *string { flag := cmd.PersistentFlags().StringP("config", "c", "", "use a specific configuration file") if err := cmd.PersistentFlags().MarkHidden("config"); err != nil { - slog.Warn(fmt.Sprintf("Failed to hide --config flag: %v", err)) + log.Warningf(context.Background(), "Failed to hide --config flag: %v", err) } return flag } @@ -96,12 +96,12 @@ func setVerboseMode(level int) { case 0: log.SetLevel(consts.DefaultLevelLog) case 1: - log.SetLevel(slog.LevelInfo) + log.SetLevel(log.InfoLevel) case 3: //reportCaller = true fallthrough default: - log.SetLevel(slog.LevelDebug) + log.SetLevel(log.DebugLevel) } //slog.SetReportCaller(reportCaller) diff --git a/cmd/authd-oidc/daemon/daemon.go b/cmd/authd-oidc/daemon/daemon.go index e1977b8d7c..454662146c 100644 --- a/cmd/authd-oidc/daemon/daemon.go +++ b/cmd/authd-oidc/daemon/daemon.go @@ -4,7 +4,6 @@ package daemon import ( "context" "fmt" - "log/slog" "os" "path/filepath" "runtime" @@ -14,6 +13,7 @@ import ( "github.com/ubuntu/authd-oidc-brokers/internal/broker" "github.com/ubuntu/authd-oidc-brokers/internal/daemon" "github.com/ubuntu/authd-oidc-brokers/internal/dbusservice" + log "github.com/ubuntu/authd/log" ) // App encapsulate commands and options of the daemon, which can be controlled by env variables and config files. @@ -88,7 +88,7 @@ func New(name string) *App { } setVerboseMode(a.config.Verbosity) - slog.Debug("Debug mode is enabled") + log.Debug(context.Background(), "Debug mode is enabled") return nil }, @@ -107,7 +107,7 @@ func New(name string) *App { // FIXME: This option is for the viper path configuration. We should merge --config and this one in the future. a.rootCmd.PersistentFlags().StringP("paths-config", "", "", "use a specific paths configuration file") if err := a.rootCmd.PersistentFlags().MarkHidden("paths-config"); err != nil { - slog.Warn(fmt.Sprintf("Failed to hide --paths-config flag: %v", err)) + log.Warningf(context.Background(), "Failed to hide --paths-config flag: %v", err) } // subcommands @@ -174,7 +174,7 @@ func installVerbosityFlag(cmd *cobra.Command, viper *viper.Viper) *int { r := cmd.PersistentFlags().CountP("verbosity", "v" /*i18n.G(*/, "issue INFO (-v), DEBUG (-vv) or DEBUG with caller (-vvv) output") //) if err := viper.BindPFlag("verbosity", cmd.PersistentFlags().Lookup("verbosity")); err != nil { - slog.Warn(err.Error()) + log.Warning(context.Background(), err.Error()) } return r diff --git a/cmd/authd-oidc/main.go b/cmd/authd-oidc/main.go index 44c8a179f6..758b9b701d 100644 --- a/cmd/authd-oidc/main.go +++ b/cmd/authd-oidc/main.go @@ -2,7 +2,7 @@ package main import ( - "log/slog" + "context" "os" "os/signal" "path/filepath" @@ -12,6 +12,7 @@ import ( "github.com/ubuntu/authd-oidc-brokers/cmd/authd-oidc/daemon" "github.com/ubuntu/authd-oidc-brokers/internal/consts" "github.com/ubuntu/authd-oidc-brokers/po" + "github.com/ubuntu/authd/log" "github.com/ubuntu/go-i18n" ) @@ -36,7 +37,7 @@ func run(a app) int { defer installSignalHandler(a)() if err := a.Run(); err != nil { - slog.Error(err.Error()) + log.Error(context.Background(), err.Error()) if a.UsageError() { return 2 diff --git a/go.mod b/go.mod index 4379785df3..921d86e88a 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.10.0 + github.com/ubuntu/authd v0.3.8-0.20250114154108-c636df971351 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 golang.org/x/crypto v0.32.0 @@ -29,6 +30,7 @@ require ( require ( github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/cjlapao/common-go v0.0.39 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-logr/logr v1.4.1 // indirect @@ -39,7 +41,7 @@ require ( github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/microsoft/kiota-abstractions-go v1.8.1 // indirect github.com/microsoft/kiota-authentication-azure-go v1.1.0 // indirect github.com/microsoft/kiota-http-go v1.4.4 // indirect diff --git a/go.sum b/go.sum index 344e297f39..7a9da132f8 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/coreos/go-oidc/v3 v3.12.0 h1:sJk+8G2qq94rDI6ehZ71Bol3oUHy63qNYmkiSjrc github.com/coreos/go-oidc/v3 v3.12.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -24,6 +26,7 @@ github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= @@ -51,8 +54,8 @@ github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3v github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/microsoft/kiota-abstractions-go v1.8.1 h1:0gtK3KERmbKYm5AxJLZ8WPlNR9eACUGWuofFIa01PnA= github.com/microsoft/kiota-abstractions-go v1.8.1/go.mod h1:YO2QCJyNM9wzvlgGLepw6s9XrPgNHODOYGVDCqQWdLI= github.com/microsoft/kiota-authentication-azure-go v1.1.0 h1:HudH57Enel9zFQ4TEaJw6lMiyZ5RbBdrRHwdU0NP2RY= @@ -118,6 +121,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/ubuntu/authd v0.3.8-0.20250114154108-c636df971351 h1:YRVZSElrXVHuqgqPrjc2DrxZWnwLi0d6Kuhe3wECRAM= +github.com/ubuntu/authd v0.3.8-0.20250114154108-c636df971351/go.mod h1:3UpP+TbrWL9vH9CGOe0nNrGJVD7yAcVFwLLC6UHJA18= github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 h1:qO8m+4mLbo1HRpD5lfhEfr7R1PuqZvbAmjaRzYEy+tM= github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5/go.mod h1:PUpwIgUuCQyuCz/gwiq6WYbo7IvtXXd8JqL01ez+jZE= github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 h1:CA2dVorxvzdsGtszqhSjyvkrXxZi4bS52ZKvP0Ko634= @@ -156,6 +161,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc 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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 181f5c9b38..63c35fa8fc 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -10,7 +10,6 @@ import ( "encoding/json" "errors" "fmt" - "log/slog" "path/filepath" "slices" "strings" @@ -27,6 +26,7 @@ import ( providerErrors "github.com/ubuntu/authd-oidc-brokers/internal/providers/errors" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "github.com/ubuntu/authd-oidc-brokers/internal/token" + "github.com/ubuntu/authd/log" "github.com/ubuntu/decorate" "golang.org/x/oauth2" ) @@ -134,7 +134,7 @@ func New(cfg Config, args ...Option) (b *Broker, err error) { // Generate a new private key for the broker. privateKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { - slog.Error(err.Error()) + log.Error(context.Background(), err.Error()) return nil, errors.New("failed to generate broker private key") } @@ -182,7 +182,7 @@ func (b *Broker) NewSession(username, lang, mode string) (sessionID, encryptionK // Construct an OIDC provider via OIDC discovery. s.oidcServer, err = b.connectToOIDCServer(context.Background()) if err != nil { - slog.Debug(fmt.Sprintf("Could not connect to the provider: %v. Starting session in offline mode.", err)) + log.Debugf(context.Background(), "Could not connect to the provider: %v. Starting session in offline mode.", err) s.isOffline = true } @@ -218,19 +218,19 @@ func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []m supportedAuthModes := b.supportedAuthModesFromLayout(supportedUILayouts) - slog.Debug(fmt.Sprintf("Supported UI Layouts for session %s: %#v", sessionID, supportedUILayouts)) - slog.Debug(fmt.Sprintf("Supported Authentication modes for session %s: %#v", sessionID, supportedAuthModes)) + log.Debugf(context.Background(), "Supported UI Layouts for session %s: %#v", sessionID, supportedUILayouts) + log.Debugf(context.Background(), "Supported Authentication modes for session %s: %#v", sessionID, supportedAuthModes) // Checks if the token exists in the cache. tokenExists, err := fileutils.FileExists(session.tokenPath) if err != nil { - slog.Warn(fmt.Sprintf("Could not check if token exists: %v", err)) + log.Warningf(context.Background(), "Could not check if token exists: %v", err) } if !tokenExists { // Check the old encrypted token path. tokenExists, err = fileutils.FileExists(session.oldEncryptedTokenPath) if err != nil { - slog.Warn(fmt.Sprintf("Could not check if old encrypted token exists: %v", err)) + log.Warningf(context.Background(), "Could not check if old encrypted token exists: %v", err) } } @@ -485,7 +485,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au // Decrypt challenge if present. challenge, err := decodeRawChallenge(b.privateKey, authData["challenge"]) if err != nil { - slog.Error(err.Error()) + log.Error(context.Background(), err.Error()) return AuthRetry, errorMessage{Message: "could not decode challenge"} } @@ -494,7 +494,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au case authmodes.Device, authmodes.DeviceQr: response, ok := session.authInfo["response"].(*oauth2.DeviceAuthResponse) if !ok { - slog.Error("could not get device auth response") + log.Error(context.Background(), "could not get device auth response") return AuthDenied, errorMessage{Message: "could not get required response"} } @@ -505,17 +505,17 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au defer cancel() t, err := session.oauth2Config.DeviceAccessToken(expiryCtx, response, b.provider.AuthOptions()...) if err != nil { - slog.Error(err.Error()) + log.Error(context.Background(), err.Error()) return AuthRetry, errorMessage{Message: "could not authenticate user remotely"} } if err = b.provider.CheckTokenScopes(t); err != nil { - slog.Warn(err.Error()) + log.Warning(context.Background(), err.Error()) } rawIDToken, ok := t.Extra("id_token").(string) if !ok { - slog.Error("could not get ID token") + log.Error(context.Background(), "could not get ID token") return AuthDenied, errorMessage{Message: "could not get ID token"} } @@ -523,13 +523,13 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au authInfo.ProviderMetadata, err = b.provider.GetMetadata(session.oidcServer) if err != nil { - slog.Error(err.Error()) + log.Error(context.Background(), err.Error()) return AuthDenied, errorMessage{Message: "could not get provider metadata"} } authInfo.UserInfo, err = b.fetchUserInfo(ctx, session, &authInfo) if err != nil { - slog.Error(err.Error()) + log.Error(context.Background(), err.Error()) return AuthDenied, errorMessageForDisplay(err, "could not fetch user info") } @@ -539,27 +539,27 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au case authmodes.Password: useOldEncryptedToken, err := token.UseOldEncryptedToken(session.tokenPath, session.passwordPath, session.oldEncryptedTokenPath) if err != nil { - slog.Error(err.Error()) + log.Error(context.Background(), err.Error()) return AuthDenied, errorMessage{Message: "could not check password file"} } if useOldEncryptedToken { authInfo, err = token.LoadOldEncryptedAuthInfo(session.oldEncryptedTokenPath, challenge) if err != nil { - slog.Error(err.Error()) + log.Error(context.Background(), err.Error()) return AuthDenied, errorMessage{Message: "could not load encrypted token"} } // We were able to decrypt the old token with the password, so we can now hash and store the password in the // new format. if err = password.HashAndStorePassword(challenge, session.passwordPath); err != nil { - slog.Error(err.Error()) + log.Error(context.Background(), err.Error()) return AuthDenied, errorMessage{Message: "could not store password"} } } else { ok, err := password.CheckPassword(challenge, session.passwordPath) if err != nil { - slog.Error(err.Error()) + log.Error(context.Background(), err.Error()) return AuthDenied, errorMessage{Message: "could not check password"} } if !ok { @@ -568,7 +568,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au authInfo, err = token.LoadAuthInfo(session.tokenPath) if err != nil { - slog.Error(err.Error()) + log.Error(context.Background(), err.Error()) return AuthDenied, errorMessage{Message: "could not load stored token"} } } @@ -577,7 +577,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au if !session.isOffline { authInfo, err = b.refreshToken(ctx, session.oauth2Config, authInfo) if err != nil { - slog.Error(err.Error()) + log.Error(context.Background(), err.Error()) return AuthDenied, errorMessage{Message: "could not refresh token"} } } @@ -586,12 +586,12 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au userInfo, err := b.fetchUserInfo(ctx, session, &authInfo) if err != nil && authInfo.UserInfo.Name == "" { // We don't have a valid user info, so we can't proceed. - slog.Error(err.Error()) + log.Error(context.Background(), err.Error()) return AuthDenied, errorMessageForDisplay(err, "could not fetch user info") } if err != nil { // We couldn't fetch the user info, but we have a valid cached one. - slog.Warn(fmt.Sprintf("Could not fetch user info: %v. Using cached user info.", err)) + log.Warningf(context.Background(), "Could not fetch user info: %v. Using cached user info.", err) } else { authInfo.UserInfo = userInfo } @@ -610,12 +610,12 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au // This mode must always come after a authentication mode, so it has to have an auth_info. authInfo, ok = session.authInfo["auth_info"].(token.AuthCachedInfo) if !ok { - slog.Error("could not get required information") + log.Error(context.Background(), "could not get required information") return AuthDenied, errorMessage{Message: "could not get required information"} } if err = password.HashAndStorePassword(challenge, session.passwordPath); err != nil { - slog.Error(err.Error()) + log.Error(context.Background(), err.Error()) return AuthDenied, errorMessage{Message: "could not store password"} } } @@ -623,7 +623,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au if err := b.cfg.registerOwner(b.cfg.ConfigFile, authInfo.UserInfo.Name); err != nil { // The user is not allowed if we fail to create the owner-autoregistration file. // Otherwise the owner might change if the broker is restarted. - slog.Error(fmt.Sprintf("Failed to assign the owner role: %v", err)) + log.Errorf(context.Background(), "Failed to assign the owner role: %v", err) return AuthDenied, errorMessage{Message: "could not register the owner"} } @@ -636,7 +636,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au } if err := token.CacheAuthInfo(session.tokenPath, authInfo); err != nil { - slog.Error(err.Error()) + log.Error(context.Background(), err.Error()) return AuthDenied, errorMessage{Message: "could not cache user info"} } @@ -671,7 +671,7 @@ func (b *Broker) startAuthenticate(sessionID string) (context.Context, error) { } if session.isAuthenticating != nil { - slog.Error(fmt.Sprintf("Authentication already running for session %q", sessionID)) + log.Errorf(context.Background(), "Authentication already running for session %q", sessionID) return nil, errors.New("authentication already running for this user session") } @@ -719,7 +719,7 @@ func (b *Broker) CancelIsAuthenticated(sessionID string) { session.isAuthenticating = nil if err := b.updateSession(sessionID, session); err != nil { - slog.Error(fmt.Sprintf("Error when cancelling IsAuthenticated: %v", err)) + log.Errorf(context.Background(), "Error when cancelling IsAuthenticated: %v", err) } } @@ -783,7 +783,7 @@ func (b *Broker) refreshToken(ctx context.Context, oauth2Config oauth2.Config, o // Update the raw ID token rawIDToken, ok := oauthToken.Extra("id_token").(string) if !ok { - slog.Debug("refreshed token does not contain an ID token, keeping the old one") + log.Debug(context.Background(), "refreshed token does not contain an ID token, keeping the old one") rawIDToken = oldToken.RawIDToken } diff --git a/internal/broker/encrypt.go b/internal/broker/encrypt.go index 56eb8b4bd2..01af3c6e83 100644 --- a/internal/broker/encrypt.go +++ b/internal/broker/encrypt.go @@ -1,12 +1,14 @@ package broker import ( + "context" "crypto/rsa" "crypto/sha512" "encoding/base64" "errors" "fmt" - "log/slog" + + "github.com/ubuntu/authd/log" ) // decodeRawChallenge extract the base64 challenge and try to decrypt it with the private key. @@ -15,7 +17,7 @@ func decodeRawChallenge(priv *rsa.PrivateKey, rawChallenge string) (decoded stri // Override the error so that we don't leak information. Also, abstract it for the user. // We still log as error for the admin to get access. if err != nil { - slog.Error(fmt.Sprintf("Error when decoding challenge: %v", err)) + log.Errorf(context.Background(), "Error when decoding challenge: %v", err) err = errors.New("could not decode challenge") } }() diff --git a/internal/consts/consts.go b/internal/consts/consts.go index d1d7f56b9b..af107eb079 100644 --- a/internal/consts/consts.go +++ b/internal/consts/consts.go @@ -1,7 +1,9 @@ // Package consts defines the constants used by the project. package consts -import "log/slog" +import ( + "github.com/ubuntu/authd/log" +) var ( // Version is the version of the executable. @@ -13,5 +15,5 @@ const ( TEXTDOMAIN = "authd-oidc" // DefaultLevelLog is the default logging level selected without any option. - DefaultLevelLog = slog.LevelWarn + DefaultLevelLog = log.WarnLevel ) diff --git a/internal/daemon/daemon.go b/internal/daemon/daemon.go index 0b19ee1ee8..50082d620c 100644 --- a/internal/daemon/daemon.go +++ b/internal/daemon/daemon.go @@ -4,9 +4,9 @@ package daemon import ( "context" "fmt" - "log/slog" "github.com/coreos/go-systemd/daemon" + "github.com/ubuntu/authd/log" "github.com/ubuntu/decorate" ) @@ -39,7 +39,7 @@ type Service interface { func New(ctx context.Context, service Service, args ...Option) (d *Daemon, err error) { defer decorate.OnError(&err, "can't create daemon") - slog.Debug("Building new daemon") + log.Debug(context.Background(), "Building new daemon") // Set default options. opts := options{ @@ -61,22 +61,22 @@ func New(ctx context.Context, service Service, args ...Option) (d *Daemon, err e func (d *Daemon) Serve(ctx context.Context) (err error) { defer decorate.OnError(&err, "error while serving") - slog.Debug("Starting to serve requests") + log.Debug(context.Background(), "Starting to serve requests") // Signal to systemd that we are ready. if sent, err := d.systemdSdNotifier(false, "READY=1"); err != nil { return fmt.Errorf("couldn't send ready notification to systemd: %v", err) } else if sent { - slog.Debug("Ready state sent to systemd") + log.Debug(context.Background(), "Ready state sent to systemd") } - slog.Info(fmt.Sprintf("Serving requests as %v", d.service.Addr())) + log.Infof(context.Background(), "Serving requests as %v", d.service.Addr()) return d.service.Serve() } // Quit gracefully quits listening loop and stops the grpc server. // It can drops any existing connexion is force is true. func (d Daemon) Quit() { - slog.Info("Stopping daemon requested.") + log.Info(context.Background(), "Stopping daemon requested.") _ = d.service.Stop() } diff --git a/internal/dbusservice/localbus.go b/internal/dbusservice/localbus.go index 423e36fb34..add4e4b4c6 100644 --- a/internal/dbusservice/localbus.go +++ b/internal/dbusservice/localbus.go @@ -3,8 +3,6 @@ package dbusservice import ( - "fmt" - "log/slog" "os" "github.com/godbus/dbus/v5" @@ -18,7 +16,7 @@ func (s *Service) getBus() (*dbus.Conn, error) { if err != nil { return nil, err } - slog.Info(fmt.Sprintf("Using local bus address: %s", os.Getenv("DBUS_SYSTEM_BUS_ADDRESS"))) + log.Infof(context.Background(), "Using local bus address: %s", os.Getenv("DBUS_SYSTEM_BUS_ADDRESS")) conn, err := dbus.ConnectSystemBus() if err != nil { return nil, err diff --git a/internal/log/log.go b/internal/log/log.go deleted file mode 100644 index 65f38886f6..0000000000 --- a/internal/log/log.go +++ /dev/null @@ -1,20 +0,0 @@ -// Package log configures the logging utilities for the project. -package log - -import ( - "log/slog" - "os" -) - -var globalLevel = &slog.LevelVar{} - -func init() { - h := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: globalLevel}) - slog.SetDefault(slog.New(h)) - globalLevel.Set(slog.LevelWarn) -} - -// SetLevel change global handler log level. -func SetLevel(l slog.Level) { - globalLevel.Set(l) -} diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 9cbe24c788..d08aae0dcb 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -5,7 +5,6 @@ import ( "context" "errors" "fmt" - "log/slog" "regexp" "slices" "strings" @@ -21,6 +20,7 @@ import ( "github.com/ubuntu/authd-oidc-brokers/internal/consts" providerErrors "github.com/ubuntu/authd-oidc-brokers/internal/providers/errors" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" + "github.com/ubuntu/authd/log" "golang.org/x/oauth2" ) @@ -152,7 +152,7 @@ func (p Provider) userClaims(idToken *oidc.IDToken) (claims, error) { // getGroups access the Microsoft Graph API to get the groups the user is a member of. func (p Provider) getGroups(token *oauth2.Token, msgraphHost string) ([]info.Group, error) { - slog.Debug("Getting user groups from Microsoft Graph API") + log.Debug(context.Background(), "Getting user groups from Microsoft Graph API") // Check if the token has the GroupMember.Read.All scope scopes, err := p.getTokenScopes(token) @@ -188,14 +188,14 @@ func (p Provider) getGroups(token *oauth2.Token, msgraphHost string) ([]info.Gro for _, msGroup := range graphGroups { idPtr := msGroup.GetId() if idPtr == nil { - slog.Warn(pp.Sprintf("Could not get ID for group: %v", msGroup)) + log.Warning(context.Background(), pp.Sprintf("Could not get ID for group: %v", msGroup)) return nil, errors.New("could not get group id") } id := *idPtr groupNamePtr := msGroup.GetDisplayName() if groupNamePtr == nil { - slog.Warn(pp.Sprintf("Could not get display name for group object (ID: %s): %v", id, msGroup)) + log.Warning(context.Background(), pp.Sprintf("Could not get display name for group object (ID: %s): %v", id, msGroup)) return nil, errors.New("could not get group name") } groupName := strings.ToLower(*groupNamePtr) @@ -222,7 +222,7 @@ func getAllUserGroups(client *msgraphsdk.GraphServiceClient) ([]msgraphmodels.Gr return nil, fmt.Errorf("failed to get user groups: %v", err) } if result == nil { - slog.Debug("Got nil response from Microsoft Graph API for user's groups, assuming that user is not a member of any group.") + log.Debug(context.Background(), "Got nil response from Microsoft Graph API for user's groups, assuming that user is not a member of any group.") return []msgraphmodels.Groupable{}, nil } @@ -247,7 +247,7 @@ func getAllUserGroups(client *msgraphsdk.GraphServiceClient) ([]msgraphmodels.Gr groupNames = append(groupNames, *groupNamePtr) } } - slog.Debug(fmt.Sprintf("Got groups: %s", strings.Join(groupNames, ", "))) + log.Debugf(context.Background(), "Got groups: %s", strings.Join(groupNames, ", ")) return groups, nil } @@ -263,7 +263,7 @@ func (p Provider) CurrentAuthenticationModesOffered( endpoints map[string]struct{}, currentAuthStep int, ) ([]string, error) { - slog.Debug(fmt.Sprintf("In CurrentAuthenticationModesOffered: sessionMode=%q, supportedAuthModes=%q, tokenExists=%t, providerReachable=%t, endpoints=%q, currentAuthStep=%d\n", sessionMode, supportedAuthModes, tokenExists, providerReachable, endpoints, currentAuthStep)) + log.Debugf(context.Background(), "In CurrentAuthenticationModesOffered: sessionMode=%q, supportedAuthModes=%q, tokenExists=%t, providerReachable=%t, endpoints=%q, currentAuthStep=%d\n", sessionMode, supportedAuthModes, tokenExists, providerReachable, endpoints, currentAuthStep) var offeredModes []string switch sessionMode { case "passwd": @@ -288,7 +288,7 @@ func (p Provider) CurrentAuthenticationModesOffered( offeredModes = []string{authmodes.NewPassword} } } - slog.Debug(fmt.Sprintf("Offered modes: %q", offeredModes)) + log.Debugf(context.Background(), "Offered modes: %q", offeredModes) for _, mode := range offeredModes { if _, ok := supportedAuthModes[mode]; !ok { diff --git a/internal/token/migration.go b/internal/token/migration.go index 04fc0b3641..6e3e088334 100644 --- a/internal/token/migration.go +++ b/internal/token/migration.go @@ -1,15 +1,16 @@ package token import ( + "context" "crypto/aes" "crypto/cipher" "encoding/json" "fmt" - "log/slog" "os" "path/filepath" "github.com/ubuntu/authd-oidc-brokers/internal/fileutils" + "github.com/ubuntu/authd/log" "golang.org/x/crypto/scrypt" ) @@ -105,14 +106,14 @@ func decrypt(blob, key []byte) ([]byte, error) { func CleanupOldEncryptedToken(path string) { exists, err := fileutils.FileExists(path) if err != nil { - slog.Warn(fmt.Sprintf("Failed to check if old encrypted token exists %s: %v", path, err)) + log.Warningf(context.Background(), "Failed to check if old encrypted token exists %s: %v", path, err) } if !exists { return } if err := os.Remove(path); err != nil { - slog.Warn(fmt.Sprintf("Failed to remove old encrypted token %s: %v", path, err)) + log.Warningf(context.Background(), "Failed to remove old encrypted token %s: %v", path, err) return } @@ -123,26 +124,26 @@ func CleanupOldEncryptedToken(path string) { // Check if the parent directory is empty. empty, err := fileutils.IsDirEmpty(filepath.Dir(path)) if err != nil { - slog.Warn(fmt.Sprintf("Failed to check if old encrypted token parent directory %s is empty: %v", filepath.Dir(path), err)) + log.Warningf(context.Background(), "Failed to check if old encrypted token parent directory %s is empty: %v", filepath.Dir(path), err) return } if !empty { return } if err := os.Remove(filepath.Dir(path)); err != nil { - slog.Warn(fmt.Sprintf("Failed to remove old encrypted token directory %s: %v", filepath.Dir(path), err)) + log.Warningf(context.Background(), "Failed to remove old encrypted token directory %s: %v", filepath.Dir(path), err) } // Check if the parent's parent directory is empty. empty, err = fileutils.IsDirEmpty(filepath.Dir(filepath.Dir(path))) if err != nil { - slog.Warn(fmt.Sprintf("Failed to check if old encrypted token parent directory %s is empty: %v", filepath.Dir(filepath.Dir(path)), err)) + log.Warningf(context.Background(), "Failed to check if old encrypted token parent directory %s is empty: %v", filepath.Dir(filepath.Dir(path)), err) return } if !empty { return } if err := os.Remove(filepath.Dir(filepath.Dir(path))); err != nil { - slog.Warn(fmt.Sprintf("Failed to remove old encrypted token parent directory %s: %v", filepath.Dir(filepath.Dir(path)), err)) + log.Warningf(context.Background(), "Failed to remove old encrypted token parent directory %s: %v", filepath.Dir(filepath.Dir(path)), err) } } From 0fb9c1bcc7a00efd0b65671c5f100ba32bf1489d Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 14 Jan 2025 15:52:49 +0100 Subject: [PATCH 0327/1670] Initialize journal handler --- cmd/authd-oidc/daemon/daemon.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/authd-oidc/daemon/daemon.go b/cmd/authd-oidc/daemon/daemon.go index 454662146c..c4e46c46d0 100644 --- a/cmd/authd-oidc/daemon/daemon.go +++ b/cmd/authd-oidc/daemon/daemon.go @@ -50,6 +50,9 @@ func New(name string) *App { Long: fmt.Sprintf("Authentication daemon %s to communicate with our authentication daemon.", name), Args: cobra.NoArgs, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + // First thing, initialize the log handler + log.InitJournalHandler(false) + // Command parsing has been successful. Returns to not print usage anymore. a.rootCmd.SilenceUsage = true From 3728324217df520f9199bc52902798a6689b2f4f Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 16 Jan 2025 17:28:18 +0100 Subject: [PATCH 0328/1670] Only use Microsoft Entra Security Groups Other groups might be created by non-admin users. --- internal/providers/msentraid/msentraid.go | 24 +++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index d08aae0dcb..f7bf8f335c 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -179,7 +179,7 @@ func (p Provider) getGroups(token *oauth2.Token, msgraphHost string) ([]info.Gro // Get the groups (only the groups, not directory roles or administrative units, because that would require // additional permissions) which the user is a member of. - graphGroups, err := getAllUserGroups(client) + graphGroups, err := getSecurityGroups(client) if err != nil { return nil, fmt.Errorf("failed to get user groups: %v", err) } @@ -214,7 +214,7 @@ func (p Provider) getGroups(token *oauth2.Token, msgraphHost string) ([]info.Gro return groups, nil } -func getAllUserGroups(client *msgraphsdk.GraphServiceClient) ([]msgraphmodels.Groupable, error) { +func getSecurityGroups(client *msgraphsdk.GraphServiceClient) ([]msgraphmodels.Groupable, error) { // Initial request to get groups requestBuilder := client.Me().TransitiveMemberOf().GraphGroup() result, err := requestBuilder.Get(context.Background(), nil) @@ -240,6 +240,15 @@ func getAllUserGroups(client *msgraphsdk.GraphServiceClient) ([]msgraphmodels.Gr groups = append(groups, result.GetValue()...) } + // Remove the groups which are not security groups (but for example Microsoft 365 groups, which can be created + // by non-admin users). + for i, group := range groups { + if !isSecurityGroup(group) { + log.Debugf(context.Background(), "Removing non-security group %s", *group.GetDisplayName()) + groups = append(groups[:i], groups[i+1:]...) + } + } + var groupNames []string for _, group := range groups { groupNamePtr := group.GetDisplayName() @@ -252,6 +261,17 @@ func getAllUserGroups(client *msgraphsdk.GraphServiceClient) ([]msgraphmodels.Gr return groups, nil } +func isSecurityGroup(group msgraphmodels.Groupable) bool { + // A group is a security group if the `securityEnabled` property is true and the `groupTypes` property does not + // contain "Unified". + securityEnabledPtr := group.GetSecurityEnabled() + if securityEnabledPtr == nil || !*securityEnabledPtr { + return false + } + + return !slices.Contains(group.GetGroupTypes(), "Unified") +} + // CurrentAuthenticationModesOffered returns the generic authentication modes supported by the provider. // // Token validity is not considered, only the presence of a token. From db180244695a2ab29edd8906fb9a4d2d48fe16f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 09:06:05 +0000 Subject: [PATCH 0329/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.57.0 to 1.58.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.57.0...v1.58.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 921d86e88a..53f574ed3b 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.57.0 + github.com/microsoftgraph/msgraph-sdk-go v1.58.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 diff --git a/go.sum b/go.sum index 7a9da132f8..de0a972a7d 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.0.0 h1:3O5sb5Zj+moLBiJy github.com/microsoft/kiota-serialization-multipart-go v1.0.0/go.mod h1:yauLeBTpANk4L03XD985akNysG24SnRJGaveZf+p4so= github.com/microsoft/kiota-serialization-text-go v1.0.0 h1:XOaRhAXy+g8ZVpcq7x7a0jlETWnWrEum0RhmbYrTFnA= github.com/microsoft/kiota-serialization-text-go v1.0.0/go.mod h1:sM1/C6ecnQ7IquQOGUrUldaO5wj+9+v7G2W3sQ3fy6M= -github.com/microsoftgraph/msgraph-sdk-go v1.57.0 h1:Em6OfzJmHEF06tJSIGaAXAyvu7UVOw9pPC/c8Zb4Ma8= -github.com/microsoftgraph/msgraph-sdk-go v1.57.0/go.mod h1:q/0JXFg3C3AJO8he4MkbdGtnzQ4XIw3b6Z2hbqjqAdA= +github.com/microsoftgraph/msgraph-sdk-go v1.58.0 h1:FJgv6RfARoO31wyV3djkQRnxa3Lx27HKtCOHEDXd85c= +github.com/microsoftgraph/msgraph-sdk-go v1.58.0/go.mod h1:2dZJTO/7S+UmfwKlPQg2vczpW7awcIEj1ZCgfZIf0V4= github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 h1:P1wpmn3xxfPMFJHg+PJPcusErfRkl63h6OdAnpDbkS8= github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1/go.mod h1:vFmWQGWyLlhxCESNLv61vlE4qesBU+eWmEVH7DJSESA= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= From 30a948c873fd816220ce62300616229ebff743fe Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 17 Jan 2025 14:46:56 +0100 Subject: [PATCH 0330/1670] Extract session mode constants For improved maintainability and code navigation. --- internal/broker/broker.go | 5 +++-- internal/broker/broker_test.go | 25 +++++++++++---------- internal/broker/helper_test.go | 3 ++- internal/broker/sessionmode/consts.go | 9 ++++++++ internal/providers/msentraid/msentraid.go | 3 ++- internal/providers/noprovider/noprovider.go | 3 ++- 6 files changed, 31 insertions(+), 17 deletions(-) create mode 100644 internal/broker/sessionmode/consts.go diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 63c35fa8fc..f498a8b60c 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -19,6 +19,7 @@ import ( "github.com/coreos/go-oidc/v3/oidc" "github.com/google/uuid" "github.com/ubuntu/authd-oidc-brokers/internal/broker/authmodes" + "github.com/ubuntu/authd-oidc-brokers/internal/broker/sessionmode" "github.com/ubuntu/authd-oidc-brokers/internal/consts" "github.com/ubuntu/authd-oidc-brokers/internal/fileutils" "github.com/ubuntu/authd-oidc-brokers/internal/password" @@ -399,7 +400,7 @@ func (b *Broker) generateUILayout(session *session, authModeID string) (map[stri case authmodes.NewPassword: label := "Create a local password" - if session.mode == "passwd" { + if session.mode == sessionmode.ChangePassword { label = "Update your local password" } @@ -596,7 +597,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au authInfo.UserInfo = userInfo } - if session.mode == "passwd" { + if session.mode == sessionmode.ChangePassword { session.authInfo["auth_info"] = authInfo return AuthNext, nil } diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 12e5ba8fdc..44dc8a7ef5 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ubuntu/authd-oidc-brokers/internal/broker" "github.com/ubuntu/authd-oidc-brokers/internal/broker/authmodes" + "github.com/ubuntu/authd-oidc-brokers/internal/broker/sessionmode" "github.com/ubuntu/authd-oidc-brokers/internal/password" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "github.com/ubuntu/authd-oidc-brokers/internal/testutils" @@ -108,7 +109,7 @@ func TestNewSession(t *testing.T) { customHandlers: tc.customHandlers, }) - id, _, err := b.NewSession("test-user", "lang", "auth") + id, _, err := b.NewSession("test-user", "lang", sessionmode.Login) require.NoError(t, err, "NewSession should not have returned an error") gotOffline, err := b.IsOffline(id) @@ -170,7 +171,7 @@ func TestGetAuthenticationModes(t *testing.T) { wantErr bool }{ - // Auth Session + // Authentication session "Get_device_auth_qr_if_there_is_no_token": {}, "Get_newpassword_if_already_authenticated_with_device_auth_qr": {secondAuthStep: true}, "Get_password_and_device_auth_qr_if_token_exists": {tokenExists: true}, @@ -178,9 +179,9 @@ func TestGetAuthenticationModes(t *testing.T) { "Get_only_password_if_token_exists_and_provider_is_not_available": {tokenExists: true, providerAddress: "127.0.0.1:31310", unavailableProvider: true}, "Get_only_password_if_token_exists_and_provider_does_not_support_device_auth_qr": {tokenExists: true, providerAddress: "127.0.0.1:31311", deviceAuthUnsupported: true}, - // Passwd Session - "Get_only_password_if_token_exists_and_session_is_passwd": {sessionMode: "passwd", tokenExists: true}, - "Get_newpassword_if_already_authenticated_with_password_and_session_is_passwd": {sessionMode: "passwd", tokenExists: true, secondAuthStep: true}, + // Change password session + "Get_only_password_if_token_exists_and_session_is_passwd": {sessionMode: sessionmode.ChangePassword, tokenExists: true}, + "Get_newpassword_if_already_authenticated_with_password_and_session_is_passwd": {sessionMode: sessionmode.ChangePassword, tokenExists: true, secondAuthStep: true}, "Error_if_there_is_no_session": {sessionID: "-", wantErr: true}, @@ -191,15 +192,15 @@ func TestGetAuthenticationModes(t *testing.T) { "Error_if_expecting_newpassword_but_not_supported": {supportedLayouts: []string{"newpassword-without-entry"}, wantErr: true}, "Error_if_expecting_password_but_not_supported": {supportedLayouts: []string{"form-without-entry"}, wantErr: true}, - // Passwd session errors - "Error_if_session_is_passwd_but_token_does_not_exist": {sessionMode: "passwd", wantErr: true}, + // Change password session errors + "Error_if_session_is_passwd_but_token_does_not_exist": {sessionMode: sessionmode.ChangePassword, wantErr: true}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { t.Parallel() if tc.sessionMode == "" { - tc.sessionMode = "auth" + tc.sessionMode = sessionmode.Login } cfg := &brokerForTestConfig{} @@ -331,9 +332,9 @@ func TestSelectAuthenticationMode(t *testing.T) { } b := newBrokerForTests(t, cfg) - sessionType := "auth" + sessionType := sessionmode.Login if tc.passwdSession { - sessionType = "passwd" + sessionType = sessionmode.ChangePassword } sessionID, _ := newSessionForTests(t, b, "", sessionType) @@ -522,7 +523,7 @@ func TestIsAuthenticated(t *testing.T) { t.Parallel() if tc.sessionMode == "" { - tc.sessionMode = "auth" + tc.sessionMode = sessionmode.Login } if tc.sessionOffline { @@ -1031,7 +1032,7 @@ func TestFetchUserInfo(t *testing.T) { } tc.token.issuer = defaultIssuerURL - sessionID, _, err := b.NewSession(tc.username, "lang", "auth") + sessionID, _, err := b.NewSession(tc.username, "lang", sessionmode.Login) require.NoError(t, err, "Setup: Failed to create session for the tests") cachedInfo := generateCachedInfo(t, tc.token) diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index f605814fc8..adc06da364 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -14,6 +14,7 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/require" "github.com/ubuntu/authd-oidc-brokers/internal/broker" + "github.com/ubuntu/authd-oidc-brokers/internal/broker/sessionmode" "github.com/ubuntu/authd-oidc-brokers/internal/providers" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "github.com/ubuntu/authd-oidc-brokers/internal/testutils" @@ -118,7 +119,7 @@ func newSessionForTests(t *testing.T, b *broker.Broker, username, mode string) ( username = "test-user@email.com" } if mode == "" { - mode = "auth" + mode = sessionmode.Login } id, key, err := b.NewSession(username, "some lang", mode) diff --git a/internal/broker/sessionmode/consts.go b/internal/broker/sessionmode/consts.go new file mode 100644 index 0000000000..69a64d6a7e --- /dev/null +++ b/internal/broker/sessionmode/consts.go @@ -0,0 +1,9 @@ +// Package sessionmode defines the session modes supported by the broker. +package sessionmode + +const ( + // Login is used when the session is for user login. + Login = "auth" + // ChangePassword is used when the session is for changing the user password. + ChangePassword = "passwd" +) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index f7bf8f335c..5aab30c211 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -17,6 +17,7 @@ import ( msgraphauth "github.com/microsoftgraph/msgraph-sdk-go-core/authentication" msgraphmodels "github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/ubuntu/authd-oidc-brokers/internal/broker/authmodes" + "github.com/ubuntu/authd-oidc-brokers/internal/broker/sessionmode" "github.com/ubuntu/authd-oidc-brokers/internal/consts" providerErrors "github.com/ubuntu/authd-oidc-brokers/internal/providers/errors" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" @@ -286,7 +287,7 @@ func (p Provider) CurrentAuthenticationModesOffered( log.Debugf(context.Background(), "In CurrentAuthenticationModesOffered: sessionMode=%q, supportedAuthModes=%q, tokenExists=%t, providerReachable=%t, endpoints=%q, currentAuthStep=%d\n", sessionMode, supportedAuthModes, tokenExists, providerReachable, endpoints, currentAuthStep) var offeredModes []string switch sessionMode { - case "passwd": + case sessionmode.ChangePassword: if !tokenExists { return nil, errors.New("user has no cached token") } diff --git a/internal/providers/noprovider/noprovider.go b/internal/providers/noprovider/noprovider.go index 47ccf130d3..156c375f6d 100644 --- a/internal/providers/noprovider/noprovider.go +++ b/internal/providers/noprovider/noprovider.go @@ -8,6 +8,7 @@ import ( "github.com/coreos/go-oidc/v3/oidc" "github.com/ubuntu/authd-oidc-brokers/internal/broker/authmodes" + "github.com/ubuntu/authd-oidc-brokers/internal/broker/sessionmode" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "golang.org/x/oauth2" ) @@ -47,7 +48,7 @@ func (p NoProvider) CurrentAuthenticationModesOffered( ) ([]string, error) { var offeredModes []string switch sessionMode { - case "passwd": + case sessionmode.ChangePassword: if !tokenExists { return nil, errors.New("user has no cached token") } From a6d3e7e3df3629ae9a9f356a31ea63c2ec489af0 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 21 Jan 2025 12:24:33 +0100 Subject: [PATCH 0331/1670] Support new session mode names --- internal/broker/broker.go | 4 ++-- internal/broker/sessionmode/consts.go | 10 ++++++++-- internal/providers/msentraid/msentraid.go | 2 +- internal/providers/noprovider/noprovider.go | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index f498a8b60c..240f509c2d 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -400,7 +400,7 @@ func (b *Broker) generateUILayout(session *session, authModeID string) (map[stri case authmodes.NewPassword: label := "Create a local password" - if session.mode == sessionmode.ChangePassword { + if session.mode == sessionmode.ChangePassword || session.mode == sessionmode.ChangePasswordOld { label = "Update your local password" } @@ -597,7 +597,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au authInfo.UserInfo = userInfo } - if session.mode == sessionmode.ChangePassword { + if session.mode == sessionmode.ChangePassword || session.mode == sessionmode.ChangePasswordOld { session.authInfo["auth_info"] = authInfo return AuthNext, nil } diff --git a/internal/broker/sessionmode/consts.go b/internal/broker/sessionmode/consts.go index 69a64d6a7e..2472e65506 100644 --- a/internal/broker/sessionmode/consts.go +++ b/internal/broker/sessionmode/consts.go @@ -3,7 +3,13 @@ package sessionmode const ( // Login is used when the session is for user login. - Login = "auth" + Login = "login" + // LoginOld is the old name for the login session, which is now deprecated but still used by authd until all broker + // installations are updated. + LoginOld = "auth" // ChangePassword is used when the session is for changing the user password. - ChangePassword = "passwd" + ChangePassword = "change-password" + // ChangePasswordOld is the old name for the change-password session, which is now deprecated but still used by authd + // until all broker installations are updated. + ChangePasswordOld = "passwd" ) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 5aab30c211..9ecfef28cb 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -287,7 +287,7 @@ func (p Provider) CurrentAuthenticationModesOffered( log.Debugf(context.Background(), "In CurrentAuthenticationModesOffered: sessionMode=%q, supportedAuthModes=%q, tokenExists=%t, providerReachable=%t, endpoints=%q, currentAuthStep=%d\n", sessionMode, supportedAuthModes, tokenExists, providerReachable, endpoints, currentAuthStep) var offeredModes []string switch sessionMode { - case sessionmode.ChangePassword: + case sessionmode.ChangePassword, sessionmode.ChangePasswordOld: if !tokenExists { return nil, errors.New("user has no cached token") } diff --git a/internal/providers/noprovider/noprovider.go b/internal/providers/noprovider/noprovider.go index 156c375f6d..ac3bb429a6 100644 --- a/internal/providers/noprovider/noprovider.go +++ b/internal/providers/noprovider/noprovider.go @@ -48,7 +48,7 @@ func (p NoProvider) CurrentAuthenticationModesOffered( ) ([]string, error) { var offeredModes []string switch sessionMode { - case sessionmode.ChangePassword: + case sessionmode.ChangePassword, sessionmode.ChangePasswordOld: if !tokenExists { return nil, errors.New("user has no cached token") } From c9aeeebd3bcbbb238a2a5e08b22d2a524cda7fde Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 21 Jan 2025 14:54:22 +0100 Subject: [PATCH 0332/1670] Rename tests --- internal/broker/broker_test.go | 6 +++--- ...ated_with_password_and_session_is_for_changing_password} | 0 ...rd_if_token_exists_and_session_is_for_changing_password} | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename internal/broker/testdata/golden/TestGetAuthenticationModes/{Get_newpassword_if_already_authenticated_with_password_and_session_is_passwd => Get_newpassword_if_already_authenticated_with_password_and_session_is_for_changing_password} (100%) rename internal/broker/testdata/golden/TestGetAuthenticationModes/{Get_only_password_if_token_exists_and_session_is_passwd => Get_only_password_if_token_exists_and_session_is_for_changing_password} (100%) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 44dc8a7ef5..78bd3b66fc 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -180,8 +180,8 @@ func TestGetAuthenticationModes(t *testing.T) { "Get_only_password_if_token_exists_and_provider_does_not_support_device_auth_qr": {tokenExists: true, providerAddress: "127.0.0.1:31311", deviceAuthUnsupported: true}, // Change password session - "Get_only_password_if_token_exists_and_session_is_passwd": {sessionMode: sessionmode.ChangePassword, tokenExists: true}, - "Get_newpassword_if_already_authenticated_with_password_and_session_is_passwd": {sessionMode: sessionmode.ChangePassword, tokenExists: true, secondAuthStep: true}, + "Get_only_password_if_token_exists_and_session_is_for_changing_password": {sessionMode: sessionmode.ChangePassword, tokenExists: true}, + "Get_newpassword_if_already_authenticated_with_password_and_session_is_for_changing_password": {sessionMode: sessionmode.ChangePassword, tokenExists: true, secondAuthStep: true}, "Error_if_there_is_no_session": {sessionID: "-", wantErr: true}, @@ -193,7 +193,7 @@ func TestGetAuthenticationModes(t *testing.T) { "Error_if_expecting_password_but_not_supported": {supportedLayouts: []string{"form-without-entry"}, wantErr: true}, // Change password session errors - "Error_if_session_is_passwd_but_token_does_not_exist": {sessionMode: sessionmode.ChangePassword, wantErr: true}, + "Error_if_session_is_for_changing_password_but_token_does_not_exist": {sessionMode: sessionmode.ChangePassword, wantErr: true}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { diff --git a/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_newpassword_if_already_authenticated_with_password_and_session_is_passwd b/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_newpassword_if_already_authenticated_with_password_and_session_is_for_changing_password similarity index 100% rename from internal/broker/testdata/golden/TestGetAuthenticationModes/Get_newpassword_if_already_authenticated_with_password_and_session_is_passwd rename to internal/broker/testdata/golden/TestGetAuthenticationModes/Get_newpassword_if_already_authenticated_with_password_and_session_is_for_changing_password diff --git a/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_only_password_if_token_exists_and_session_is_passwd b/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_only_password_if_token_exists_and_session_is_for_changing_password similarity index 100% rename from internal/broker/testdata/golden/TestGetAuthenticationModes/Get_only_password_if_token_exists_and_session_is_passwd rename to internal/broker/testdata/golden/TestGetAuthenticationModes/Get_only_password_if_token_exists_and_session_is_for_changing_password From 84842438d4028d5e3e524cfca399ed09a844c862 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 21 Jan 2025 14:55:40 +0100 Subject: [PATCH 0333/1670] Add test for old session mode value --- internal/broker/broker_test.go | 1 + ...ord_if_token_exists_and_session_mode_is_the_old_passwd_value | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 internal/broker/testdata/golden/TestGetAuthenticationModes/Get_only_password_if_token_exists_and_session_mode_is_the_old_passwd_value diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 78bd3b66fc..ce8578d9b3 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -182,6 +182,7 @@ func TestGetAuthenticationModes(t *testing.T) { // Change password session "Get_only_password_if_token_exists_and_session_is_for_changing_password": {sessionMode: sessionmode.ChangePassword, tokenExists: true}, "Get_newpassword_if_already_authenticated_with_password_and_session_is_for_changing_password": {sessionMode: sessionmode.ChangePassword, tokenExists: true, secondAuthStep: true}, + "Get_only_password_if_token_exists_and_session_mode_is_the_old_passwd_value": {sessionMode: sessionmode.ChangePasswordOld, tokenExists: true}, "Error_if_there_is_no_session": {sessionID: "-", wantErr: true}, diff --git a/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_only_password_if_token_exists_and_session_mode_is_the_old_passwd_value b/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_only_password_if_token_exists_and_session_mode_is_the_old_passwd_value new file mode 100644 index 0000000000..e4e7cb1d35 --- /dev/null +++ b/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_only_password_if_token_exists_and_session_mode_is_the_old_passwd_value @@ -0,0 +1,2 @@ +- id: password + label: Local Password Authentication From 2efde10cfe6e47a67e94133a17960623af14d5f7 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 21 Jan 2025 13:57:21 +0100 Subject: [PATCH 0334/1670] Fix incorrect uses of the term "challenge" Use "secret" instead. --- internal/broker/broker.go | 18 +++---- internal/broker/broker_test.go | 50 +++++++++---------- internal/broker/encrypt.go | 14 +++--- internal/broker/helper_test.go | 8 +-- .../first_call | 3 -- .../second_call | 3 -- .../data/.empty | 0 .../first_call | 0 .../second_call | 3 ++ .../provider_url/test-user@email.com/password | 0 .../test-user@email.com/token.json | 0 .../first_call | 0 .../data/.empty | 0 .../first_call | 3 ++ 14 files changed, 51 insertions(+), 51 deletions(-) delete mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Error_when_challenge_can_not_be_decrypted/first_call delete mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Error_when_empty_challenge_is_provided_for_local_password/second_call rename internal/broker/testdata/golden/TestIsAuthenticated/{Error_when_challenge_can_not_be_decrypted => Error_when_empty_secret_is_provided_for_local_password}/data/.empty (100%) rename internal/broker/testdata/golden/TestIsAuthenticated/{Error_when_empty_challenge_is_provided_for_local_password => Error_when_empty_secret_is_provided_for_local_password}/first_call (100%) create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Error_when_empty_secret_is_provided_for_local_password/second_call rename internal/broker/testdata/golden/TestIsAuthenticated/{Error_when_provided_wrong_challenge => Error_when_provided_wrong_secret}/data/provider_url/test-user@email.com/password (100%) rename internal/broker/testdata/golden/TestIsAuthenticated/{Error_when_provided_wrong_challenge => Error_when_provided_wrong_secret}/data/provider_url/test-user@email.com/token.json (100%) rename internal/broker/testdata/golden/TestIsAuthenticated/{Error_when_provided_wrong_challenge => Error_when_provided_wrong_secret}/first_call (100%) rename internal/broker/testdata/golden/TestIsAuthenticated/{Error_when_empty_challenge_is_provided_for_local_password => Error_when_secret_can_not_be_decrypted}/data/.empty (100%) create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Error_when_secret_can_not_be_decrypted/first_call diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 240f509c2d..1b61e12cbc 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -483,11 +483,11 @@ func (b *Broker) IsAuthenticated(sessionID, authenticationData string) (string, func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, authData map[string]string) (access string, data isAuthenticatedDataResponse) { defer decorateErrorMessage(&data, "authentication failure") - // Decrypt challenge if present. - challenge, err := decodeRawChallenge(b.privateKey, authData["challenge"]) + // Decrypt secret if present. + secret, err := decodeRawSecret(b.privateKey, authData["challenge"]) if err != nil { log.Error(context.Background(), err.Error()) - return AuthRetry, errorMessage{Message: "could not decode challenge"} + return AuthRetry, errorMessage{Message: "could not decode secret"} } var authInfo token.AuthCachedInfo @@ -545,7 +545,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au } if useOldEncryptedToken { - authInfo, err = token.LoadOldEncryptedAuthInfo(session.oldEncryptedTokenPath, challenge) + authInfo, err = token.LoadOldEncryptedAuthInfo(session.oldEncryptedTokenPath, secret) if err != nil { log.Error(context.Background(), err.Error()) return AuthDenied, errorMessage{Message: "could not load encrypted token"} @@ -553,12 +553,12 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au // We were able to decrypt the old token with the password, so we can now hash and store the password in the // new format. - if err = password.HashAndStorePassword(challenge, session.passwordPath); err != nil { + if err = password.HashAndStorePassword(secret, session.passwordPath); err != nil { log.Error(context.Background(), err.Error()) return AuthDenied, errorMessage{Message: "could not store password"} } } else { - ok, err := password.CheckPassword(challenge, session.passwordPath) + ok, err := password.CheckPassword(secret, session.passwordPath) if err != nil { log.Error(context.Background(), err.Error()) return AuthDenied, errorMessage{Message: "could not check password"} @@ -603,8 +603,8 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au } case authmodes.NewPassword: - if challenge == "" { - return AuthRetry, errorMessage{Message: "empty challenge"} + if secret == "" { + return AuthRetry, errorMessage{Message: "empty secret"} } var ok bool @@ -615,7 +615,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au return AuthDenied, errorMessage{Message: "could not get required information"} } - if err = password.HashAndStorePassword(challenge, session.passwordPath); err != nil { + if err = password.HashAndStorePassword(secret, session.passwordPath); err != nil { log.Error(context.Background(), err.Error()) return AuthDenied, errorMessage{Message: "could not store password"} } diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index ce8578d9b3..ad13d55bc6 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -385,7 +385,7 @@ func TestIsAuthenticated(t *testing.T) { username string firstMode string - firstChallenge string + firstSecret string firstAuthInfo map[string]any badFirstKey bool getUserInfoFails bool @@ -394,9 +394,9 @@ func TestIsAuthenticated(t *testing.T) { customHandlers map[string]testutils.EndpointHandler address string - wantSecondCall bool - secondMode string - secondChallenge string + wantSecondCall bool + secondMode string + secondSecret string token *tokenOptions invalidAuthData bool @@ -404,10 +404,10 @@ func TestIsAuthenticated(t *testing.T) { readOnlyDataDir bool wantGroups []info.Group }{ - "Successfully_authenticate_user_with_device_auth_and_newpassword": {firstChallenge: "-", wantSecondCall: true}, + "Successfully_authenticate_user_with_device_auth_and_newpassword": {firstSecret: "-", wantSecondCall: true}, "Successfully_authenticate_user_with_password": {firstMode: authmodes.Password, token: &tokenOptions{}}, - "Authenticating_with_qrcode_reacquires_token": {firstChallenge: "-", wantSecondCall: true, token: &tokenOptions{}}, + "Authenticating_with_qrcode_reacquires_token": {firstSecret: "-", wantSecondCall: true, token: &tokenOptions{}}, "Authenticating_with_password_refreshes_expired_token": {firstMode: authmodes.Password, token: &tokenOptions{expired: true}}, "Authenticating_with_password_still_allowed_if_server_is_unreachable": { firstMode: authmodes.Password, @@ -424,7 +424,7 @@ func TestIsAuthenticated(t *testing.T) { }, }, "Authenticating_still_allowed_if_token_is_missing_scopes": { - firstChallenge: "-", + firstSecret: "-", wantSecondCall: true, customHandlers: map[string]testutils.EndpointHandler{ "/token": testutils.TokenHandler("http://127.0.0.1:31313", nil), @@ -451,9 +451,9 @@ func TestIsAuthenticated(t *testing.T) { }, "Error_when_authentication_data_is_invalid": {invalidAuthData: true}, - "Error_when_challenge_can_not_be_decrypted": {firstMode: authmodes.Password, badFirstKey: true}, - "Error_when_provided_wrong_challenge": {firstMode: authmodes.Password, token: &tokenOptions{}, firstChallenge: "wrongpassword"}, - "Error_when_can_not_cache_token": {firstChallenge: "-", wantSecondCall: true, readOnlyDataDir: true}, + "Error_when_secret_can_not_be_decrypted": {firstMode: authmodes.Password, badFirstKey: true}, + "Error_when_provided_wrong_secret": {firstMode: authmodes.Password, token: &tokenOptions{}, firstSecret: "wrongpassword"}, + "Error_when_can_not_cache_token": {firstSecret: "-", wantSecondCall: true, readOnlyDataDir: true}, "Error_when_IsAuthenticated_is_ongoing_for_session": {dontWaitForFirstCall: true, wantSecondCall: true}, "Error_when_mode_is_password_and_token_does_not_exist": {firstMode: authmodes.Password}, @@ -514,10 +514,10 @@ func TestIsAuthenticated(t *testing.T) { "/token": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, }, - "Error_when_empty_challenge_is_provided_for_local_password": {firstChallenge: "-", wantSecondCall: true, secondChallenge: "-"}, - "Error_when_mode_is_newpassword_and_session_has_no_token": {firstMode: authmodes.NewPassword}, + "Error_when_empty_secret_is_provided_for_local_password": {firstSecret: "-", wantSecondCall: true, secondSecret: "-"}, + "Error_when_mode_is_newpassword_and_session_has_no_token": {firstMode: authmodes.NewPassword}, // This test case also tests that errors with double quotes are marshaled to JSON correctly. - "Error_when_selected_username_does_not_match_the_provider_one": {username: "not-matching", firstChallenge: "-"}, + "Error_when_selected_username_does_not_match_the_provider_one": {username: "not-matching", firstSecret: "-"}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { @@ -577,20 +577,20 @@ func TestIsAuthenticated(t *testing.T) { t.Cleanup(readOnlyDataCleanup) } - switch tc.firstChallenge { + switch tc.firstSecret { case "": - tc.firstChallenge = correctPassword + tc.firstSecret = correctPassword case "-": - tc.firstChallenge = "" + tc.firstSecret = "" } authData := "{}" - if tc.firstChallenge != "" { + if tc.firstSecret != "" { eKey := key if tc.badFirstKey { eKey = "" } - authData = `{"challenge":"` + encryptChallenge(t, tc.firstChallenge, eKey) + `"}` + authData = `{"challenge":"` + encryptSecret(t, tc.firstSecret, eKey) + `"}` } if tc.invalidAuthData { authData = "invalid json" @@ -641,12 +641,12 @@ func TestIsAuthenticated(t *testing.T) { // Give some time for the first call time.Sleep(10 * time.Millisecond) - challenge := "passwordpassword" - if tc.secondChallenge == "-" { - challenge = "" + secret := "passwordpassword" + if tc.secondSecret == "-" { + secret = "" } - secondAuthData := `{"challenge":"` + encryptChallenge(t, challenge, key) + `"}` + secondAuthData := `{"challenge":"` + encryptSecret(t, secret, key) + `"}` if tc.invalidAuthData { secondAuthData = "invalid json" } @@ -789,7 +789,7 @@ func TestConcurrentIsAuthenticated(t *testing.T) { updateAuthModes(t, b, firstSession, authmodes.Password) - authData := `{"challenge":"` + encryptChallenge(t, "password", firstKey) + `"}` + authData := `{"challenge":"` + encryptSecret(t, "password", firstKey) + `"}` access, data, err := b.IsAuthenticated(firstSession, authData) require.True(t, json.Valid([]byte(data)), "IsAuthenticated returned data must be a valid JSON") @@ -813,7 +813,7 @@ func TestConcurrentIsAuthenticated(t *testing.T) { updateAuthModes(t, b, secondSession, authmodes.Password) - authData := `{"challenge":"` + encryptChallenge(t, "password", secondKey) + `"}` + authData := `{"challenge":"` + encryptSecret(t, "password", secondKey) + `"}` access, data, err := b.IsAuthenticated(secondSession, authData) require.True(t, json.Valid([]byte(data)), "IsAuthenticated returned data must be a valid JSON") @@ -959,7 +959,7 @@ func TestIsAuthenticatedAllowedUsersConfig(t *testing.T) { updateAuthModes(t, b, sessionID, authmodes.Password) - authData := `{"challenge":"` + encryptChallenge(t, "password", key) + `"}` + authData := `{"challenge":"` + encryptSecret(t, "password", key) + `"}` access, data, err := b.IsAuthenticated(sessionID, authData) require.True(t, json.Valid([]byte(data)), "IsAuthenticated returned data must be a valid JSON") diff --git a/internal/broker/encrypt.go b/internal/broker/encrypt.go index 01af3c6e83..5163d1b25e 100644 --- a/internal/broker/encrypt.go +++ b/internal/broker/encrypt.go @@ -11,29 +11,29 @@ import ( "github.com/ubuntu/authd/log" ) -// decodeRawChallenge extract the base64 challenge and try to decrypt it with the private key. -func decodeRawChallenge(priv *rsa.PrivateKey, rawChallenge string) (decoded string, err error) { +// decodeRawSecret extract the base64 secret and try to decrypt it with the private key. +func decodeRawSecret(priv *rsa.PrivateKey, rawSecret string) (decoded string, err error) { defer func() { // Override the error so that we don't leak information. Also, abstract it for the user. // We still log as error for the admin to get access. if err != nil { - log.Errorf(context.Background(), "Error when decoding challenge: %v", err) - err = errors.New("could not decode challenge") + log.Errorf(context.Background(), "Error when decoding secret: %v", err) + err = errors.New("could not decode secret") } }() - if rawChallenge == "" { + if rawSecret == "" { return "", nil } - ciphertext, err := base64.StdEncoding.DecodeString(rawChallenge) + ciphertext, err := base64.StdEncoding.DecodeString(rawSecret) if err != nil { return "", err } plaintext, err := rsa.DecryptOAEP(sha512.New(), nil, priv, ciphertext, nil) if err != nil { - return "", fmt.Errorf("could not decrypt challenge: %v", err) + return "", fmt.Errorf("could not decrypt secret: %v", err) } return string(plaintext), nil diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index adc06da364..c17187fef3 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -128,11 +128,11 @@ func newSessionForTests(t *testing.T, b *broker.Broker, username, mode string) ( return id, key } -func encryptChallenge(t *testing.T, challenge, strKey string) string { +func encryptSecret(t *testing.T, secret, strKey string) string { t.Helper() if strKey == "" { - return challenge + return secret } pubASN1, err := base64.StdEncoding.DecodeString(strKey) @@ -144,10 +144,10 @@ func encryptChallenge(t *testing.T, challenge, strKey string) string { rsaPubKey, ok := pubKey.(*rsa.PublicKey) require.True(t, ok, "Setup: public key should be an RSA key") - ciphertext, err := rsa.EncryptOAEP(sha512.New(), rand.Reader, rsaPubKey, []byte(challenge), nil) + ciphertext, err := rsa.EncryptOAEP(sha512.New(), rand.Reader, rsaPubKey, []byte(secret), nil) require.NoError(t, err, "Setup: encryption should not have failed") - // encrypt it to base64 and replace the challenge with it + // encrypt it to base64 and replace the secret with it return base64.StdEncoding.EncodeToString(ciphertext) } diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_challenge_can_not_be_decrypted/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_challenge_can_not_be_decrypted/first_call deleted file mode 100644 index 919fc1d7cd..0000000000 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_challenge_can_not_be_decrypted/first_call +++ /dev/null @@ -1,3 +0,0 @@ -access: retry -data: '{"message":"authentication failure: could not decode challenge"}' -err: diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_empty_challenge_is_provided_for_local_password/second_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_empty_challenge_is_provided_for_local_password/second_call deleted file mode 100644 index 8bb152f9ba..0000000000 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_empty_challenge_is_provided_for_local_password/second_call +++ /dev/null @@ -1,3 +0,0 @@ -access: retry -data: '{"message":"authentication failure: empty challenge"}' -err: diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_challenge_can_not_be_decrypted/data/.empty b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_empty_secret_is_provided_for_local_password/data/.empty similarity index 100% rename from internal/broker/testdata/golden/TestIsAuthenticated/Error_when_challenge_can_not_be_decrypted/data/.empty rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_empty_secret_is_provided_for_local_password/data/.empty diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_empty_challenge_is_provided_for_local_password/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_empty_secret_is_provided_for_local_password/first_call similarity index 100% rename from internal/broker/testdata/golden/TestIsAuthenticated/Error_when_empty_challenge_is_provided_for_local_password/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_empty_secret_is_provided_for_local_password/first_call diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_empty_secret_is_provided_for_local_password/second_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_empty_secret_is_provided_for_local_password/second_call new file mode 100644 index 0000000000..71438bc359 --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_empty_secret_is_provided_for_local_password/second_call @@ -0,0 +1,3 @@ +access: retry +data: '{"message":"authentication failure: empty secret"}' +err: diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provided_wrong_secret/data/provider_url/test-user@email.com/password similarity index 100% rename from internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/password rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provided_wrong_secret/data/provider_url/test-user@email.com/password diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provided_wrong_secret/data/provider_url/test-user@email.com/token.json similarity index 100% rename from internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provided_wrong_challenge/data/provider_url/test-user@email.com/token.json rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provided_wrong_secret/data/provider_url/test-user@email.com/token.json diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provided_wrong_challenge/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provided_wrong_secret/first_call similarity index 100% rename from internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provided_wrong_challenge/first_call rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provided_wrong_secret/first_call diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_empty_challenge_is_provided_for_local_password/data/.empty b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_secret_can_not_be_decrypted/data/.empty similarity index 100% rename from internal/broker/testdata/golden/TestIsAuthenticated/Error_when_empty_challenge_is_provided_for_local_password/data/.empty rename to internal/broker/testdata/golden/TestIsAuthenticated/Error_when_secret_can_not_be_decrypted/data/.empty diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_secret_can_not_be_decrypted/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_secret_can_not_be_decrypted/first_call new file mode 100644 index 0000000000..73295e1dff --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_secret_can_not_be_decrypted/first_call @@ -0,0 +1,3 @@ +access: retry +data: '{"message":"authentication failure: could not decode secret"}' +err: From d10164726afadc5dc6bee7e248bb017fa4cfe120 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 21 Jan 2025 14:00:37 +0100 Subject: [PATCH 0335/1670] Support auth data field "secret" --- internal/broker/broker.go | 7 ++++++- internal/broker/broker_test.go | 10 +++++----- internal/broker/consts.go | 8 ++++++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 1b61e12cbc..febf52697e 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -483,8 +483,13 @@ func (b *Broker) IsAuthenticated(sessionID, authenticationData string) (string, func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, authData map[string]string) (access string, data isAuthenticatedDataResponse) { defer decorateErrorMessage(&data, "authentication failure") + rawSecret, ok := authData[AuthDataSecret] + if !ok { + rawSecret = authData[AuthDataSecretOld] + } + // Decrypt secret if present. - secret, err := decodeRawSecret(b.privateKey, authData["challenge"]) + secret, err := decodeRawSecret(b.privateKey, rawSecret) if err != nil { log.Error(context.Background(), err.Error()) return AuthRetry, errorMessage{Message: "could not decode secret"} diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index ad13d55bc6..4b9d0802b3 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -590,7 +590,7 @@ func TestIsAuthenticated(t *testing.T) { if tc.badFirstKey { eKey = "" } - authData = `{"challenge":"` + encryptSecret(t, tc.firstSecret, eKey) + `"}` + authData = `{"secret":"` + encryptSecret(t, tc.firstSecret, eKey) + `"}` } if tc.invalidAuthData { authData = "invalid json" @@ -646,7 +646,7 @@ func TestIsAuthenticated(t *testing.T) { secret = "" } - secondAuthData := `{"challenge":"` + encryptSecret(t, secret, key) + `"}` + secondAuthData := `{"secret":"` + encryptSecret(t, secret, key) + `"}` if tc.invalidAuthData { secondAuthData = "invalid json" } @@ -789,7 +789,7 @@ func TestConcurrentIsAuthenticated(t *testing.T) { updateAuthModes(t, b, firstSession, authmodes.Password) - authData := `{"challenge":"` + encryptSecret(t, "password", firstKey) + `"}` + authData := `{"secret":"` + encryptSecret(t, "password", firstKey) + `"}` access, data, err := b.IsAuthenticated(firstSession, authData) require.True(t, json.Valid([]byte(data)), "IsAuthenticated returned data must be a valid JSON") @@ -813,7 +813,7 @@ func TestConcurrentIsAuthenticated(t *testing.T) { updateAuthModes(t, b, secondSession, authmodes.Password) - authData := `{"challenge":"` + encryptSecret(t, "password", secondKey) + `"}` + authData := `{"secret":"` + encryptSecret(t, "password", secondKey) + `"}` access, data, err := b.IsAuthenticated(secondSession, authData) require.True(t, json.Valid([]byte(data)), "IsAuthenticated returned data must be a valid JSON") @@ -959,7 +959,7 @@ func TestIsAuthenticatedAllowedUsersConfig(t *testing.T) { updateAuthModes(t, b, sessionID, authmodes.Password) - authData := `{"challenge":"` + encryptSecret(t, "password", key) + `"}` + authData := `{"secret":"` + encryptSecret(t, "password", key) + `"}` access, data, err := b.IsAuthenticated(sessionID, authData) require.True(t, json.Valid([]byte(data)), "IsAuthenticated returned data must be a valid JSON") diff --git a/internal/broker/consts.go b/internal/broker/consts.go index dd1a08a4f7..0365cf2f91 100644 --- a/internal/broker/consts.go +++ b/internal/broker/consts.go @@ -16,3 +16,11 @@ const ( // AuthReplies is the list of all possible authentication replies. var AuthReplies = []string{AuthGranted, AuthDenied, AuthCancelled, AuthRetry, AuthNext} + +const ( + // AuthDataSecret is the key for the secret in the authentication data. + AuthDataSecret = "secret" + // AuthDataSecretOld is the old key for the secret in the authentication data, which is now deprecated + // TODO(UDENG-5844): Remove this once all authd installations use "secret" instead of "challenge". + AuthDataSecretOld = "challenge" +) From 612e715e4c9b6b8fed9330195ed7fc1def5b8470 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 22 Jan 2025 12:53:08 +0100 Subject: [PATCH 0336/1670] Replace some literal strings with the new AuthDataSecret constant --- internal/broker/broker_test.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 4b9d0802b3..6f345e996f 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -590,7 +590,8 @@ func TestIsAuthenticated(t *testing.T) { if tc.badFirstKey { eKey = "" } - authData = `{"secret":"` + encryptSecret(t, tc.firstSecret, eKey) + `"}` + secret := encryptSecret(t, tc.firstSecret, eKey) + authData = fmt.Sprintf(`{"%s":"%s"}`, broker.AuthDataSecret, secret) } if tc.invalidAuthData { authData = "invalid json" @@ -646,7 +647,7 @@ func TestIsAuthenticated(t *testing.T) { secret = "" } - secondAuthData := `{"secret":"` + encryptSecret(t, secret, key) + `"}` + secondAuthData := fmt.Sprintf(`{"%s":"%s"}`, broker.AuthDataSecret, encryptSecret(t, secret, key)) if tc.invalidAuthData { secondAuthData = "invalid json" } @@ -789,7 +790,8 @@ func TestConcurrentIsAuthenticated(t *testing.T) { updateAuthModes(t, b, firstSession, authmodes.Password) - authData := `{"secret":"` + encryptSecret(t, "password", firstKey) + `"}` + secret := encryptSecret(t, "password", firstKey) + authData := fmt.Sprintf(`{"%s":"%s"}`, broker.AuthDataSecret, secret) access, data, err := b.IsAuthenticated(firstSession, authData) require.True(t, json.Valid([]byte(data)), "IsAuthenticated returned data must be a valid JSON") @@ -813,7 +815,8 @@ func TestConcurrentIsAuthenticated(t *testing.T) { updateAuthModes(t, b, secondSession, authmodes.Password) - authData := `{"secret":"` + encryptSecret(t, "password", secondKey) + `"}` + secret := encryptSecret(t, "password", secondKey) + authData := fmt.Sprintf(`{"%s":"%s"}`, broker.AuthDataSecret, secret) access, data, err := b.IsAuthenticated(secondSession, authData) require.True(t, json.Valid([]byte(data)), "IsAuthenticated returned data must be a valid JSON") @@ -959,7 +962,8 @@ func TestIsAuthenticatedAllowedUsersConfig(t *testing.T) { updateAuthModes(t, b, sessionID, authmodes.Password) - authData := `{"secret":"` + encryptSecret(t, "password", key) + `"}` + secret := encryptSecret(t, "password", key) + authData := fmt.Sprintf(`{"%s":"%s"}`, broker.AuthDataSecret, secret) access, data, err := b.IsAuthenticated(sessionID, authData) require.True(t, json.Valid([]byte(data)), "IsAuthenticated returned data must be a valid JSON") From 9797e19bc096aa545b88c68736d86ea097c8d682 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 22 Jan 2025 13:05:50 +0100 Subject: [PATCH 0337/1670] Add test for old secret field name --- internal/broker/broker_test.go | 19 +++++++++++++++++-- .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json | 1 + .../first_call | 3 +++ 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_when_the_auth_data_secret_field_uses_the_old_name/data/provider_url/test-user@email.com/password create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_when_the_auth_data_secret_field_uses_the_old_name/data/provider_url/test-user@email.com/token.json create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_when_the_auth_data_secret_field_uses_the_old_name/first_call diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 6f345e996f..03225ea1bd 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -389,6 +389,7 @@ func TestIsAuthenticated(t *testing.T) { firstAuthInfo map[string]any badFirstKey bool getUserInfoFails bool + useOldNameForSecretField bool groupsReturnedByProvider []info.Group customHandlers map[string]testutils.EndpointHandler @@ -449,6 +450,11 @@ func TestIsAuthenticated(t *testing.T) { sessionOffline: true, wantGroups: []info.Group{{Name: "old-group"}}, }, + "Authenticating_when_the_auth_data_secret_field_uses_the_old_name": { + firstMode: authmodes.Password, + token: &tokenOptions{}, + useOldNameForSecretField: true, + }, "Error_when_authentication_data_is_invalid": {invalidAuthData: true}, "Error_when_secret_can_not_be_decrypted": {firstMode: authmodes.Password, badFirstKey: true}, @@ -591,7 +597,11 @@ func TestIsAuthenticated(t *testing.T) { eKey = "" } secret := encryptSecret(t, tc.firstSecret, eKey) - authData = fmt.Sprintf(`{"%s":"%s"}`, broker.AuthDataSecret, secret) + field := broker.AuthDataSecret + if tc.useOldNameForSecretField { + field = broker.AuthDataSecretOld + } + authData = fmt.Sprintf(`{"%s":"%s"}`, field, secret) } if tc.invalidAuthData { authData = "invalid json" @@ -647,7 +657,12 @@ func TestIsAuthenticated(t *testing.T) { secret = "" } - secondAuthData := fmt.Sprintf(`{"%s":"%s"}`, broker.AuthDataSecret, encryptSecret(t, secret, key)) + secret = encryptSecret(t, secret, key) + field := broker.AuthDataSecret + if tc.useOldNameForSecretField { + field = broker.AuthDataSecretOld + } + secondAuthData := fmt.Sprintf(`{"%s":"%s"}`, field, secret) if tc.invalidAuthData { secondAuthData = "invalid json" } diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_when_the_auth_data_secret_field_uses_the_old_name/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_when_the_auth_data_secret_field_uses_the_old_name/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_when_the_auth_data_secret_field_uses_the_old_name/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_when_the_auth_data_secret_field_uses_the_old_name/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_when_the_auth_data_secret_field_uses_the_old_name/data/provider_url/test-user@email.com/token.json new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_when_the_auth_data_secret_field_uses_the_old_name/data/provider_url/test-user@email.com/token.json @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_when_the_auth_data_secret_field_uses_the_old_name/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_when_the_auth_data_secret_field_uses_the_old_name/first_call new file mode 100644 index 0000000000..f50b5eb551 --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_when_the_auth_data_secret_field_uses_the_old_name/first_call @@ -0,0 +1,3 @@ +access: granted +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-test-group","ugid":"12345"},{"name":"local-test-group","ugid":""}]}}' +err: From 864077dbf7ae6bee3f16f3291870f0f970eb4f24 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 24 Jan 2025 17:01:31 +0100 Subject: [PATCH 0338/1670] Fix allowed users migration The INITIAL_ALLOWED_USERS_VERSION was changed in 1d98bf26fda178f0e76f5942b8d6c152819e05e6 but the migrations directory was not renamed, so no files were copied. --- .../broker.conf.d/00-migration-allowed_users.conf | 0 .../broker.conf.d/10-allowed_users.conf | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename conf/migrations/{pre-0.2.0 => pre-0.2.0-pre1}/broker.conf.d/00-migration-allowed_users.conf (100%) rename conf/migrations/{pre-0.2.0 => pre-0.2.0-pre1}/broker.conf.d/10-allowed_users.conf (100%) diff --git a/conf/migrations/pre-0.2.0/broker.conf.d/00-migration-allowed_users.conf b/conf/migrations/pre-0.2.0-pre1/broker.conf.d/00-migration-allowed_users.conf similarity index 100% rename from conf/migrations/pre-0.2.0/broker.conf.d/00-migration-allowed_users.conf rename to conf/migrations/pre-0.2.0-pre1/broker.conf.d/00-migration-allowed_users.conf diff --git a/conf/migrations/pre-0.2.0/broker.conf.d/10-allowed_users.conf b/conf/migrations/pre-0.2.0-pre1/broker.conf.d/10-allowed_users.conf similarity index 100% rename from conf/migrations/pre-0.2.0/broker.conf.d/10-allowed_users.conf rename to conf/migrations/pre-0.2.0-pre1/broker.conf.d/10-allowed_users.conf From f39babcf0719f02b643a7803a845b1ebb5383ee3 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 24 Jan 2025 17:02:34 +0100 Subject: [PATCH 0339/1670] Log error if source directory of allowed users migration doesn't exist Would have helped to debug the bug that was fixed by the previous commit. --- snap/hooks/post-refresh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/snap/hooks/post-refresh b/snap/hooks/post-refresh index 9f7a90fd60..169a4051ff 100755 --- a/snap/hooks/post-refresh +++ b/snap/hooks/post-refresh @@ -58,6 +58,12 @@ transition_to_allowed_users() { log "Transitioning to allowed users" src_dir="${SNAP}/conf/migrations/pre-${INITIAL_ALLOWED_USERS_VERSION}/broker.conf.d" dest_dir="${SNAP_DATA}/broker.conf.d" + + if [ ! -d "${src_dir}" ]; then + log "Error: ${src_dir} does not exist, cannot transition to allowed users" + return + fi + # shellcheck disable=SC2174 # it's fine that --mode only applies to the deepest directory, because the SNAP_DATA # directory is created by snapd with the correct permissions. mkdir -p --mode=0700 "${dest_dir}" From ac86ce80253f0edcd84df7034864d2f2f16259ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 08:16:59 +0000 Subject: [PATCH 0340/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.58.0 to 1.59.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.58.0...v1.59.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 53f574ed3b..7f9aff0398 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.58.0 + github.com/microsoftgraph/msgraph-sdk-go v1.59.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 diff --git a/go.sum b/go.sum index de0a972a7d..c63f85b384 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.0.0 h1:3O5sb5Zj+moLBiJy github.com/microsoft/kiota-serialization-multipart-go v1.0.0/go.mod h1:yauLeBTpANk4L03XD985akNysG24SnRJGaveZf+p4so= github.com/microsoft/kiota-serialization-text-go v1.0.0 h1:XOaRhAXy+g8ZVpcq7x7a0jlETWnWrEum0RhmbYrTFnA= github.com/microsoft/kiota-serialization-text-go v1.0.0/go.mod h1:sM1/C6ecnQ7IquQOGUrUldaO5wj+9+v7G2W3sQ3fy6M= -github.com/microsoftgraph/msgraph-sdk-go v1.58.0 h1:FJgv6RfARoO31wyV3djkQRnxa3Lx27HKtCOHEDXd85c= -github.com/microsoftgraph/msgraph-sdk-go v1.58.0/go.mod h1:2dZJTO/7S+UmfwKlPQg2vczpW7awcIEj1ZCgfZIf0V4= +github.com/microsoftgraph/msgraph-sdk-go v1.59.0 h1:2zyS9XPyWfxUhR3dEd0QcijbKRqM80luVD4vX7S0Ntg= +github.com/microsoftgraph/msgraph-sdk-go v1.59.0/go.mod h1:2dZJTO/7S+UmfwKlPQg2vczpW7awcIEj1ZCgfZIf0V4= github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 h1:P1wpmn3xxfPMFJHg+PJPcusErfRkl63h6OdAnpDbkS8= github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1/go.mod h1:vFmWQGWyLlhxCESNLv61vlE4qesBU+eWmEVH7DJSESA= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= From 5de703cbae279ae1e7a228de3617b5a0ed68f394 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 24 Jan 2025 15:31:38 +0100 Subject: [PATCH 0341/1670] Log login attempts of users which are not allowed To 1. allow the user/admin to figure out why authentication is denied, and 2. log failed login attempts, which is security critical (security logging failure is in the OWASP top 10, see https://owasp.org/Top10/A09_2021-Security_Logging_and_Monitoring_Failures). --- internal/broker/broker.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index febf52697e..6dcd1611b4 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -634,6 +634,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au } if !b.userNameIsAllowed(authInfo.UserInfo.Name) { + log.Warningf(context.Background(), "User %q is not in the list of allowed users", authInfo.UserInfo.Name) return AuthDenied, errorMessage{Message: "permission denied"} } From 66999ccaae244a74924323d819003e5457a4872c Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 24 Jan 2025 15:38:05 +0100 Subject: [PATCH 0342/1670] Log the username which doesn't match the allowed suffix To have more complete logs of failed login attempts. --- internal/broker/broker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 6dcd1611b4..d50391b6b6 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -741,7 +741,7 @@ func (b *Broker) UserPreCheck(username string) (string, error) { } if !found { - return "", errors.New("username does not match the allowed suffixes") + return "", fmt.Errorf("username %q does not match any allowed suffix", username) } u := info.NewUser(username, filepath.Join(b.cfg.homeBaseDir, username), "", "", "", nil) From 80adc0c2ec8b18aeb641161540e6b3e8751758b9 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 24 Jan 2025 15:39:44 +0100 Subject: [PATCH 0343/1670] Add debug output Print the IsAuthenticated result for debugging. --- internal/dbusservice/methods.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/dbusservice/methods.go b/internal/dbusservice/methods.go index 06744a7d08..0747ee87c8 100644 --- a/internal/dbusservice/methods.go +++ b/internal/dbusservice/methods.go @@ -1,7 +1,10 @@ package dbusservice import ( + "context" + "github.com/godbus/dbus/v5" + "github.com/ubuntu/authd/log" ) // NewSession is the method through which the broker and the daemon will communicate once dbusInterface.NewSession is called. @@ -35,8 +38,10 @@ func (s *Service) SelectAuthenticationMode(sessionID, authenticationModeName str func (s *Service) IsAuthenticated(sessionID, authenticationData string) (access, data string, dbusErr *dbus.Error) { access, data, err := s.broker.IsAuthenticated(sessionID, authenticationData) if err != nil { + log.Warningf(context.Background(), "IsAuthenticated error: %v", err) return "", "", dbus.MakeFailedError(err) } + log.Debugf(context.Background(), "IsAuthenticated: %s", access) return access, data, nil } From 7296ada4746f5ae93d9cebf954d7d3e4b4ad4679 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 24 Jan 2025 17:51:23 +0100 Subject: [PATCH 0344/1670] Fail earlier if user is not allowed to log in We want the user to perform the OIDC authentication for auditing and logging purposes, but we don't need them to choose a local password before we tell them that they are not allowed to log in. --- internal/broker/broker.go | 19 ++++++------------- internal/broker/config.go | 27 ++++++++++++++++++++++++--- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index d50391b6b6..cfa2d15eec 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -539,6 +539,11 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au return AuthDenied, errorMessageForDisplay(err, "could not fetch user info") } + if !b.userNameIsAllowed(authInfo.UserInfo.Name) { + log.Warningf(context.Background(), "User %q is not in the list of allowed users", authInfo.UserInfo.Name) + return AuthDenied, errorMessage{Message: "permission denied"} + } + session.authInfo["auth_info"] = authInfo return AuthNext, nil @@ -656,19 +661,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au // userNameIsAllowed checks whether the user's username is allowed to access the machine. func (b *Broker) userNameIsAllowed(userName string) bool { - normalizedUsername := b.provider.NormalizeUsername(userName) - // The user is allowed to log in if: - // - ALL users are allowed - // - the user's name is in the list of allowed_users - // - the user is the owner of the machine and OWNER is in the allowed_users list - if b.cfg.userConfig.allUsersAllowed { - return true - } - if _, ok := b.cfg.userConfig.allowedUsers[normalizedUsername]; ok { - return true - } - - return b.cfg.isOwnerAllowed(normalizedUsername) + return b.cfg.userNameIsAllowed(b.provider.NormalizeUsername(userName)) } func (b *Broker) startAuthenticate(sessionID string) (context.Context, error) { diff --git a/internal/broker/config.go b/internal/broker/config.go index 1da3d6c162..04cd8f42c5 100644 --- a/internal/broker/config.go +++ b/internal/broker/config.go @@ -187,11 +187,32 @@ func parseConfigFile(cfgPath string, p provider) (userConfig, error) { return cfg, nil } -func (uc *userConfig) isOwnerAllowed(userName string) bool { +func (uc *userConfig) userNameIsAllowed(userName string) bool { uc.ownerMutex.RLock() defer uc.ownerMutex.RUnlock() - return uc.ownerAllowed && uc.owner == userName + // The user is allowed to log in if: + // - ALL users are allowed + // - the user's name is in the list of allowed_users + // - OWNER is in the allowed_users list and the user is the owner of the machine + // - The user will be registered as the owner + if uc.allUsersAllowed { + return true + } + if _, ok := uc.allowedUsers[userName]; ok { + return true + } + if uc.ownerAllowed && uc.owner == userName { + return true + } + + return uc.shouldRegisterOwner() +} + +// shouldRegisterOwner returns true if the first user to log in should be registered as the owner. +// Only call this with the ownerMutex locked. +func (uc *userConfig) shouldRegisterOwner() bool { + return uc.ownerAllowed && uc.firstUserBecomesOwner && uc.owner == "" } func (uc *userConfig) registerOwner(cfgPath, userName string) error { @@ -200,7 +221,7 @@ func (uc *userConfig) registerOwner(cfgPath, userName string) error { uc.ownerMutex.Lock() defer uc.ownerMutex.Unlock() - if shouldRegister := uc.ownerAllowed && uc.firstUserBecomesOwner && uc.owner == ""; !shouldRegister { + if !uc.shouldRegisterOwner() { return nil } From 8a8e5643c4c958af2145e1d3e2ef5672cebd1f82 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 27 Jan 2025 22:41:37 +0100 Subject: [PATCH 0345/1670] Fix order of "expected" and "actual" arguments --- internal/broker/broker_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 03225ea1bd..934fdcec57 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -984,11 +984,11 @@ func TestIsAuthenticatedAllowedUsersConfig(t *testing.T) { require.True(t, json.Valid([]byte(data)), "IsAuthenticated returned data must be a valid JSON") require.NoError(t, err) if slices.Contains(tc.wantAllowedUsers, u) { - require.Equal(t, access, broker.AuthGranted, "authentication failed") + require.Equal(t, broker.AuthGranted, access, "authentication failed") continue } if slices.Contains(tc.wantUnallowedUsers, u) { - require.Equal(t, access, broker.AuthDenied, "authentication failed") + require.Equal(t, broker.AuthDenied, access, "authentication failed") continue } t.Fatalf("user %s is not in the allowed or unallowed users list", u) From 26b6f792feab2d0abb6030a4ae92abd00dd952f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Jan 2025 10:48:48 +0000 Subject: [PATCH 0346/1670] deps(go): bump github.com/ubuntu/authd Bumps [github.com/ubuntu/authd](https://github.com/ubuntu/authd) from 0.3.8-0.20250114154108-c636df971351 to 0.4.1. - [Commits](https://github.com/ubuntu/authd/commits/v0.4.1) --- updated-dependencies: - dependency-name: github.com/ubuntu/authd dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7f9aff0398..8156631062 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.10.0 - github.com/ubuntu/authd v0.3.8-0.20250114154108-c636df971351 + github.com/ubuntu/authd v0.4.1 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 golang.org/x/crypto v0.32.0 diff --git a/go.sum b/go.sum index c63f85b384..28d2ca8f34 100644 --- a/go.sum +++ b/go.sum @@ -121,8 +121,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/ubuntu/authd v0.3.8-0.20250114154108-c636df971351 h1:YRVZSElrXVHuqgqPrjc2DrxZWnwLi0d6Kuhe3wECRAM= -github.com/ubuntu/authd v0.3.8-0.20250114154108-c636df971351/go.mod h1:3UpP+TbrWL9vH9CGOe0nNrGJVD7yAcVFwLLC6UHJA18= +github.com/ubuntu/authd v0.4.1 h1:6gihmB6soI6J14aY8KyM+vamN24y+un5rGdvD3iGQi4= +github.com/ubuntu/authd v0.4.1/go.mod h1:QUhHmd7fuMzHkwGu41k3C/D+ul1+9ZnpUdFtGoOtsU4= github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 h1:qO8m+4mLbo1HRpD5lfhEfr7R1PuqZvbAmjaRzYEy+tM= github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5/go.mod h1:PUpwIgUuCQyuCz/gwiq6WYbo7IvtXXd8JqL01ez+jZE= github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 h1:CA2dVorxvzdsGtszqhSjyvkrXxZi4bS52ZKvP0Ko634= From 9fbe1977306a1b65b6908be5091a3571016dcffc Mon Sep 17 00:00:00 2001 From: Simon Hanna Date: Fri, 31 Jan 2025 13:20:54 +0100 Subject: [PATCH 0347/1670] Fix security group filter in msentraid.go Previously it was modifying the array it was iterating over causing a runtime error Closes: #352 --- internal/providers/msentraid/msentraid.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 9ecfef28cb..2d44e460ef 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -215,6 +215,18 @@ func (p Provider) getGroups(token *oauth2.Token, msgraphHost string) ([]info.Gro return groups, nil } +func removeNonSecurityGroups(groups []msgraphmodels.Groupable) []msgraphmodels.Groupable { + securityGroups := []msgraphmodels.Groupable{} + for _, group := range groups { + if !isSecurityGroup(group) { + log.Debugf(context.Background(), "Removing non-security group %s", *group.GetDisplayName()) + } else { + securityGroups = append(securityGroups, group) + } + } + return securityGroups +} + func getSecurityGroups(client *msgraphsdk.GraphServiceClient) ([]msgraphmodels.Groupable, error) { // Initial request to get groups requestBuilder := client.Me().TransitiveMemberOf().GraphGroup() @@ -243,12 +255,7 @@ func getSecurityGroups(client *msgraphsdk.GraphServiceClient) ([]msgraphmodels.G // Remove the groups which are not security groups (but for example Microsoft 365 groups, which can be created // by non-admin users). - for i, group := range groups { - if !isSecurityGroup(group) { - log.Debugf(context.Background(), "Removing non-security group %s", *group.GetDisplayName()) - groups = append(groups[:i], groups[i+1:]...) - } - } + groups = removeNonSecurityGroups(groups) var groupNames []string for _, group := range groups { From e457bdd3db420715708da9d97ea09ff93863acfc Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 31 Jan 2025 14:26:12 +0100 Subject: [PATCH 0348/1670] Refactor --- internal/providers/msentraid/msentraid.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 2d44e460ef..178c3d1caa 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -216,13 +216,13 @@ func (p Provider) getGroups(token *oauth2.Token, msgraphHost string) ([]info.Gro } func removeNonSecurityGroups(groups []msgraphmodels.Groupable) []msgraphmodels.Groupable { - securityGroups := []msgraphmodels.Groupable{} + var securityGroups []msgraphmodels.Groupable for _, group := range groups { if !isSecurityGroup(group) { log.Debugf(context.Background(), "Removing non-security group %s", *group.GetDisplayName()) - } else { - securityGroups = append(securityGroups, group) + continue } + securityGroups = append(securityGroups, group) } return securityGroups } From 276d4cd22d45d85f1630df43dbedfb18628a79ea Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 3 Feb 2025 11:37:30 +0100 Subject: [PATCH 0349/1670] Fix snap releases still having version 0.2.0-pre1 instead of 0.2.0 `git tag --sort=-v:refname` sorts tagnames with the same base version but different suffixes in lexicographical order, causing prerelease tags to be listed as higher than the main release. This results in the snaps still having `0.2.0-pre1+...` versions instead of `0.2.0+...`. Git supports the `versionsort.suffix` option to avoid exactly that, so let's use it. --- snap/get_version | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snap/get_version b/snap/get_version index b5a095ecdd..b8e3ce6a8a 100755 --- a/snap/get_version +++ b/snap/get_version @@ -60,14 +60,14 @@ strip_branch_tag_prefix() { current_branch=$(git -C "${SNAPCRAFT_PART_SRC}" branch --show-current) # Get the highest version tag which is prefixed with the current branch name. -tag=$(git tag --sort=-v:refname --merged="${current_branch}" | grep "^${current_branch}-" | head -1) +tag=$(git -c "versionsort.suffix=-pre" tag --sort=-v:refname --merged="${current_branch}" | grep "^${current_branch}-" | head -1) # If there is no tag prefixed with the current branch name, use the most # recent tag that does not have a non-numerical prefix (that's the case # when we're building a snap for testing on a branch that's not # "msentraid" or "google"). if [ -z "${tag}" ]; then - tag=$(git tag --sort=-v:refname --merged="${current_branch}" | grep -E '^[0-9]+' | head -1) + tag=$(git -c "versionsort.suffix=-pre" tag --sort=-v:refname --merged="${current_branch}" | grep -E '^[0-9]+' | head -1) fi version="${tag}" From 5d51231b381f613b14e7be80fc87072ef9864cb0 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 17 Jan 2025 15:01:26 +0100 Subject: [PATCH 0350/1670] Support changing the password if refreshing token or user info fails We don't need to refresh the token and user info if the session is for changing the password. --- internal/broker/broker.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index cfa2d15eec..8d21d2d377 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -584,6 +584,13 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au } } + // If the session is for changing the password, we don't need to refresh the token and user info (and we don't + // want the method call to return an error if refreshing the token or user info fails). + if session.mode == sessionmode.ChangePassword || session.mode == sessionmode.ChangePasswordOld { + session.authInfo["auth_info"] = authInfo + return AuthNext, nil + } + // Refresh the token if we're online even if the token has not expired if !session.isOffline { authInfo, err = b.refreshToken(ctx, session.oauth2Config, authInfo) @@ -607,11 +614,6 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au authInfo.UserInfo = userInfo } - if session.mode == sessionmode.ChangePassword || session.mode == sessionmode.ChangePasswordOld { - session.authInfo["auth_info"] = authInfo - return AuthNext, nil - } - case authmodes.NewPassword: if secret == "" { return AuthRetry, errorMessage{Message: "empty secret"} From fc8abea1d460b93c66fcd69a74c52ffab1d64383 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 3 Feb 2025 15:58:38 +0100 Subject: [PATCH 0351/1670] Test authenticating to change password when fetching user info fails Regression test for the previous commit --- internal/broker/broker_test.go | 6 ++++++ .../data/provider_url/test-user@email.com/password | 1 + .../data/provider_url/test-user@email.com/token.json | 1 + .../first_call | 3 +++ 4 files changed, 11 insertions(+) create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_to_change_password_still_allowed_if_fetching_user_info_fails/data/provider_url/test-user@email.com/password create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_to_change_password_still_allowed_if_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_to_change_password_still_allowed_if_fetching_user_info_fails/first_call diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 934fdcec57..9c0275d739 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -455,6 +455,12 @@ func TestIsAuthenticated(t *testing.T) { token: &tokenOptions{}, useOldNameForSecretField: true, }, + "Authenticating_to_change_password_still_allowed_if_fetching_user_info_fails": { + sessionMode: sessionmode.ChangePassword, + firstMode: authmodes.Password, + token: &tokenOptions{noUserInfo: true}, + getUserInfoFails: true, + }, "Error_when_authentication_data_is_invalid": {invalidAuthData: true}, "Error_when_secret_can_not_be_decrypted": {firstMode: authmodes.Password, badFirstKey: true}, diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_to_change_password_still_allowed_if_fetching_user_info_fails/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_to_change_password_still_allowed_if_fetching_user_info_fails/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_to_change_password_still_allowed_if_fetching_user_info_fails/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_to_change_password_still_allowed_if_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_to_change_password_still_allowed_if_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_to_change_password_still_allowed_if_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_to_change_password_still_allowed_if_fetching_user_info_fails/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_to_change_password_still_allowed_if_fetching_user_info_fails/first_call new file mode 100644 index 0000000000..d0887a134f --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_to_change_password_still_allowed_if_fetching_user_info_fails/first_call @@ -0,0 +1,3 @@ +access: next +data: '{}' +err: From 3f086d04ebfcc650319587e9cf913aef7279c306 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 3 Feb 2025 16:07:42 +0100 Subject: [PATCH 0352/1670] Bump Go toolchain version to 1.23.5 govulncheck reports multiple vulnerabilities affecting Go versions before 1.23.5: Vulnerability #1: GO-2025-3420 Sensitive headers incorrectly sent after cross-domain redirect in net/http More info: https://pkg.go.dev/vuln/GO-2025-3420 Standard library Found in: net/http@go1.23 Fixed in: net/http@go1.23.5 Vulnerability #2: GO-2025-3373 Usage of IPv6 zone IDs can bypass URI name constraints in crypto/x509 More info: https://pkg.go.dev/vuln/GO-2025-3373 Standard library Found in: crypto/x509@go1.23 Fixed in: crypto/x509@go1.23.5 --- go.mod | 2 ++ tools/go.mod | 2 ++ 2 files changed, 4 insertions(+) diff --git a/go.mod b/go.mod index 8156631062..651a35e903 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/ubuntu/authd-oidc-brokers go 1.23.0 +toolchain go1.23.5 + require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 github.com/coreos/go-oidc/v3 v3.12.0 diff --git a/tools/go.mod b/tools/go.mod index d30c859085..cc12b4e627 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -2,6 +2,8 @@ module github.com/ubuntu/authd-oidc-brokers/tools go 1.23.0 +toolchain go1.23.5 + require github.com/golangci/golangci-lint v1.63.4 require ( From 9752d47124a3769383afb5e2a7a696783bb5af2f Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 3 Feb 2025 16:42:26 +0100 Subject: [PATCH 0353/1670] Log authentication failures caused by an incorrect local password Unsuccessful authentication attempts are security relevant, so we should make sure to log all cases of those. See also https://owasp.org/Top10/A09_2021-Security_Logging_and_Monitoring_Failures/ --- internal/broker/broker.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index cfa2d15eec..25901663f1 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -574,6 +574,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au return AuthDenied, errorMessage{Message: "could not check password"} } if !ok { + log.Warningf(context.Background(), "Authentication failure: incorrect local password for user %q", session.username) return AuthRetry, errorMessage{Message: "incorrect password"} } From f8343ba416e286c698dc001f944a7ba38beaccdd Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 4 Feb 2025 11:02:47 +0100 Subject: [PATCH 0354/1670] deps(go): bump github.com/ubuntu/authd So that we can use `log.Notice`. --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 8156631062..04850ac787 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.10.0 - github.com/ubuntu/authd v0.4.1 + github.com/ubuntu/authd v0.4.2-0.20250203155723-a499f298f969 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 golang.org/x/crypto v0.32.0 diff --git a/go.sum b/go.sum index 28d2ca8f34..cd1b9c04fa 100644 --- a/go.sum +++ b/go.sum @@ -123,6 +123,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/ubuntu/authd v0.4.1 h1:6gihmB6soI6J14aY8KyM+vamN24y+un5rGdvD3iGQi4= github.com/ubuntu/authd v0.4.1/go.mod h1:QUhHmd7fuMzHkwGu41k3C/D+ul1+9ZnpUdFtGoOtsU4= +github.com/ubuntu/authd v0.4.2-0.20250203155723-a499f298f969 h1:93TjnT/gtCY09U2BY0sCSvCgpBNzjgtTNLOmSzGp7ek= +github.com/ubuntu/authd v0.4.2-0.20250203155723-a499f298f969/go.mod h1:QUhHmd7fuMzHkwGu41k3C/D+ul1+9ZnpUdFtGoOtsU4= github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 h1:qO8m+4mLbo1HRpD5lfhEfr7R1PuqZvbAmjaRzYEy+tM= github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5/go.mod h1:PUpwIgUuCQyuCz/gwiq6WYbo7IvtXXd8JqL01ez+jZE= github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 h1:CA2dVorxvzdsGtszqhSjyvkrXxZi4bS52ZKvP0Ko634= From b803efa7ad08c404a51c09388cf9d7b9eb20568d Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 4 Feb 2025 11:04:18 +0100 Subject: [PATCH 0355/1670] Use log level NOTICE to log incorrect password attempts Similar to how pam_unix logs incorrect password attempts. See also the discussion on UDENG-5921. --- internal/broker/broker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index f8d269ac91..97bc1d9f13 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -574,7 +574,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au return AuthDenied, errorMessage{Message: "could not check password"} } if !ok { - log.Warningf(context.Background(), "Authentication failure: incorrect local password for user %q", session.username) + log.Noticef(context.Background(), "Authentication failure: incorrect local password for user %q", session.username) return AuthRetry, errorMessage{Message: "incorrect password"} } From 8f434e60ab75654731189fbe39506df0dfc7ad8c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2025 08:15:17 +0000 Subject: [PATCH 0356/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.59.0 to 1.60.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.59.0...v1.60.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 5bbb31f01a..ff16ea6021 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.59.0 + github.com/microsoftgraph/msgraph-sdk-go v1.60.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 diff --git a/go.sum b/go.sum index cd1b9c04fa..40a784b7aa 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.0.0 h1:3O5sb5Zj+moLBiJy github.com/microsoft/kiota-serialization-multipart-go v1.0.0/go.mod h1:yauLeBTpANk4L03XD985akNysG24SnRJGaveZf+p4so= github.com/microsoft/kiota-serialization-text-go v1.0.0 h1:XOaRhAXy+g8ZVpcq7x7a0jlETWnWrEum0RhmbYrTFnA= github.com/microsoft/kiota-serialization-text-go v1.0.0/go.mod h1:sM1/C6ecnQ7IquQOGUrUldaO5wj+9+v7G2W3sQ3fy6M= -github.com/microsoftgraph/msgraph-sdk-go v1.59.0 h1:2zyS9XPyWfxUhR3dEd0QcijbKRqM80luVD4vX7S0Ntg= -github.com/microsoftgraph/msgraph-sdk-go v1.59.0/go.mod h1:2dZJTO/7S+UmfwKlPQg2vczpW7awcIEj1ZCgfZIf0V4= +github.com/microsoftgraph/msgraph-sdk-go v1.60.0 h1:l7XCJT03tiSWcIaodieAjH88ICJJUtMwUF55Wk0osxs= +github.com/microsoftgraph/msgraph-sdk-go v1.60.0/go.mod h1:2dZJTO/7S+UmfwKlPQg2vczpW7awcIEj1ZCgfZIf0V4= github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 h1:P1wpmn3xxfPMFJHg+PJPcusErfRkl63h6OdAnpDbkS8= github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1/go.mod h1:vFmWQGWyLlhxCESNLv61vlE4qesBU+eWmEVH7DJSESA= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -121,8 +121,6 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/ubuntu/authd v0.4.1 h1:6gihmB6soI6J14aY8KyM+vamN24y+un5rGdvD3iGQi4= -github.com/ubuntu/authd v0.4.1/go.mod h1:QUhHmd7fuMzHkwGu41k3C/D+ul1+9ZnpUdFtGoOtsU4= github.com/ubuntu/authd v0.4.2-0.20250203155723-a499f298f969 h1:93TjnT/gtCY09U2BY0sCSvCgpBNzjgtTNLOmSzGp7ek= github.com/ubuntu/authd v0.4.2-0.20250203155723-a499f298f969/go.mod h1:QUhHmd7fuMzHkwGu41k3C/D+ul1+9ZnpUdFtGoOtsU4= github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 h1:qO8m+4mLbo1HRpD5lfhEfr7R1PuqZvbAmjaRzYEy+tM= From 90c29cd5fad4e869f8ac964af7785c93b2f28462 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2025 08:15:27 +0000 Subject: [PATCH 0357/1670] deps(go): bump golang.org/x/oauth2 from 0.25.0 to 0.26.0 Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.25.0 to 0.26.0. - [Commits](https://github.com/golang/oauth2/compare/v0.25.0...v0.26.0) --- updated-dependencies: - dependency-name: golang.org/x/oauth2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 5bbb31f01a..f0e2c81c3d 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 golang.org/x/crypto v0.32.0 - golang.org/x/oauth2 v0.25.0 + golang.org/x/oauth2 v0.26.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index cd1b9c04fa..1c5b45f5fc 100644 --- a/go.sum +++ b/go.sum @@ -121,8 +121,6 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/ubuntu/authd v0.4.1 h1:6gihmB6soI6J14aY8KyM+vamN24y+un5rGdvD3iGQi4= -github.com/ubuntu/authd v0.4.1/go.mod h1:QUhHmd7fuMzHkwGu41k3C/D+ul1+9ZnpUdFtGoOtsU4= github.com/ubuntu/authd v0.4.2-0.20250203155723-a499f298f969 h1:93TjnT/gtCY09U2BY0sCSvCgpBNzjgtTNLOmSzGp7ek= github.com/ubuntu/authd v0.4.2-0.20250203155723-a499f298f969/go.mod h1:QUhHmd7fuMzHkwGu41k3C/D+ul1+9ZnpUdFtGoOtsU4= github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 h1:qO8m+4mLbo1HRpD5lfhEfr7R1PuqZvbAmjaRzYEy+tM= @@ -150,8 +148,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= -golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= +golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= From 2d50d096c4008dc12d13b1906260c1906293f48d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2025 12:32:36 +0000 Subject: [PATCH 0358/1670] deps(go): bump golang.org/x/crypto from 0.32.0 to 0.33.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.32.0 to 0.33.0. - [Commits](https://github.com/golang/crypto/compare/v0.32.0...v0.33.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index cd390868d2..4f99cfd1c2 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/ubuntu/authd v0.4.2-0.20250203155723-a499f298f969 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 - golang.org/x/crypto v0.32.0 + golang.org/x/crypto v0.33.0 golang.org/x/oauth2 v0.26.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 @@ -69,7 +69,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/net v0.34.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect ) diff --git a/go.sum b/go.sum index 8f3d008152..e077018970 100644 --- a/go.sum +++ b/go.sum @@ -138,8 +138,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -152,8 +152,8 @@ golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -162,16 +162,16 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From 8eb64e7226f43714febbe5a319c3457477bf5d0a Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 10 Feb 2025 12:52:08 +0100 Subject: [PATCH 0359/1670] Bump Go toolchain version to 1.23.6 govulncheck reports the following vulnerability in nistec@go1.23.5: Vulnerability #1: GO-2025-3447 Timing sidechannel for P-256 on ppc64le in crypto/internal/nistec More info: https://pkg.go.dev/vuln/GO-2025-3447 Standard library Found in: crypto/internal/nistec@go1.23.5 Fixed in: crypto/internal/nistec@go1.23.6 Platforms: ppc64le --- go.mod | 2 +- tools/go.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 5bbb31f01a..6f7c86152f 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/ubuntu/authd-oidc-brokers go 1.23.0 -toolchain go1.23.5 +toolchain go1.23.6 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 diff --git a/tools/go.mod b/tools/go.mod index cc12b4e627..0900f1c75e 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -2,7 +2,7 @@ module github.com/ubuntu/authd-oidc-brokers/tools go 1.23.0 -toolchain go1.23.5 +toolchain go1.23.6 require github.com/golangci/golangci-lint v1.63.4 From fdc290974b77c39f36b12efde896b7a2ce0f5ae8 Mon Sep 17 00:00:00 2001 From: Wesley Hershberger Date: Tue, 11 Feb 2025 12:40:15 -0600 Subject: [PATCH 0360/1670] internal/broker: Warn when switching to offline mode If a user gives an invalid issuer URL in broker.conf, go-oidc will return an error with the HTTP response code. This should be logged by default so that users aren't left guessing as to the online/offline state of the broker. Signed-off-by: Wesley Hershberger Reviewed-by: Adrian Dombeck --- internal/broker/broker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 97bc1d9f13..549f9759f9 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -183,7 +183,7 @@ func (b *Broker) NewSession(username, lang, mode string) (sessionID, encryptionK // Construct an OIDC provider via OIDC discovery. s.oidcServer, err = b.connectToOIDCServer(context.Background()) if err != nil { - log.Debugf(context.Background(), "Could not connect to the provider: %v. Starting session in offline mode.", err) + log.Noticef(context.Background(), "Could not connect to the provider: %v. Starting session in offline mode.", err) s.isOffline = true } From 724ee327102ea3ac6c3f39cd918813311b22f0da Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 12 Feb 2025 15:48:31 +0100 Subject: [PATCH 0361/1670] Explain why we use the lowercase of the group name --- internal/providers/msentraid/msentraid.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 178c3d1caa..c9d3135e2d 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -199,6 +199,7 @@ func (p Provider) getGroups(token *oauth2.Token, msgraphHost string) ([]info.Gro log.Warning(context.Background(), pp.Sprintf("Could not get display name for group object (ID: %s): %v", id, msGroup)) return nil, errors.New("could not get group name") } + // Microsoft groups are case-insensitive, see https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules groupName := strings.ToLower(*groupNamePtr) // Check if the group is a local group, in which case we don't set the UGID (because that's how the user manager From ae9d11ec54d3ad668285581bbfb2fd4210d6a065 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 12 Feb 2025 15:49:05 +0100 Subject: [PATCH 0362/1670] Ignore duplicate groups returned by the Microsoft Graph API Closes #789 --- internal/providers/msentraid/msentraid.go | 59 +++++++++++++++++++---- 1 file changed, 49 insertions(+), 10 deletions(-) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index c9d3135e2d..a1d7e83dd8 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -186,7 +186,10 @@ func (p Provider) getGroups(token *oauth2.Token, msgraphHost string) ([]info.Gro } var groups []info.Group + var msGroupNames []string for _, msGroup := range graphGroups { + var group info.Group + idPtr := msGroup.GetId() if idPtr == nil { log.Warning(context.Background(), pp.Sprintf("Could not get ID for group: %v", msGroup)) @@ -194,28 +197,64 @@ func (p Provider) getGroups(token *oauth2.Token, msgraphHost string) ([]info.Gro } id := *idPtr - groupNamePtr := msGroup.GetDisplayName() - if groupNamePtr == nil { + msGroupNamePtr := msGroup.GetDisplayName() + if msGroupNamePtr == nil { log.Warning(context.Background(), pp.Sprintf("Could not get display name for group object (ID: %s): %v", id, msGroup)) return nil, errors.New("could not get group name") } - // Microsoft groups are case-insensitive, see https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules - groupName := strings.ToLower(*groupNamePtr) + msGroupName := *msGroupNamePtr - // Check if the group is a local group, in which case we don't set the UGID (because that's how the user manager - // differentiates between local and remote groups). - if strings.HasPrefix(groupName, localGroupPrefix) { - groupName = strings.TrimPrefix(groupName, localGroupPrefix) - groups = append(groups, info.Group{Name: groupName}) + // Check if there is a name conflict with another group returned by the Graph API. It's not clear in which case + // the Graph API returns multiple groups with the same name (or the same group twice), but we've seen it happen + // in https://github.com/ubuntu/authd/issues/789. + if checkGroupIsDuplicate(msGroupName, msGroupNames) { continue } - groups = append(groups, info.Group{Name: groupName, UGID: id}) + // Microsoft groups are case-insensitive, see https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules + group.Name = strings.ToLower(msGroupName) + + isLocalGroup := strings.HasPrefix(group.Name, localGroupPrefix) + if isLocalGroup { + group.Name = strings.TrimPrefix(group.Name, localGroupPrefix) + } + + // Don't set the UGID for local groups, because that's how the user manager differentiates between local and + // remote groups. + if !isLocalGroup { + group.UGID = id + } + + groups = append(groups, group) + msGroupNames = append(msGroupNames, msGroupName) } return groups, nil } +func checkGroupIsDuplicate(groupName string, groupNames []string) bool { + for _, name := range groupNames { + // We don't want to treat local groups without the prefix as duplicates of non-local groups + // (e.g. "linux-sudo" and "sudo"), so we compare the names as returned by the Graph API - except that we + // ignore the case, because we use the group names in lowercase. + if strings.EqualFold(name, groupName) { + // Not a duplicate + continue + } + + // To make debugging easier, check if the groups differ in case, and mention that in the log message. + if name == groupName { + log.Warningf(context.Background(), "The Microsoft Graph API returned the group %q multiple times, ignoring the duplicate", name) + } else { + log.Warningf(context.Background(), "The Microsoft Graph API returned the group %[1]q multiple times, but with different case (%[2]q and %[1]q), ignoring the duplicate", groupName, name) + } + + return true + } + + return false +} + func removeNonSecurityGroups(groups []msgraphmodels.Groupable) []msgraphmodels.Groupable { var securityGroups []msgraphmodels.Groupable for _, group := range groups { From 50a8202a546de2e7ce3fb4fca6b717c3a07bc0b1 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 17 Feb 2025 11:49:45 +0100 Subject: [PATCH 0363/1670] Fix invalid golangci-lint config The nolintlint settings must be part of the "linters-settings" list. Fixes that golangci-lint fails in the CI with: Failed to run: Error: Command failed: /home/runner/golangci-lint-1.63.4-linux-amd64/golangci-lint config verify jsonschema: "" does not validate with "/additionalProperties": additional properties 'nolintlint' not allowed Failed executing command with error: the configuration contains invalid elements --- .golangci.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index c1d1a934f3..8179253e62 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -47,10 +47,6 @@ issues: # Sometimes it is more readable it do a `if err:=a(); err != nil` tha simpy `return a()` - if-return -nolintlint: - require-explanation: true - require-specific: true - linters-settings: # Forbid the usage of deprecated ioutil and debug prints forbidigo: @@ -60,3 +56,6 @@ linters-settings: # Never have naked return ever nakedret: max-func-lines: 1 + nolintlint: + require-explanation: true + require-specific: true From c3df62cafc63b81add8b020c9ccd51bf6f2ff6ca Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 17 Feb 2025 11:52:20 +0100 Subject: [PATCH 0364/1670] .golangci.yaml: Add explanatory comments Copied from https://github.com/golangci/golangci-lint/blob/2c1d661a335214d4c4df257396727cb8381abfd4/.golangci.reference.yml#L2226-L2231 --- .golangci.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.golangci.yaml b/.golangci.yaml index 8179253e62..4fa7331493 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -57,5 +57,7 @@ linters-settings: nakedret: max-func-lines: 1 nolintlint: + # Require an explanation of nonzero length after each nolint directive. require-explanation: true + # Require nolint directives to mention the specific linter being suppressed. require-specific: true From 1a3a3df7b1e53adf7168c64038698d2531c32978 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 11:51:15 +0000 Subject: [PATCH 0365/1670] deps(ci): bump canonical/has-signed-canonical-cla from 1 to 2 Bumps [canonical/has-signed-canonical-cla](https://github.com/canonical/has-signed-canonical-cla) from 1 to 2. - [Release notes](https://github.com/canonical/has-signed-canonical-cla/releases) - [Commits](https://github.com/canonical/has-signed-canonical-cla/compare/v1...v2) --- updated-dependencies: - dependency-name: canonical/has-signed-canonical-cla dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/cla-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cla-check.yml b/.github/workflows/cla-check.yml index 32aca7f840..c9fc001af5 100644 --- a/.github/workflows/cla-check.yml +++ b/.github/workflows/cla-check.yml @@ -7,6 +7,6 @@ jobs: runs-on: ubuntu-latest steps: - name: Check if CLA signed - uses: canonical/has-signed-canonical-cla@v1 + uses: canonical/has-signed-canonical-cla@v2 with: accept-existing-contributors: true From 26c1a1299bc1acc30f4cff2775e703b29da21cf3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 11:51:30 +0000 Subject: [PATCH 0366/1670] deps(go): bump github.com/spf13/cobra from 1.8.1 to 1.9.1 Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.8.1 to 1.9.1. - [Release notes](https://github.com/spf13/cobra/releases) - [Commits](https://github.com/spf13/cobra/compare/v1.8.1...v1.9.1) --- updated-dependencies: - dependency-name: github.com/spf13/cobra dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 6b5bcdadba..f237dfbbc9 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 - github.com/spf13/cobra v1.8.1 + github.com/spf13/cobra v1.9.1 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.10.0 github.com/ubuntu/authd v0.4.2-0.20250203155723-a499f298f969 @@ -60,7 +60,7 @@ require ( github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.1 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect diff --git a/go.sum b/go.sum index e077018970..87e59a1e34 100644 --- a/go.sum +++ b/go.sum @@ -10,7 +10,7 @@ github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pq github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -100,10 +100,10 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.1 h1:/m2cTZHpqgofDsrwPqsASI6fSNMNhb+9EmUYtHEV2Uk= From 594d135e18e60988185dd1d0e3bc9bac1aa4c68a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 11:51:34 +0000 Subject: [PATCH 0367/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.60.0 to 1.61.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.60.0...v1.61.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6b5bcdadba..319a850c2c 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.60.0 + github.com/microsoftgraph/msgraph-sdk-go v1.61.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 diff --git a/go.sum b/go.sum index e077018970..71ba366a6e 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.0.0 h1:3O5sb5Zj+moLBiJy github.com/microsoft/kiota-serialization-multipart-go v1.0.0/go.mod h1:yauLeBTpANk4L03XD985akNysG24SnRJGaveZf+p4so= github.com/microsoft/kiota-serialization-text-go v1.0.0 h1:XOaRhAXy+g8ZVpcq7x7a0jlETWnWrEum0RhmbYrTFnA= github.com/microsoft/kiota-serialization-text-go v1.0.0/go.mod h1:sM1/C6ecnQ7IquQOGUrUldaO5wj+9+v7G2W3sQ3fy6M= -github.com/microsoftgraph/msgraph-sdk-go v1.60.0 h1:l7XCJT03tiSWcIaodieAjH88ICJJUtMwUF55Wk0osxs= -github.com/microsoftgraph/msgraph-sdk-go v1.60.0/go.mod h1:2dZJTO/7S+UmfwKlPQg2vczpW7awcIEj1ZCgfZIf0V4= +github.com/microsoftgraph/msgraph-sdk-go v1.61.0 h1:/dqZXI7VAHTAeddhEfEPLf1EXszaDAkLcC9dkOARPxI= +github.com/microsoftgraph/msgraph-sdk-go v1.61.0/go.mod h1:2dZJTO/7S+UmfwKlPQg2vczpW7awcIEj1ZCgfZIf0V4= github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 h1:P1wpmn3xxfPMFJHg+PJPcusErfRkl63h6OdAnpDbkS8= github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1/go.mod h1:vFmWQGWyLlhxCESNLv61vlE4qesBU+eWmEVH7DJSESA= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= From 6207309c332bef266ac4a7bc28ae5920f3876d7b Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 17 Jan 2025 13:51:53 +0100 Subject: [PATCH 0368/1670] Remove unused field The firstSelectedMode was set but never used. --- internal/broker/broker.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 97bc1d9f13..8f874fcd34 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -64,10 +64,9 @@ type session struct { lang string mode string - selectedMode string - firstSelectedMode string - authModes []string - attemptsPerMode map[string]int + selectedMode string + authModes []string + attemptsPerMode map[string]int oidcServer *oidc.Provider oauth2Config oauth2.Config @@ -322,10 +321,6 @@ func (b *Broker) SelectAuthenticationMode(sessionID, authModeID string) (uiLayou // Store selected mode session.selectedMode = authModeID - // Store the first one to use to update the lastSelectedMode in MFA cases. - if session.currentAuthStep == 0 { - session.firstSelectedMode = authModeID - } if err = b.updateSession(sessionID, session); err != nil { return nil, err From b86e6b333a53a35f95542fd584c4b55200ce99f0 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 17 Jan 2025 14:02:45 +0100 Subject: [PATCH 0369/1670] refactor: Replace CurrentAuthenticationModesOffered with SupportedOIDCAuthModes All the decisions related to the local password do not belong in provider-specific code, so they were moved to the provider-agnostic brokers package. The provider implementation now only defines which OIDC authentication modes it supports. --- internal/broker/broker.go | 44 ++++++++++++++--- internal/providers/msentraid/msentraid.go | 53 ++------------------- internal/providers/noprovider/noprovider.go | 50 ++----------------- internal/providers/providers.go | 9 +--- 4 files changed, 48 insertions(+), 108 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 8f874fcd34..2fd21be000 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -246,17 +246,17 @@ func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []m } } - availableModes, err := b.provider.CurrentAuthenticationModesOffered( - session.mode, - supportedAuthModes, - tokenExists, - !session.isOffline, - endpoints, - session.currentAuthStep) + availableModes, err := b.availableAuthModes(session, tokenExists, endpoints) if err != nil { return nil, err } + for _, mode := range availableModes { + if _, ok := supportedAuthModes[mode]; !ok { + return nil, fmt.Errorf("auth mode %q required by the provider, but is not supported locally", mode) + } + } + for _, id := range availableModes { authModes = append(authModes, map[string]string{ "id": id, @@ -276,6 +276,36 @@ func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []m return authModes, nil } +func (b *Broker) availableAuthModes(session session, tokenExists bool, endpoints map[string]struct{}) (availableModes []string, err error) { + switch session.mode { + case sessionmode.ChangePassword, sessionmode.ChangePasswordOld: + // session is for changing the password + if !tokenExists { + return nil, errors.New("user has no cached token") + } + availableModes = []string{authmodes.Password} + if session.currentAuthStep > 0 { + availableModes = []string{authmodes.NewPassword} + } + + default: // session is for login + if !session.isOffline { + for _, mode := range b.provider.SupportedOIDCAuthModes() { + if _, ok := endpoints[mode]; ok { + availableModes = append(availableModes, mode) + } + } + } + if tokenExists { + availableModes = append([]string{authmodes.Password}, availableModes...) + } + if session.currentAuthStep > 0 { + availableModes = []string{authmodes.NewPassword} + } + } + return availableModes, nil +} + func (b *Broker) supportedAuthModesFromLayout(supportedUILayouts []map[string]string) (supportedModes map[string]string) { supportedModes = make(map[string]string) for _, layout := range supportedUILayouts { diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 178c3d1caa..891fbd3cdc 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -17,7 +17,6 @@ import ( msgraphauth "github.com/microsoftgraph/msgraph-sdk-go-core/authentication" msgraphmodels "github.com/microsoftgraph/msgraph-sdk-go/models" "github.com/ubuntu/authd-oidc-brokers/internal/broker/authmodes" - "github.com/ubuntu/authd-oidc-brokers/internal/broker/sessionmode" "github.com/ubuntu/authd-oidc-brokers/internal/consts" providerErrors "github.com/ubuntu/authd-oidc-brokers/internal/providers/errors" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" @@ -280,53 +279,6 @@ func isSecurityGroup(group msgraphmodels.Groupable) bool { return !slices.Contains(group.GetGroupTypes(), "Unified") } -// CurrentAuthenticationModesOffered returns the generic authentication modes supported by the provider. -// -// Token validity is not considered, only the presence of a token. -func (p Provider) CurrentAuthenticationModesOffered( - sessionMode string, - supportedAuthModes map[string]string, - tokenExists bool, - providerReachable bool, - endpoints map[string]struct{}, - currentAuthStep int, -) ([]string, error) { - log.Debugf(context.Background(), "In CurrentAuthenticationModesOffered: sessionMode=%q, supportedAuthModes=%q, tokenExists=%t, providerReachable=%t, endpoints=%q, currentAuthStep=%d\n", sessionMode, supportedAuthModes, tokenExists, providerReachable, endpoints, currentAuthStep) - var offeredModes []string - switch sessionMode { - case sessionmode.ChangePassword, sessionmode.ChangePasswordOld: - if !tokenExists { - return nil, errors.New("user has no cached token") - } - offeredModes = []string{authmodes.Password} - if currentAuthStep > 0 { - offeredModes = []string{authmodes.NewPassword} - } - - default: // auth mode - if _, ok := endpoints[authmodes.DeviceQr]; ok && providerReachable { - offeredModes = []string{authmodes.DeviceQr} - } else if _, ok := endpoints[authmodes.Device]; ok && providerReachable { - offeredModes = []string{authmodes.Device} - } - if tokenExists { - offeredModes = append([]string{authmodes.Password}, offeredModes...) - } - if currentAuthStep > 0 { - offeredModes = []string{authmodes.NewPassword} - } - } - log.Debugf(context.Background(), "Offered modes: %q", offeredModes) - - for _, mode := range offeredModes { - if _, ok := supportedAuthModes[mode]; !ok { - return nil, fmt.Errorf("auth mode %q required by the provider, but is not supported locally", mode) - } - } - - return offeredModes, nil -} - // NormalizeUsername parses a username into a normalized version. func (p Provider) NormalizeUsername(username string) string { // Microsoft Entra usernames are case-insensitive. We can safely use strings.ToLower here without worrying about @@ -335,6 +287,11 @@ func (p Provider) NormalizeUsername(username string) string { return strings.ToLower(username) } +// SupportedOIDCAuthModes returns the OIDC authentication modes supported by the provider. +func (p Provider) SupportedOIDCAuthModes() []string { + return []string{authmodes.Device, authmodes.DeviceQr} +} + // VerifyUsername checks if the authenticated username matches the requested username and that both are valid. func (p Provider) VerifyUsername(requestedUsername, authenticatedUsername string) error { if p.NormalizeUsername(requestedUsername) != p.NormalizeUsername(authenticatedUsername) { diff --git a/internal/providers/noprovider/noprovider.go b/internal/providers/noprovider/noprovider.go index ac3bb429a6..da99619584 100644 --- a/internal/providers/noprovider/noprovider.go +++ b/internal/providers/noprovider/noprovider.go @@ -3,12 +3,10 @@ package noprovider import ( "context" - "errors" "fmt" "github.com/coreos/go-oidc/v3/oidc" "github.com/ubuntu/authd-oidc-brokers/internal/broker/authmodes" - "github.com/ubuntu/authd-oidc-brokers/internal/broker/sessionmode" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "golang.org/x/oauth2" ) @@ -37,49 +35,6 @@ func (p NoProvider) AuthOptions() []oauth2.AuthCodeOption { return []oauth2.AuthCodeOption{} } -// CurrentAuthenticationModesOffered returns the generic authentication modes supported by the provider. -func (p NoProvider) CurrentAuthenticationModesOffered( - sessionMode string, - supportedAuthModes map[string]string, - tokenExists bool, - providerReachable bool, - endpoints map[string]struct{}, - currentAuthStep int, -) ([]string, error) { - var offeredModes []string - switch sessionMode { - case sessionmode.ChangePassword, sessionmode.ChangePasswordOld: - if !tokenExists { - return nil, errors.New("user has no cached token") - } - offeredModes = []string{authmodes.Password} - if currentAuthStep > 0 { - offeredModes = []string{authmodes.NewPassword} - } - - default: // auth mode - if _, ok := endpoints[authmodes.DeviceQr]; ok && providerReachable { - offeredModes = []string{authmodes.DeviceQr} - } else if _, ok := endpoints[authmodes.Device]; ok && providerReachable { - offeredModes = []string{authmodes.Device} - } - if tokenExists { - offeredModes = append([]string{authmodes.Password}, offeredModes...) - } - if currentAuthStep > 0 { - offeredModes = []string{authmodes.NewPassword} - } - } - - for _, mode := range offeredModes { - if _, ok := supportedAuthModes[mode]; !ok { - return nil, fmt.Errorf("auth mode %q required by the provider, but is not supported locally", mode) - } - } - - return offeredModes, nil -} - // GetExtraFields returns the extra fields of the token which should be stored persistently. func (p NoProvider) GetExtraFields(token *oauth2.Token) map[string]interface{} { return nil @@ -125,6 +80,11 @@ func (p NoProvider) VerifyUsername(requestedUsername, username string) error { return nil } +// SupportedOIDCAuthModes returns the OIDC authentication modes supported by the provider. +func (p NoProvider) SupportedOIDCAuthModes() []string { + return []string{authmodes.Device, authmodes.DeviceQr} +} + type claims struct { Email string `json:"email"` Sub string `json:"sub"` diff --git a/internal/providers/providers.go b/internal/providers/providers.go index ccc8cfa9b5..e292d90f0b 100644 --- a/internal/providers/providers.go +++ b/internal/providers/providers.go @@ -14,17 +14,10 @@ type Provider interface { AdditionalScopes() []string AuthOptions() []oauth2.AuthCodeOption CheckTokenScopes(token *oauth2.Token) error - CurrentAuthenticationModesOffered( - sessionMode string, - supportedAuthModes map[string]string, - tokenExists bool, - providerReachable bool, - endpoints map[string]struct{}, - currentAuthStep int, - ) ([]string, error) GetExtraFields(token *oauth2.Token) map[string]interface{} GetMetadata(provider *oidc.Provider) (map[string]interface{}, error) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken, providerMetadata map[string]interface{}) (info.User, error) NormalizeUsername(username string) string + SupportedOIDCAuthModes() []string VerifyUsername(requestedUsername, authenticatedUsername string) error } From d59598267e3c20be69e63072aa3c62927ae91661 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 18 Feb 2025 11:52:42 +0100 Subject: [PATCH 0370/1670] Remove unnecessary debug output --- internal/broker/broker.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 2fd21be000..7d04668d06 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -218,9 +218,6 @@ func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []m supportedAuthModes := b.supportedAuthModesFromLayout(supportedUILayouts) - log.Debugf(context.Background(), "Supported UI Layouts for session %s: %#v", sessionID, supportedUILayouts) - log.Debugf(context.Background(), "Supported Authentication modes for session %s: %#v", sessionID, supportedAuthModes) - // Checks if the token exists in the cache. tokenExists, err := fileutils.FileExists(session.tokenPath) if err != nil { From 353b666c9b3709e14b0f30ed0148f14372dbacf7 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 18 Feb 2025 11:56:30 +0100 Subject: [PATCH 0371/1670] refactor: Clean separation of auth modes supported by provider and UI We used the auth modes supported by the UI to define the list of auth modes supported by provider endpoints, which was confusing. Also, print a log message when the UI doesn't support an authentication mode which is supported by the provider, instead of returning an error, because that's not an error if there are other modes available. --- internal/broker/broker.go | 68 ++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 7d04668d06..ef5669d7a0 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -210,15 +210,13 @@ func (b *Broker) connectToOIDCServer(ctx context.Context) (*oidc.Provider, error } // GetAuthenticationModes returns the authentication modes available for the user. -func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []map[string]string) (authModes []map[string]string, err error) { +func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []map[string]string) (authModesWithLabels []map[string]string, err error) { session, err := b.getSession(sessionID) if err != nil { return nil, err } - supportedAuthModes := b.supportedAuthModesFromLayout(supportedUILayouts) - - // Checks if the token exists in the cache. + // Check if the token exists in the cache. tokenExists, err := fileutils.FileExists(session.tokenPath) if err != nil { log.Warningf(context.Background(), "Could not check if token exists: %v", err) @@ -231,37 +229,26 @@ func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []m } } - endpoints := make(map[string]struct{}) - if session.oidcServer != nil && session.oidcServer.Endpoint().DeviceAuthURL != "" { - authMode := authmodes.DeviceQr - if _, ok := supportedAuthModes[authMode]; ok { - endpoints[authMode] = struct{}{} - } - authMode = authmodes.Device - if _, ok := supportedAuthModes[authMode]; ok { - endpoints[authMode] = struct{}{} - } - } - - availableModes, err := b.availableAuthModes(session, tokenExists, endpoints) + availableModes, err := b.availableAuthModes(session, tokenExists) if err != nil { return nil, err } + modesSupportedByUI := b.authModesSupportedByUI(supportedUILayouts) + for _, mode := range availableModes { - if _, ok := supportedAuthModes[mode]; !ok { - return nil, fmt.Errorf("auth mode %q required by the provider, but is not supported locally", mode) + if _, ok := modesSupportedByUI[mode]; !ok { + log.Infof(context.Background(), "Authentication mode %q is not supported by the UI", mode) + continue } - } - for _, id := range availableModes { - authModes = append(authModes, map[string]string{ - "id": id, - "label": supportedAuthModes[id], + authModesWithLabels = append(authModesWithLabels, map[string]string{ + "id": mode, + "label": modesSupportedByUI[mode], }) } - if len(authModes) == 0 { + if len(authModesWithLabels) == 0 { return nil, fmt.Errorf("no authentication modes available for user %q", session.username) } @@ -270,10 +257,10 @@ func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []m return nil, err } - return authModes, nil + return authModesWithLabels, nil } -func (b *Broker) availableAuthModes(session session, tokenExists bool, endpoints map[string]struct{}) (availableModes []string, err error) { +func (b *Broker) availableAuthModes(session session, tokenExists bool) (availableModes []string, err error) { switch session.mode { case sessionmode.ChangePassword, sessionmode.ChangePasswordOld: // session is for changing the password @@ -287,11 +274,7 @@ func (b *Broker) availableAuthModes(session session, tokenExists bool, endpoints default: // session is for login if !session.isOffline { - for _, mode := range b.provider.SupportedOIDCAuthModes() { - if _, ok := endpoints[mode]; ok { - availableModes = append(availableModes, mode) - } - } + availableModes = b.oidcAuthModes(session) } if tokenExists { availableModes = append([]string{authmodes.Password}, availableModes...) @@ -303,7 +286,26 @@ func (b *Broker) availableAuthModes(session session, tokenExists bool, endpoints return availableModes, nil } -func (b *Broker) supportedAuthModesFromLayout(supportedUILayouts []map[string]string) (supportedModes map[string]string) { +func (b *Broker) oidcAuthModes(session session) []string { + var modes []string + endpoints := make(map[string]struct{}) + if session.oidcServer != nil && session.oidcServer.Endpoint().DeviceAuthURL != "" { + endpoints[authmodes.DeviceQr] = struct{}{} + endpoints[authmodes.Device] = struct{}{} + } + + for _, mode := range b.provider.SupportedOIDCAuthModes() { + if _, ok := endpoints[mode]; ok { + modes = append(modes, mode) + } else { + log.Warningf(context.Background(), "No provider endpoint for mode %q", mode) + } + } + + return modes +} + +func (b *Broker) authModesSupportedByUI(supportedUILayouts []map[string]string) (supportedModes map[string]string) { supportedModes = make(map[string]string) for _, layout := range supportedUILayouts { supportedEntries := strings.Split(strings.TrimPrefix(layout["entry"], "optional:"), ",") From 043fd079d8d3a0ed09a2b9144f68ec2d783fb13a Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 17 Jan 2025 14:39:13 +0100 Subject: [PATCH 0372/1670] refactor: Replace field currentAuthStep with nextAuthMode In preparation for supporting device authentication as the next auth mode. --- internal/broker/broker.go | 18 +++++++----------- internal/broker/broker_test.go | 20 ++++++++++---------- internal/broker/export_test.go | 6 +++--- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index ef5669d7a0..aba272ee18 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -67,6 +67,7 @@ type session struct { selectedMode string authModes []string attemptsPerMode map[string]int + nextAuthMode string oidcServer *oidc.Provider oauth2Config oauth2.Config @@ -77,8 +78,6 @@ type session struct { tokenPath string oldEncryptedTokenPath string - currentAuthStep int - isAuthenticating *isAuthenticatedCtx } @@ -261,6 +260,10 @@ func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []m } func (b *Broker) availableAuthModes(session session, tokenExists bool) (availableModes []string, err error) { + if session.nextAuthMode != "" { + return []string{session.nextAuthMode}, nil + } + switch session.mode { case sessionmode.ChangePassword, sessionmode.ChangePasswordOld: // session is for changing the password @@ -268,9 +271,6 @@ func (b *Broker) availableAuthModes(session session, tokenExists bool) (availabl return nil, errors.New("user has no cached token") } availableModes = []string{authmodes.Password} - if session.currentAuthStep > 0 { - availableModes = []string{authmodes.NewPassword} - } default: // session is for login if !session.isOffline { @@ -279,9 +279,6 @@ func (b *Broker) availableAuthModes(session session, tokenExists bool) (availabl if tokenExists { availableModes = append([]string{authmodes.Password}, availableModes...) } - if session.currentAuthStep > 0 { - availableModes = []string{authmodes.NewPassword} - } } return availableModes, nil } @@ -483,9 +480,6 @@ func (b *Broker) IsAuthenticated(sessionID, authenticationData string) (string, access = AuthDenied iadResponse = errorMessage{Message: "maximum number of attempts reached"} } - - case AuthNext: - session.currentAuthStep++ } if err = b.updateSession(sessionID, session); err != nil { @@ -569,6 +563,8 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au } session.authInfo["auth_info"] = authInfo + session.nextAuthMode = authmodes.NewPassword + return AuthNext, nil case authmodes.Password: diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 9c0275d739..d2cbf0aefe 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -165,7 +165,7 @@ func TestGetAuthenticationModes(t *testing.T) { providerAddress string tokenExists bool - secondAuthStep bool + nextAuthMode string unavailableProvider bool deviceAuthUnsupported bool @@ -173,7 +173,7 @@ func TestGetAuthenticationModes(t *testing.T) { }{ // Authentication session "Get_device_auth_qr_if_there_is_no_token": {}, - "Get_newpassword_if_already_authenticated_with_device_auth_qr": {secondAuthStep: true}, + "Get_newpassword_if_already_authenticated_with_device_auth_qr": {nextAuthMode: authmodes.NewPassword}, "Get_password_and_device_auth_qr_if_token_exists": {tokenExists: true}, "Get_only_password_if_token_exists_and_provider_is_not_available": {tokenExists: true, providerAddress: "127.0.0.1:31310", unavailableProvider: true}, @@ -181,7 +181,7 @@ func TestGetAuthenticationModes(t *testing.T) { // Change password session "Get_only_password_if_token_exists_and_session_is_for_changing_password": {sessionMode: sessionmode.ChangePassword, tokenExists: true}, - "Get_newpassword_if_already_authenticated_with_password_and_session_is_for_changing_password": {sessionMode: sessionmode.ChangePassword, tokenExists: true, secondAuthStep: true}, + "Get_newpassword_if_already_authenticated_with_password_and_session_is_for_changing_password": {sessionMode: sessionmode.ChangePassword, tokenExists: true, nextAuthMode: authmodes.NewPassword}, "Get_only_password_if_token_exists_and_session_mode_is_the_old_passwd_value": {sessionMode: sessionmode.ChangePasswordOld, tokenExists: true}, "Error_if_there_is_no_session": {sessionID: "-", wantErr: true}, @@ -235,8 +235,8 @@ func TestGetAuthenticationModes(t *testing.T) { err = os.WriteFile(b.TokenPathForSession(sessionID), []byte("some token"), 0600) require.NoError(t, err, "Setup: WriteFile should not have returned an error") } - if tc.secondAuthStep { - b.UpdateSessionAuthStep(sessionID, 1) + if tc.nextAuthMode != "" { + b.SetNextAuthMode(sessionID, tc.nextAuthMode) } if tc.supportedLayouts == nil { @@ -278,7 +278,7 @@ func TestSelectAuthenticationMode(t *testing.T) { modeName string tokenExists bool - secondAuthStep bool + nextAuthMode string passwdSession bool customHandlers map[string]testutils.EndpointHandler supportedLayouts []map[string]string @@ -288,9 +288,9 @@ func TestSelectAuthenticationMode(t *testing.T) { "Successfully_select_password": {modeName: authmodes.Password, tokenExists: true}, "Successfully_select_device_auth_qr": {modeName: authmodes.DeviceQr}, "Successfully_select_device_auth": {supportedLayouts: supportedLayoutsWithoutQrCode, modeName: authmodes.Device}, - "Successfully_select_newpassword": {modeName: authmodes.NewPassword, secondAuthStep: true}, + "Successfully_select_newpassword": {modeName: authmodes.NewPassword, nextAuthMode: authmodes.NewPassword}, - "Selected_newpassword_shows_correct_label_in_passwd_session": {modeName: authmodes.NewPassword, passwdSession: true, tokenExists: true, secondAuthStep: true}, + "Selected_newpassword_shows_correct_label_in_passwd_session": {modeName: authmodes.NewPassword, passwdSession: true, tokenExists: true, nextAuthMode: authmodes.NewPassword}, "Error_when_selecting_invalid_mode": {modeName: "invalid", wantErr: true}, "Error_when_selecting_device_auth_qr_but_provider_is_unavailable": {modeName: authmodes.DeviceQr, wantErr: true, @@ -345,8 +345,8 @@ func TestSelectAuthenticationMode(t *testing.T) { err = os.WriteFile(b.TokenPathForSession(sessionID), []byte("some token"), 0600) require.NoError(t, err, "Setup: WriteFile should not have returned an error") } - if tc.secondAuthStep { - b.UpdateSessionAuthStep(sessionID, 1) + if tc.nextAuthMode != "" { + b.SetNextAuthMode(sessionID, tc.nextAuthMode) } if tc.supportedLayouts == nil { tc.supportedLayouts = supportedLayouts diff --git a/internal/broker/export_test.go b/internal/broker/export_test.go index 9283baeb5d..3b7d28d5b5 100644 --- a/internal/broker/export_test.go +++ b/internal/broker/export_test.go @@ -113,8 +113,8 @@ func (b *Broker) DataDir() string { return b.cfg.DataDir } -// UpdateSessionAuthStep updates the current auth step for the given session. -func (b *Broker) UpdateSessionAuthStep(sessionID string, authStep int) { +// SetNextAuthMode sets the next auth mode of the specified session. +func (b *Broker) SetNextAuthMode(sessionID string, authMode string) { b.currentSessionsMu.Lock() defer b.currentSessionsMu.Unlock() @@ -123,7 +123,7 @@ func (b *Broker) UpdateSessionAuthStep(sessionID string, authStep int) { return } - session.currentAuthStep = authStep + session.nextAuthMode = authMode b.currentSessions[sessionID] = session } From ffbf31b78daff11f6e073157555f911b96b73d51 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 17 Jan 2025 16:09:23 +0100 Subject: [PATCH 0373/1670] refactor: Simplify GetAuthenticationModes --- internal/broker/authmodes/consts.go | 10 +++ internal/broker/broker.go | 107 +++++++++++++++------------- 2 files changed, 69 insertions(+), 48 deletions(-) diff --git a/internal/broker/authmodes/consts.go b/internal/broker/authmodes/consts.go index 19fa8aedb0..366a9d2af5 100644 --- a/internal/broker/authmodes/consts.go +++ b/internal/broker/authmodes/consts.go @@ -14,3 +14,13 @@ const ( // NewPassword is the ID of the new password configuration method. NewPassword = "newpassword" ) + +var ( + // Label is a map of auth mode IDs to their display labels. + Label = map[string]string{ + Password: "Local Password Authentication", + Device: "Device Authentication", + DeviceQr: "Device Authentication", + NewPassword: "Define your local password", + } +) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index aba272ee18..30d9190b55 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -215,35 +215,28 @@ func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []m return nil, err } - // Check if the token exists in the cache. - tokenExists, err := fileutils.FileExists(session.tokenPath) + availableModes, err := b.availableAuthModes(session) if err != nil { - log.Warningf(context.Background(), "Could not check if token exists: %v", err) - } - if !tokenExists { - // Check the old encrypted token path. - tokenExists, err = fileutils.FileExists(session.oldEncryptedTokenPath) - if err != nil { - log.Warningf(context.Background(), "Could not check if old encrypted token exists: %v", err) - } + return nil, err } - availableModes, err := b.availableAuthModes(session, tokenExists) - if err != nil { + // Store the available auth modes, so that we can check in SelectAuthenticationMode if the selected mode is valid. + session.authModes = availableModes + if err := b.updateSession(sessionID, session); err != nil { return nil, err } modesSupportedByUI := b.authModesSupportedByUI(supportedUILayouts) for _, mode := range availableModes { - if _, ok := modesSupportedByUI[mode]; !ok { + if !slices.Contains(modesSupportedByUI, mode) { log.Infof(context.Background(), "Authentication mode %q is not supported by the UI", mode) continue } authModesWithLabels = append(authModesWithLabels, map[string]string{ "id": mode, - "label": modesSupportedByUI[mode], + "label": authmodes.Label[mode], }) } @@ -251,15 +244,10 @@ func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []m return nil, fmt.Errorf("no authentication modes available for user %q", session.username) } - session.authModes = availableModes - if err := b.updateSession(sessionID, session); err != nil { - return nil, err - } - return authModesWithLabels, nil } -func (b *Broker) availableAuthModes(session session, tokenExists bool) (availableModes []string, err error) { +func (b *Broker) availableAuthModes(session session) (availableModes []string, err error) { if session.nextAuthMode != "" { return []string{session.nextAuthMode}, nil } @@ -267,20 +255,21 @@ func (b *Broker) availableAuthModes(session session, tokenExists bool) (availabl switch session.mode { case sessionmode.ChangePassword, sessionmode.ChangePasswordOld: // session is for changing the password - if !tokenExists { + if !tokenExists(session) { return nil, errors.New("user has no cached token") } - availableModes = []string{authmodes.Password} + return []string{authmodes.Password}, nil - default: // session is for login + default: + // session is for login if !session.isOffline { availableModes = b.oidcAuthModes(session) } - if tokenExists { + if tokenExists(session) { availableModes = append([]string{authmodes.Password}, availableModes...) } + return availableModes, nil } - return availableModes, nil } func (b *Broker) oidcAuthModes(session session) []string { @@ -302,36 +291,58 @@ func (b *Broker) oidcAuthModes(session session) []string { return modes } -func (b *Broker) authModesSupportedByUI(supportedUILayouts []map[string]string) (supportedModes map[string]string) { - supportedModes = make(map[string]string) - for _, layout := range supportedUILayouts { - supportedEntries := strings.Split(strings.TrimPrefix(layout["entry"], "optional:"), ",") - switch layout["type"] { - case "qrcode": - if !strings.Contains(layout["wait"], "true") { - continue - } - deviceAuthID := authmodes.DeviceQr - if layout["renders_qrcode"] == "false" { - deviceAuthID = authmodes.Device - } - supportedModes[deviceAuthID] = "Device Authentication" +func tokenExists(session session) bool { + tokenExists, err := fileutils.FileExists(session.tokenPath) + if err != nil { + log.Warningf(context.Background(), "Could not check if token exists: %v", err) + } + if tokenExists { + return true + } - case "form": - if slices.Contains(supportedEntries, "chars_password") { - supportedModes[authmodes.Password] = "Local Password Authentication" - } + // Check the old encrypted token path. + tokenExists, err = fileutils.FileExists(session.oldEncryptedTokenPath) + if err != nil { + log.Warningf(context.Background(), "Could not check if old encrypted token exists: %v", err) + } + return tokenExists +} - case "newpassword": - if slices.Contains(supportedEntries, "chars_password") { - supportedModes[authmodes.NewPassword] = "Define your local password" - } +func (b *Broker) authModesSupportedByUI(supportedUILayouts []map[string]string) (supportedModes []string) { + for _, layout := range supportedUILayouts { + mode := b.supportedAuthModeFromLayout(layout) + if mode != "" { + supportedModes = append(supportedModes, mode) } } - return supportedModes } +func (b *Broker) supportedAuthModeFromLayout(layout map[string]string) string { + supportedEntries := strings.Split(strings.TrimPrefix(layout["entry"], "optional:"), ",") + switch layout["type"] { + case "qrcode": + if !strings.Contains(layout["wait"], "true") { + return "" + } + if layout["renders_qrcode"] == "false" { + return authmodes.Device + } + return authmodes.DeviceQr + + case "form": + if slices.Contains(supportedEntries, "chars_password") { + return authmodes.Password + } + + case "newpassword": + if slices.Contains(supportedEntries, "chars_password") { + return authmodes.NewPassword + } + } + return "" +} + // SelectAuthenticationMode selects the authentication mode for the user. func (b *Broker) SelectAuthenticationMode(sessionID, authModeID string) (uiLayoutInfo map[string]string, err error) { session, err := b.getSession(sessionID) From 8ded61bf39b64dabd7f9543aee9e47874177c60e Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 17 Jan 2025 16:26:16 +0100 Subject: [PATCH 0374/1670] Automatic device authentication when refresh token is expired --- internal/broker/broker.go | 58 +++++++++++-------- internal/broker/broker_test.go | 4 +- internal/broker/export_test.go | 6 +- ...assword_and_device_auth_qr_if_token_exists | 4 +- internal/providers/msentraid/msentraid.go | 5 ++ internal/providers/noprovider/noprovider.go | 6 ++ internal/providers/providers.go | 1 + 7 files changed, 52 insertions(+), 32 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 30d9190b55..04bf4f2761 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -67,7 +67,7 @@ type session struct { selectedMode string authModes []string attemptsPerMode map[string]int - nextAuthMode string + nextAuthModes []string oidcServer *oidc.Provider oauth2Config oauth2.Config @@ -248,8 +248,15 @@ func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []m } func (b *Broker) availableAuthModes(session session) (availableModes []string, err error) { - if session.nextAuthMode != "" { - return []string{session.nextAuthMode}, nil + if len(session.nextAuthModes) > 0 { + for _, mode := range session.nextAuthModes { + if !authModeIsAvailable(session, mode) { + log.Infof(context.Background(), "Authentication mode %q is not available", mode) + continue + } + availableModes = append(availableModes, mode) + } + return availableModes, nil } switch session.mode { @@ -262,33 +269,26 @@ func (b *Broker) availableAuthModes(session session) (availableModes []string, e default: // session is for login - if !session.isOffline { - availableModes = b.oidcAuthModes(session) - } - if tokenExists(session) { - availableModes = append([]string{authmodes.Password}, availableModes...) + modes := append(b.provider.SupportedOIDCAuthModes(), authmodes.Password) + for _, mode := range modes { + if authModeIsAvailable(session, mode) { + availableModes = append(availableModes, mode) + } } return availableModes, nil } } -func (b *Broker) oidcAuthModes(session session) []string { - var modes []string - endpoints := make(map[string]struct{}) - if session.oidcServer != nil && session.oidcServer.Endpoint().DeviceAuthURL != "" { - endpoints[authmodes.DeviceQr] = struct{}{} - endpoints[authmodes.Device] = struct{}{} - } - - for _, mode := range b.provider.SupportedOIDCAuthModes() { - if _, ok := endpoints[mode]; ok { - modes = append(modes, mode) - } else { - log.Warningf(context.Background(), "No provider endpoint for mode %q", mode) - } +func authModeIsAvailable(session session, authMode string) bool { + switch authMode { + case authmodes.Password: + return tokenExists(session) + case authmodes.NewPassword: + return true + case authmodes.Device, authmodes.DeviceQr: + return session.oidcServer != nil && session.oidcServer.Endpoint().DeviceAuthURL != "" && !session.isOffline } - - return modes + return false } func tokenExists(session session) bool { @@ -574,7 +574,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au } session.authInfo["auth_info"] = authInfo - session.nextAuthMode = authmodes.NewPassword + session.nextAuthModes = []string{authmodes.NewPassword} return AuthNext, nil @@ -626,6 +626,14 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au // Refresh the token if we're online even if the token has not expired if !session.isOffline { authInfo, err = b.refreshToken(ctx, session.oauth2Config, authInfo) + + var retrieveErr *oauth2.RetrieveError + if errors.As(err, &retrieveErr) && b.provider.IsTokenExpiredError(*retrieveErr) { + // The refresh token is expired, so the user needs to authenticate via OIDC again. + session.nextAuthModes = []string{authmodes.Device, authmodes.DeviceQr} + return AuthNext, nil + } + if err != nil { log.Error(context.Background(), err.Error()) return AuthDenied, errorMessage{Message: "could not refresh token"} diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index d2cbf0aefe..1b9f7bca27 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -236,7 +236,7 @@ func TestGetAuthenticationModes(t *testing.T) { require.NoError(t, err, "Setup: WriteFile should not have returned an error") } if tc.nextAuthMode != "" { - b.SetNextAuthMode(sessionID, tc.nextAuthMode) + b.SetNextAuthModes(sessionID, []string{tc.nextAuthMode}) } if tc.supportedLayouts == nil { @@ -346,7 +346,7 @@ func TestSelectAuthenticationMode(t *testing.T) { require.NoError(t, err, "Setup: WriteFile should not have returned an error") } if tc.nextAuthMode != "" { - b.SetNextAuthMode(sessionID, tc.nextAuthMode) + b.SetNextAuthModes(sessionID, []string{tc.nextAuthMode}) } if tc.supportedLayouts == nil { tc.supportedLayouts = supportedLayouts diff --git a/internal/broker/export_test.go b/internal/broker/export_test.go index 3b7d28d5b5..9336df33a0 100644 --- a/internal/broker/export_test.go +++ b/internal/broker/export_test.go @@ -113,8 +113,8 @@ func (b *Broker) DataDir() string { return b.cfg.DataDir } -// SetNextAuthMode sets the next auth mode of the specified session. -func (b *Broker) SetNextAuthMode(sessionID string, authMode string) { +// SetNextAuthModes sets the next auth mode of the specified session. +func (b *Broker) SetNextAuthModes(sessionID string, authModes []string) { b.currentSessionsMu.Lock() defer b.currentSessionsMu.Unlock() @@ -123,7 +123,7 @@ func (b *Broker) SetNextAuthMode(sessionID string, authMode string) { return } - session.nextAuthMode = authMode + session.nextAuthModes = authModes b.currentSessions[sessionID] = session } diff --git a/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_password_and_device_auth_qr_if_token_exists b/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_password_and_device_auth_qr_if_token_exists index 6327db6363..309786925e 100644 --- a/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_password_and_device_auth_qr_if_token_exists +++ b/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_password_and_device_auth_qr_if_token_exists @@ -1,4 +1,4 @@ -- id: password - label: Local Password Authentication - id: device_auth_qr label: Device Authentication +- id: password + label: Local Password Authentication diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 891fbd3cdc..c780ba6a81 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -324,3 +324,8 @@ func (c azureTokenCredential) GetToken(_ context.Context, _ policy.TokenRequestO ExpiresOn: c.token.Expiry, }, nil } + +// IsTokenExpiredError returns true if the reason for the error is that the refresh token is expired. +func (p Provider) IsTokenExpiredError(err oauth2.RetrieveError) bool { + return err.ErrorCode == "invalid_grant" && strings.HasPrefix(err.ErrorDescription, "AADSTS50173:") +} diff --git a/internal/providers/noprovider/noprovider.go b/internal/providers/noprovider/noprovider.go index da99619584..182d639e39 100644 --- a/internal/providers/noprovider/noprovider.go +++ b/internal/providers/noprovider/noprovider.go @@ -106,3 +106,9 @@ func (p NoProvider) userClaims(idToken *oidc.IDToken) (claims, error) { func (p NoProvider) getGroups(_ *oauth2.Token) ([]info.Group, error) { return nil, nil } + +// IsTokenExpiredError returns true if the reason for the error is that the refresh token is expired. +func (p NoProvider) IsTokenExpiredError(err oauth2.RetrieveError) bool { + // There is no generic error for this, so we return false. + return false +} diff --git a/internal/providers/providers.go b/internal/providers/providers.go index e292d90f0b..511ef0bffa 100644 --- a/internal/providers/providers.go +++ b/internal/providers/providers.go @@ -20,4 +20,5 @@ type Provider interface { NormalizeUsername(username string) string SupportedOIDCAuthModes() []string VerifyUsername(requestedUsername, authenticatedUsername string) error + IsTokenExpiredError(err oauth2.RetrieveError) bool } From 081d729e65dd478519c4eb249c2d58972f61e6df Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 17 Jan 2025 16:31:42 +0100 Subject: [PATCH 0375/1670] Enable debug logs in broker tests --- internal/broker/broker_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 1b9f7bca27..9693c4c7c0 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -20,6 +20,7 @@ import ( "github.com/ubuntu/authd-oidc-brokers/internal/testutils" "github.com/ubuntu/authd-oidc-brokers/internal/testutils/golden" "github.com/ubuntu/authd-oidc-brokers/internal/token" + "github.com/ubuntu/authd/log" "gopkg.in/yaml.v3" ) @@ -1183,6 +1184,8 @@ func TestUserPreCheck(t *testing.T) { } func TestMain(m *testing.M) { + log.SetLevel(log.DebugLevel) + var cleanup func() defaultIssuerURL, cleanup = testutils.StartMockProviderServer("", nil) defer cleanup() From d430c1e8ea4290885051fa121f9abeddcb14a07e Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 17 Jan 2025 16:33:46 +0100 Subject: [PATCH 0376/1670] Add test for device authentication as next auth mode --- internal/broker/broker_test.go | 1 + .../Get_device_auth_qr_if_next_auth_mode_is_device_qr | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 internal/broker/testdata/golden/TestGetAuthenticationModes/Get_device_auth_qr_if_next_auth_mode_is_device_qr diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 9693c4c7c0..0bc3d23a41 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -176,6 +176,7 @@ func TestGetAuthenticationModes(t *testing.T) { "Get_device_auth_qr_if_there_is_no_token": {}, "Get_newpassword_if_already_authenticated_with_device_auth_qr": {nextAuthMode: authmodes.NewPassword}, "Get_password_and_device_auth_qr_if_token_exists": {tokenExists: true}, + "Get_device_auth_qr_if_next_auth_mode_is_device_qr": {nextAuthMode: authmodes.DeviceQr}, "Get_only_password_if_token_exists_and_provider_is_not_available": {tokenExists: true, providerAddress: "127.0.0.1:31310", unavailableProvider: true}, "Get_only_password_if_token_exists_and_provider_does_not_support_device_auth_qr": {tokenExists: true, providerAddress: "127.0.0.1:31311", deviceAuthUnsupported: true}, diff --git a/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_device_auth_qr_if_next_auth_mode_is_device_qr b/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_device_auth_qr_if_next_auth_mode_is_device_qr new file mode 100644 index 0000000000..0742ddffe6 --- /dev/null +++ b/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_device_auth_qr_if_next_auth_mode_is_device_qr @@ -0,0 +1,2 @@ +- id: device_auth_qr + label: Device Authentication From fea001f3a953a9b797cad3d8202c4939fe6e254d Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 17 Jan 2025 16:37:20 +0100 Subject: [PATCH 0377/1670] Rename test cases For consistency --- internal/broker/broker_test.go | 14 +++++++------- ...t_newpassword_if_next_auth_mode_is_newpassword} | 0 ...ing_password_and_next_auth_mode_is_newpassword} | 0 3 files changed, 7 insertions(+), 7 deletions(-) rename internal/broker/testdata/golden/TestGetAuthenticationModes/{Get_newpassword_if_already_authenticated_with_device_auth_qr => Get_newpassword_if_next_auth_mode_is_newpassword} (100%) rename internal/broker/testdata/golden/TestGetAuthenticationModes/{Get_newpassword_if_already_authenticated_with_password_and_session_is_for_changing_password => Get_newpassword_if_session_is_for_changing_password_and_next_auth_mode_is_newpassword} (100%) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 0bc3d23a41..a67d8bf962 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -173,18 +173,18 @@ func TestGetAuthenticationModes(t *testing.T) { wantErr bool }{ // Authentication session - "Get_device_auth_qr_if_there_is_no_token": {}, - "Get_newpassword_if_already_authenticated_with_device_auth_qr": {nextAuthMode: authmodes.NewPassword}, - "Get_password_and_device_auth_qr_if_token_exists": {tokenExists: true}, - "Get_device_auth_qr_if_next_auth_mode_is_device_qr": {nextAuthMode: authmodes.DeviceQr}, + "Get_device_auth_qr_if_there_is_no_token": {}, + "Get_password_and_device_auth_qr_if_token_exists": {tokenExists: true}, + "Get_newpassword_if_next_auth_mode_is_newpassword": {nextAuthMode: authmodes.NewPassword}, + "Get_device_auth_qr_if_next_auth_mode_is_device_qr": {nextAuthMode: authmodes.DeviceQr}, "Get_only_password_if_token_exists_and_provider_is_not_available": {tokenExists: true, providerAddress: "127.0.0.1:31310", unavailableProvider: true}, "Get_only_password_if_token_exists_and_provider_does_not_support_device_auth_qr": {tokenExists: true, providerAddress: "127.0.0.1:31311", deviceAuthUnsupported: true}, // Change password session - "Get_only_password_if_token_exists_and_session_is_for_changing_password": {sessionMode: sessionmode.ChangePassword, tokenExists: true}, - "Get_newpassword_if_already_authenticated_with_password_and_session_is_for_changing_password": {sessionMode: sessionmode.ChangePassword, tokenExists: true, nextAuthMode: authmodes.NewPassword}, - "Get_only_password_if_token_exists_and_session_mode_is_the_old_passwd_value": {sessionMode: sessionmode.ChangePasswordOld, tokenExists: true}, + "Get_only_password_if_token_exists_and_session_is_for_changing_password": {sessionMode: sessionmode.ChangePassword, tokenExists: true}, + "Get_newpassword_if_session_is_for changing_password_and_next_auth_mode_is_newpassword": {sessionMode: sessionmode.ChangePassword, tokenExists: true, nextAuthMode: authmodes.NewPassword}, + "Get_only_password_if_token_exists_and_session_mode_is_the_old_passwd_value": {sessionMode: sessionmode.ChangePasswordOld, tokenExists: true}, "Error_if_there_is_no_session": {sessionID: "-", wantErr: true}, diff --git a/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_newpassword_if_already_authenticated_with_device_auth_qr b/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_newpassword_if_next_auth_mode_is_newpassword similarity index 100% rename from internal/broker/testdata/golden/TestGetAuthenticationModes/Get_newpassword_if_already_authenticated_with_device_auth_qr rename to internal/broker/testdata/golden/TestGetAuthenticationModes/Get_newpassword_if_next_auth_mode_is_newpassword diff --git a/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_newpassword_if_already_authenticated_with_password_and_session_is_for_changing_password b/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_newpassword_if_session_is_for_changing_password_and_next_auth_mode_is_newpassword similarity index 100% rename from internal/broker/testdata/golden/TestGetAuthenticationModes/Get_newpassword_if_already_authenticated_with_password_and_session_is_for_changing_password rename to internal/broker/testdata/golden/TestGetAuthenticationModes/Get_newpassword_if_session_is_for_changing_password_and_next_auth_mode_is_newpassword From 6bae33cb0ce73c10b732566b57f2bf94ea164870 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 17 Jan 2025 17:26:37 +0100 Subject: [PATCH 0378/1670] Add another test for device authentication as next auth mode --- internal/broker/broker.go | 2 -- internal/broker/broker_test.go | 13 +++++++++++ internal/broker/export_test.go | 12 ++++++++++ internal/broker/helper_test.go | 16 ++++++++----- .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json | 1 + .../first_call | 3 +++ .../second_call | 3 +++ internal/providers/noprovider/noprovider.go | 6 +++-- internal/testutils/provider.go | 23 +++++++++++++++++++ 10 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode/data/provider_url/test-user@email.com/password create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode/data/provider_url/test-user@email.com/token.json create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode/first_call create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode/second_call diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 04bf4f2761..e9bf6c7a8d 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -626,14 +626,12 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au // Refresh the token if we're online even if the token has not expired if !session.isOffline { authInfo, err = b.refreshToken(ctx, session.oauth2Config, authInfo) - var retrieveErr *oauth2.RetrieveError if errors.As(err, &retrieveErr) && b.provider.IsTokenExpiredError(*retrieveErr) { // The refresh token is expired, so the user needs to authenticate via OIDC again. session.nextAuthModes = []string{authmodes.Device, authmodes.DeviceQr} return AuthNext, nil } - if err != nil { log.Error(context.Background(), err.Error()) return AuthDenied, errorMessage{Message: "could not refresh token"} diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index a67d8bf962..126844d034 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -406,6 +406,7 @@ func TestIsAuthenticated(t *testing.T) { dontWaitForFirstCall bool readOnlyDataDir bool wantGroups []info.Group + wantNextAuthModes []string }{ "Successfully_authenticate_user_with_device_auth_and_newpassword": {firstSecret: "-", wantSecondCall: true}, "Successfully_authenticate_user_with_password": {firstMode: authmodes.Password, token: &tokenOptions{}}, @@ -463,6 +464,13 @@ func TestIsAuthenticated(t *testing.T) { token: &tokenOptions{noUserInfo: true}, getUserInfoFails: true, }, + "Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode": { + firstMode: authmodes.Password, + token: &tokenOptions{refreshTokenExpired: true}, + wantNextAuthModes: []string{authmodes.Device, authmodes.DeviceQr}, + wantSecondCall: true, + secondMode: authmodes.DeviceQr, + }, "Error_when_authentication_data_is_invalid": {invalidAuthData: true}, "Error_when_secret_can_not_be_decrypted": {firstMode: authmodes.Password, badFirstKey: true}, @@ -640,6 +648,11 @@ func TestIsAuthenticated(t *testing.T) { err = os.WriteFile(filepath.Join(outDir, "first_call"), out, 0600) require.NoError(t, err, "Failed to write first response") + if tc.wantNextAuthModes != nil { + nextAuthModes := b.GetNextAuthModes(sessionID) + require.ElementsMatch(t, tc.wantNextAuthModes, nextAuthModes, "Next auth modes should match") + } + if tc.wantGroups != nil { type userInfoMsgType struct { UserInfo info.User `json:"userinfo"` diff --git a/internal/broker/export_test.go b/internal/broker/export_test.go index 9336df33a0..c0be5df818 100644 --- a/internal/broker/export_test.go +++ b/internal/broker/export_test.go @@ -113,6 +113,18 @@ func (b *Broker) DataDir() string { return b.cfg.DataDir } +// GetNextAuthModes returns the next auth mode of the specified session. +func (b *Broker) GetNextAuthModes(sessionID string) []string { + b.currentSessionsMu.Lock() + defer b.currentSessionsMu.Unlock() + + session, ok := b.currentSessions[sessionID] + if !ok { + return nil + } + return session.nextAuthModes +} + // SetNextAuthModes sets the next auth mode of the specified session. func (b *Broker) SetNextAuthModes(sessionID string, authModes []string) { b.currentSessionsMu.Lock() diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index c17187fef3..7f6c4c2871 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -177,12 +177,13 @@ type tokenOptions struct { issuer string groups []info.Group - expired bool - noRefreshToken bool - noIDToken bool - invalid bool - invalidClaims bool - noUserInfo bool + expired bool + noRefreshToken bool + refreshTokenExpired bool + noIDToken bool + invalid bool + invalidClaims bool + noUserInfo bool } func generateCachedInfo(t *testing.T, options tokenOptions) *token.AuthCachedInfo { @@ -226,6 +227,9 @@ func generateCachedInfo(t *testing.T, options tokenOptions) *token.AuthCachedInf if options.noRefreshToken { tok.Token.RefreshToken = "" } + if options.refreshTokenExpired { + tok.Token.RefreshToken = testutils.ExpiredRefreshToken + } if !options.noUserInfo { tok.UserInfo = info.User{ diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode/data/provider_url/test-user@email.com/token.json new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode/data/provider_url/test-user@email.com/token.json @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode/first_call new file mode 100644 index 0000000000..d0887a134f --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode/first_call @@ -0,0 +1,3 @@ +access: next +data: '{}' +err: diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode/second_call b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode/second_call new file mode 100644 index 0000000000..d0887a134f --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode/second_call @@ -0,0 +1,3 @@ +access: next +data: '{}' +err: diff --git a/internal/providers/noprovider/noprovider.go b/internal/providers/noprovider/noprovider.go index 182d639e39..9d07c8f08b 100644 --- a/internal/providers/noprovider/noprovider.go +++ b/internal/providers/noprovider/noprovider.go @@ -4,6 +4,7 @@ package noprovider import ( "context" "fmt" + "strings" "github.com/coreos/go-oidc/v3/oidc" "github.com/ubuntu/authd-oidc-brokers/internal/broker/authmodes" @@ -109,6 +110,7 @@ func (p NoProvider) getGroups(_ *oauth2.Token) ([]info.Group, error) { // IsTokenExpiredError returns true if the reason for the error is that the refresh token is expired. func (p NoProvider) IsTokenExpiredError(err oauth2.RetrieveError) bool { - // There is no generic error for this, so we return false. - return false + // TODO: This is an msentraid specific error code and description. + // Change it to the ones from Google once we know them. + return err.ErrorCode == "invalid_grant" && strings.HasPrefix(err.ErrorDescription, "AADSTS50173:") } diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index d383e506ba..c660307024 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -12,6 +12,7 @@ import ( "net" "net/http" "net/http/httptest" + "net/http/httputil" "slices" "strings" "sync" @@ -23,9 +24,15 @@ import ( "github.com/ubuntu/authd-oidc-brokers/internal/consts" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "github.com/ubuntu/authd-oidc-brokers/internal/providers/noprovider" + "github.com/ubuntu/authd/log" "golang.org/x/oauth2" ) +const ( + // ExpiredRefreshToken is used to test the expired refresh token error. + ExpiredRefreshToken = "expired-refresh-token" +) + // MockKey is the RSA key used to sign the JWTs for the mock provider. var MockKey *rsa.PrivateKey @@ -197,6 +204,22 @@ func TokenHandler(serverURL string, opts *TokenHandlerOptions) EndpointHandler { } return func(w http.ResponseWriter, r *http.Request) { + s, err := httputil.DumpRequest(r, true) + if err != nil { + log.Errorf(context.Background(), "could not dump request: %v", err) + } + log.Debugf(context.Background(), "/token endpoint request:\n%s", s) + + // Handle expired refresh token + refreshToken := r.FormValue("refresh_token") + if refreshToken == ExpiredRefreshToken { + w.Header().Add("Content-Type", "application/json") + w.WriteHeader(http.StatusBadRequest) + // This is an msentraid specific error code and description. + _, _ = w.Write([]byte(`{"error": "invalid_grant", "error_description": "AADSTS50173: The refresh token has expired."}`)) + return + } + // Mimics user going through auth process time.Sleep(2 * time.Second) From 5cd06a95e44f19dc028732384fe74ceb2870e7e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 11:51:33 +0000 Subject: [PATCH 0379/1670] deps(go-tools): bump github.com/golangci/golangci-lint in /tools Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.63.4 to 1.64.5. - [Release notes](https://github.com/golangci/golangci-lint/releases) - [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md) - [Commits](https://github.com/golangci/golangci-lint/compare/v1.63.4...v1.64.5) --- updated-dependencies: - dependency-name: github.com/golangci/golangci-lint dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- tools/go.mod | 73 ++++++++++++----------- tools/go.sum | 163 ++++++++++++++++++++++++--------------------------- 2 files changed, 112 insertions(+), 124 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index 0900f1c75e..03b6f161b1 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -4,18 +4,21 @@ go 1.23.0 toolchain go1.23.6 -require github.com/golangci/golangci-lint v1.63.4 +require ( + github.com/golangci/golangci-lint v1.64.5 + golang.org/x/mod v0.23.0 +) require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect - 4d63.com/gochecknoglobals v0.2.1 // indirect + 4d63.com/gochecknoglobals v0.2.2 // indirect github.com/4meepo/tagalign v1.4.1 // indirect github.com/Abirdcfly/dupword v0.1.3 // indirect github.com/Antonboom/errname v1.0.0 // indirect github.com/Antonboom/nilnil v1.0.1 // indirect github.com/Antonboom/testifylint v1.5.2 // indirect github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect - github.com/Crocmagnon/fatcontext v0.5.3 // indirect + github.com/Crocmagnon/fatcontext v0.7.1 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect @@ -24,7 +27,7 @@ require ( github.com/alexkohler/nakedret/v2 v2.0.5 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect - github.com/alingse/nilnesserr v0.1.1 // indirect + github.com/alingse/nilnesserr v0.1.2 // indirect github.com/ashanbrown/forbidigo v1.6.0 // indirect github.com/ashanbrown/makezero v1.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -35,9 +38,9 @@ require ( github.com/breml/errchkjson v0.4.0 // indirect github.com/butuzov/ireturn v0.3.1 // indirect github.com/butuzov/mirror v1.3.0 // indirect - github.com/catenacyber/perfsprint v0.7.1 // indirect + github.com/catenacyber/perfsprint v0.8.1 // indirect github.com/ccojocar/zxcvbn-go v1.0.2 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charithe/durationcheck v0.0.10 // indirect github.com/chavacava/garif v0.1.0 // indirect github.com/ckaznocha/intrange v0.3.0 // indirect @@ -51,8 +54,8 @@ require ( github.com/firefart/nonamedreturns v1.0.5 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/ghostiam/protogetter v0.3.8 // indirect - github.com/go-critic/go-critic v0.11.5 // indirect + github.com/ghostiam/protogetter v0.3.9 // indirect + github.com/go-critic/go-critic v0.12.0 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect github.com/go-toolsmith/astequal v1.2.0 // indirect @@ -67,16 +70,16 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect github.com/golangci/go-printf-func-name v0.1.0 // indirect - github.com/golangci/gofmt v0.0.0-20241223200906-057b0627d9b9 // indirect + github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect github.com/golangci/misspell v0.6.0 // indirect github.com/golangci/plugin-module-register v0.1.1 // indirect - github.com/golangci/revgrep v0.5.3 // indirect + github.com/golangci/revgrep v0.8.0 // indirect github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/gordonklaus/ineffassign v0.1.0 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect github.com/gostaticanalysis/comment v1.4.2 // indirect - github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect + github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect github.com/gostaticanalysis/nilerr v0.1.1 // indirect github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect github.com/hashicorp/go-version v1.7.0 // indirect @@ -88,16 +91,15 @@ require ( github.com/jingyugao/rowserrcheck v1.1.1 // indirect github.com/jjti/go-spancheck v0.6.4 // indirect github.com/julz/importas v0.2.0 // indirect - github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect + github.com/karamaru-alpha/copyloopvar v1.2.1 // indirect github.com/kisielk/errcheck v1.8.0 // indirect github.com/kkHAIKE/contextcheck v1.1.5 // indirect github.com/kulti/thelper v0.6.3 // indirect github.com/kunwardeep/paralleltest v1.0.10 // indirect - github.com/kyoh86/exportloopref v0.1.11 // indirect github.com/lasiar/canonicalheader v1.1.2 // indirect - github.com/ldez/exptostd v0.3.1 // indirect - github.com/ldez/gomoddirectives v0.6.0 // indirect - github.com/ldez/grignotin v0.7.0 // indirect + github.com/ldez/exptostd v0.4.1 // indirect + github.com/ldez/gomoddirectives v0.6.1 // indirect + github.com/ldez/grignotin v0.9.0 // indirect github.com/ldez/tagliatelle v0.7.1 // indirect github.com/ldez/usetesting v0.4.2 // indirect github.com/leonklingele/grouper v1.1.2 // indirect @@ -105,24 +107,24 @@ require ( github.com/magiconair/properties v1.8.6 // indirect github.com/maratori/testableexamples v1.0.0 // indirect github.com/maratori/testpackage v1.1.1 // indirect - github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/matoous/godox v1.1.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mgechev/revive v1.5.1 // indirect + github.com/mgechev/revive v1.6.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moricho/tparallel v0.3.2 // indirect github.com/nakabonne/nestif v0.3.1 // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect - github.com/nunnatsa/ginkgolinter v0.18.4 // indirect + github.com/nunnatsa/ginkgolinter v0.19.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/polyfloyd/go-errorlint v1.7.0 // indirect + github.com/polyfloyd/go-errorlint v1.7.1 // indirect github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect @@ -141,25 +143,24 @@ require ( github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.28.0 // indirect - github.com/securego/gosec/v2 v2.21.4 // indirect - github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect + github.com/securego/gosec/v2 v2.22.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect github.com/sivchari/tenv v1.12.1 // indirect github.com/sonatard/noctx v0.1.0 // indirect github.com/sourcegraph/go-diff v0.7.0 // indirect - github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/afero v1.12.0 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/spf13/viper v1.12.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/testify v1.10.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect - github.com/tdakkota/asciicheck v0.3.0 // indirect + github.com/tdakkota/asciicheck v0.4.0 // indirect github.com/tetafro/godot v1.4.20 // indirect github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 // indirect github.com/timonwong/loggercheck v0.10.1 // indirect @@ -168,30 +169,28 @@ require ( github.com/ultraware/funlen v0.2.0 // indirect github.com/ultraware/whitespace v0.2.0 // indirect github.com/uudashr/gocognit v1.2.0 // indirect - github.com/uudashr/iface v1.3.0 // indirect + github.com/uudashr/iface v1.3.1 // indirect github.com/xen0n/gosmopolitan v1.2.2 // indirect github.com/yagipy/maintidx v1.0.0 // indirect github.com/yeya24/promlinter v0.3.0 // indirect github.com/ykadowak/zerologlint v0.1.5 // indirect gitlab.com/bosi/decorder v0.4.2 // indirect go-simpler.org/musttag v0.13.0 // indirect - go-simpler.org/sloglint v0.7.2 // indirect + go-simpler.org/sloglint v0.9.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect - golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f // indirect - golang.org/x/mod v0.22.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/text v0.20.0 // indirect - golang.org/x/tools v0.28.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect + golang.org/x/tools v0.30.0 // indirect + google.golang.org/protobuf v1.36.4 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - honnef.co/go/tools v0.5.1 // indirect + honnef.co/go/tools v0.6.0 // indirect mvdan.cc/gofumpt v0.7.0 // indirect mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect ) diff --git a/tools/go.sum b/tools/go.sum index a698f87148..ad7fb72fe8 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -1,7 +1,7 @@ 4d63.com/gocheckcompilerdirectives v1.2.1 h1:AHcMYuw56NPjq/2y615IGg2kYkBdTvOaojYCBcRE7MA= 4d63.com/gocheckcompilerdirectives v1.2.1/go.mod h1:yjDJSxmDTtIHHCqX0ufRYZDL6vQtMG7tJdKVeWwsqvs= -4d63.com/gochecknoglobals v0.2.1 h1:1eiorGsgHOFOuoOiJDy2psSrQbRdIHrlge0IJIkUgDc= -4d63.com/gochecknoglobals v0.2.1/go.mod h1:KRE8wtJB3CXCsb1xy421JfTHIIbmT3U5ruxw2Qu8fSU= +4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= +4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -49,8 +49,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Crocmagnon/fatcontext v0.5.3 h1:zCh/wjc9oyeF+Gmp+V60wetm8ph2tlsxocgg/J0hOps= -github.com/Crocmagnon/fatcontext v0.5.3/go.mod h1:XoCQYY1J+XTfyv74qLXvNw4xFunr3L1wkopIIKG7wGM= +github.com/Crocmagnon/fatcontext v0.7.1 h1:SC/VIbRRZQeQWj/TcQBS6JmrXcfA+BU4OGSVUt54PjM= +github.com/Crocmagnon/fatcontext v0.7.1/go.mod h1:1wMvv3NXEBJucFGfwOJBxSVWcoIO6emV215SMkW9MFU= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 h1:/fTUt5vmbkAcMBt4YQiuC23cV0kEsN1MVMNqeOW43cU= @@ -76,8 +76,8 @@ github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pO github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= -github.com/alingse/nilnesserr v0.1.1 h1:7cYuJewpy9jFNMEA72Q1+3Nm3zKHzg+Q28D5f2bBFUA= -github.com/alingse/nilnesserr v0.1.1/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= +github.com/alingse/nilnesserr v0.1.2 h1:Yf8Iwm3z2hUUrP4muWfW83DF4nE3r1xZ26fGWUKCZlo= +github.com/alingse/nilnesserr v0.1.2/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY= github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= github.com/ashanbrown/makezero v1.2.0 h1:/2Lp1bypdmK9wDIq7uWBlDF1iMUpIIS4A+pF6C9IEUU= @@ -102,14 +102,15 @@ github.com/butuzov/ireturn v0.3.1 h1:mFgbEI6m+9W8oP/oDdfA34dLisRFCj2G6o/yiI1yZrY github.com/butuzov/ireturn v0.3.1/go.mod h1:ZfRp+E7eJLC0NQmk1Nrm1LOrn/gQlOykv+cVPdiXH5M= github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc= github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= -github.com/catenacyber/perfsprint v0.7.1 h1:PGW5G/Kxn+YrN04cRAZKC+ZuvlVwolYMrIyyTJ/rMmc= -github.com/catenacyber/perfsprint v0.7.1/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= +github.com/catenacyber/perfsprint v0.8.1 h1:bGOHuzHe0IkoGeY831RW4aSlt1lPRd3WRAScSWOaV7E= +github.com/catenacyber/perfsprint v0.8.1/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= @@ -151,10 +152,10 @@ github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwV github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/ghostiam/protogetter v0.3.8 h1:LYcXbYvybUyTIxN2Mj9h6rHrDZBDwZloPoKctWrFyJY= -github.com/ghostiam/protogetter v0.3.8/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA= -github.com/go-critic/go-critic v0.11.5 h1:TkDTOn5v7EEngMxu8KbuFqFR43USaaH8XRJLz1jhVYA= -github.com/go-critic/go-critic v0.11.5/go.mod h1:wu6U7ny9PiaHaZHcvMDmdysMqvDem162Rh3zWTrqk8M= +github.com/ghostiam/protogetter v0.3.9 h1:j+zlLLWzqLay22Cz/aYwTHKQ88GE2DQ6GkWSYFOI4lQ= +github.com/ghostiam/protogetter v0.3.9/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA= +github.com/go-critic/go-critic v0.12.0 h1:iLosHZuye812wnkEz1Xu3aBwn5ocCPfc9yqmFG9pa6w= +github.com/go-critic/go-critic v0.12.0/go.mod h1:DpE0P6OVc6JzVYzmM5gq5jMU31zLr4am5mB/VfFK64w= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -232,16 +233,16 @@ github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9 github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU= github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s= -github.com/golangci/gofmt v0.0.0-20241223200906-057b0627d9b9 h1:t5wybL6RtO83VwoMOb7U/Peqe3gGKQlPIC66wXmnkvM= -github.com/golangci/gofmt v0.0.0-20241223200906-057b0627d9b9/go.mod h1:Ag3L7sh7E28qAp/5xnpMMTuGYqxLZoSaEHZDkZB1RgU= -github.com/golangci/golangci-lint v1.63.4 h1:bJQFQ3hSfUto597dkL7ipDzOxsGEpiWdLiZ359OWOBI= -github.com/golangci/golangci-lint v1.63.4/go.mod h1:Hx0B7Lg5/NXbaOHem8+KU+ZUIzMI6zNj/7tFwdnn10I= +github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE= +github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY= +github.com/golangci/golangci-lint v1.64.5 h1:5omC86XFBKXZgCrVdUWU+WNHKd+CWCxNx717KXnzKZY= +github.com/golangci/golangci-lint v1.64.5/go.mod h1:WZnwq8TF0z61h3jLQ7Sk5trcP7b3kUFxLD6l1ivtdvU= github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= -github.com/golangci/revgrep v0.5.3 h1:3tL7c1XBMtWHHqVpS5ChmiAAoe4PF/d5+ULzV9sLAzs= -github.com/golangci/revgrep v0.5.3/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= +github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s= +github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed h1:IURFTjxeTfNFP0hTEi1YKjB/ub8zkpaOqFFMApi2EAs= github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed/go.mod h1:XLXN8bNw4CGRPaqgl3bv/lhz7bsGPh4/xSaMTbo2vkQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -270,8 +271,8 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA= -github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -282,8 +283,8 @@ github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/o github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= github.com/gostaticanalysis/comment v1.4.2 h1:hlnx5+S2fY9Zo9ePo4AhgYsYHbM2+eAv8m/s1JiCd6Q= github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= -github.com/gostaticanalysis/forcetypeassert v0.1.0 h1:6eUflI3DiGusXGK6X7cCcIgVCpZ2CiZ1Q7jl6ZxNV70= -github.com/gostaticanalysis/forcetypeassert v0.1.0/go.mod h1:qZEedyP/sY1lTGV1uJ3VhWZ2mqag3IkWsDHVbplHXak= +github.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk= +github.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY= github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk= github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= @@ -324,8 +325,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ= github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY= -github.com/karamaru-alpha/copyloopvar v1.1.0 h1:x7gNyKcC2vRBO1H2Mks5u1VxQtYvFiym7fCjIP8RPos= -github.com/karamaru-alpha/copyloopvar v1.1.0/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= +github.com/karamaru-alpha/copyloopvar v1.2.1 h1:wmZaZYIjnJ0b5UoKDjUHrikcV0zuPyyxI4SVplLd2CI= +github.com/karamaru-alpha/copyloopvar v1.2.1/go.mod h1:nFmMlFNlClC2BPvNaHMdkirmTJxVCY0lhxBtlfOypMM= github.com/kisielk/errcheck v1.8.0 h1:ZX/URYa7ilESY19ik/vBmCn6zdGQLxACwjAcWbHlYlg= github.com/kisielk/errcheck v1.8.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -345,16 +346,14 @@ github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= github.com/kunwardeep/paralleltest v1.0.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCTdvWJ/lDDs= github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= -github.com/kyoh86/exportloopref v0.1.11 h1:1Z0bcmTypkL3Q4k+IDHMWTcnCliEZcaPiIe0/ymEyhQ= -github.com/kyoh86/exportloopref v0.1.11/go.mod h1:qkV4UF1zGl6EkF1ox8L5t9SwyeBAZ3qLMd6up458uqA= github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= -github.com/ldez/exptostd v0.3.1 h1:90yWWoAKMFHeovTK8uzBms9Ppp8Du/xQ20DRO26Ymrw= -github.com/ldez/exptostd v0.3.1/go.mod h1:iZBRYaUmcW5jwCR3KROEZ1KivQQp6PHXbDPk9hqJKCQ= -github.com/ldez/gomoddirectives v0.6.0 h1:Jyf1ZdTeiIB4dd+2n4qw+g4aI9IJ6JyfOZ8BityWvnA= -github.com/ldez/gomoddirectives v0.6.0/go.mod h1:TuwOGYoPAoENDWQpe8DMqEm5nIfjrxZXmxX/CExWyZ4= -github.com/ldez/grignotin v0.7.0 h1:vh0dI32WhHaq6LLPZ38g7WxXuZ1+RzyrJ7iPG9JMa8c= -github.com/ldez/grignotin v0.7.0/go.mod h1:uaVTr0SoZ1KBii33c47O1M8Jp3OP3YDwhZCmzT9GHEk= +github.com/ldez/exptostd v0.4.1 h1:DIollgQ3LWZMp3HJbSXsdE2giJxMfjyHj3eX4oiD6JU= +github.com/ldez/exptostd v0.4.1/go.mod h1:iZBRYaUmcW5jwCR3KROEZ1KivQQp6PHXbDPk9hqJKCQ= +github.com/ldez/gomoddirectives v0.6.1 h1:Z+PxGAY+217f/bSGjNZr/b2KTXcyYLgiWI6geMBN2Qc= +github.com/ldez/gomoddirectives v0.6.1/go.mod h1:cVBiu3AHR9V31em9u2kwfMKD43ayN5/XDgr+cdaFaKs= +github.com/ldez/grignotin v0.9.0 h1:MgOEmjZIVNn6p5wPaGp/0OKWyvq42KnzAt/DAb8O4Ow= +github.com/ldez/grignotin v0.9.0/go.mod h1:uaVTr0SoZ1KBii33c47O1M8Jp3OP3YDwhZCmzT9GHEk= github.com/ldez/tagliatelle v0.7.1 h1:bTgKjjc2sQcsgPiT902+aadvMjCeMHrY7ly2XKFORIk= github.com/ldez/tagliatelle v0.7.1/go.mod h1:3zjxUpsNB2aEZScWiZTHrAXOl1x25t3cRmzfK1mlo2I= github.com/ldez/usetesting v0.4.2 h1:J2WwbrFGk3wx4cZwSMiCQQ00kjGR0+tuuyW0Lqm4lwA= @@ -369,13 +368,12 @@ github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc= -github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 h1:gWg6ZQ4JhDfJPqlo2srm/LN17lpybq15AryXIRcWYLE= -github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= +github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4= +github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= @@ -383,8 +381,8 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mgechev/revive v1.5.1 h1:hE+QPeq0/wIzJwOphdVyUJ82njdd8Khp4fUIHGZHW3M= -github.com/mgechev/revive v1.5.1/go.mod h1:lC9AhkJIBs5zwx8wkudyHrU+IJkrEKmpCmGMnIJPk4o= +github.com/mgechev/revive v1.6.1 h1:ncK0ZCMWtb8GXwVAmk+IeWF2ULIDsvRxSRfg5sTwQ2w= +github.com/mgechev/revive v1.6.1/go.mod h1:/2tfHWVO8UQi/hqJsIYNEKELi+DJy/e+PQpLgTB1v88= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -404,14 +402,14 @@ github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhK github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/nunnatsa/ginkgolinter v0.18.4 h1:zmX4KUR+6fk/vhUFt8DOP6KwznekhkmVSzzVJve2vyM= -github.com/nunnatsa/ginkgolinter v0.18.4/go.mod h1:AMEane4QQ6JwFz5GgjI5xLUM9S/CylO+UyM97fN2iBI= +github.com/nunnatsa/ginkgolinter v0.19.0 h1:CnHRFAeBS3LdLI9h+Jidbcc5KH71GKOmaBZQk8Srnto= +github.com/nunnatsa/ginkgolinter v0.19.0/go.mod h1:jkQ3naZDmxaZMXPWaS9rblH+i+GWXQCaS/JFIWcOH2s= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= -github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= -github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= -github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= +github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= +github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= @@ -429,8 +427,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polyfloyd/go-errorlint v1.7.0 h1:Zp6lzCK4hpBDj8y8a237YK4EPrMXQWvOe3nGoH4pFrU= -github.com/polyfloyd/go-errorlint v1.7.0/go.mod h1:dGWKu85mGHnegQ2SWpEybFityCg3j7ZbwsVUxAOk9gY= +github.com/polyfloyd/go-errorlint v1.7.1 h1:RyLVXIbosq1gBdk/pChWA8zWYLsq9UEw7a1L5TVMCnA= +github.com/polyfloyd/go-errorlint v1.7.1/go.mod h1:aXjNb1x2TNhoLsk26iv1yl7a+zTnXPhwEMtEXukiLR8= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -486,10 +484,8 @@ github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tM github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.28.0 h1:jZnudE2zKCtYlGzLVreNp5pmCdOxXUzwsMDBkR21cyQ= github.com/sashamelentyev/usestdlibvars v1.28.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= -github.com/securego/gosec/v2 v2.21.4 h1:Le8MSj0PDmOnHJgUATjD96PaXRvCpKC+DGJvwyy0Mlk= -github.com/securego/gosec/v2 v2.21.4/go.mod h1:Jtb/MwRQfRxCXyCm1rfM1BEiiiTfUOdyzzAhlr6lUTA= -github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= -github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= +github.com/securego/gosec/v2 v2.22.1 h1:IcBt3TpI5Y9VN1YlwjSpM2cHu0i3Iw52QM+PQeg7jN8= +github.com/securego/gosec/v2 v2.22.1/go.mod h1:4bb95X4Jz7VSEPdVjC0hD7C/yR6kdeUBvCPOy9gDQ0g= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -505,16 +501,17 @@ github.com/sonatard/noctx v0.1.0 h1:JjqOc2WN16ISWAjAk8M5ej0RfExEXtkEyExl2hLW+OM= github.com/sonatard/noctx v0.1.0/go.mod h1:0RvBxqY8D4j9cTTTWE8ylt2vqj2EPI8fHmrxHdsaZ2c= github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= @@ -539,8 +536,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/tdakkota/asciicheck v0.3.0 h1:LqDGgZdholxZMaJgpM6b0U9CFIjDCbFdUF00bDnBKOQ= -github.com/tdakkota/asciicheck v0.3.0/go.mod h1:KoJKXuX/Z/lt6XzLo8WMBfQGzak0SrAKZlvRr4tg8Ac= +github.com/tdakkota/asciicheck v0.4.0 h1:VZ13Itw4k1i7d+dpDSNS8Op645XgGHpkCEh/WHicgWw= +github.com/tdakkota/asciicheck v0.4.0/go.mod h1:0k7M3rCfRXb0Z6bwgvkEIMleKH3kXNz9UqJ9Xuqopr8= github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= @@ -561,8 +558,8 @@ github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSW github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA= github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU= -github.com/uudashr/iface v1.3.0 h1:zwPch0fs9tdh9BmL5kcgSpvnObV+yHjO4JjVBl8IA10= -github.com/uudashr/iface v1.3.0/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg= +github.com/uudashr/iface v1.3.1 h1:bA51vmVx1UIhiIsQFSNq6GZ6VPTk3WNMZgRiCe9R29U= +github.com/uudashr/iface v1.3.1/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg= github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= @@ -584,8 +581,8 @@ go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= go-simpler.org/musttag v0.13.0 h1:Q/YAW0AHvaoaIbsPj3bvEI5/QFP7w696IMUpnKXQfCE= go-simpler.org/musttag v0.13.0/go.mod h1:FTzIGeK6OkKlUDVpj0iQUXZLUO1Js9+mvykDQy9C5yM= -go-simpler.org/sloglint v0.7.2 h1:Wc9Em/Zeuu7JYpl+oKoYOsQSy2X560aVueCW/m6IijY= -go-simpler.org/sloglint v0.7.2/go.mod h1:US+9C80ppl7VsThQclkM7BkCHQAzuz8kHLsW3ppuluo= +go-simpler.org/sloglint v0.9.0 h1:/40NQtjRx9txvsB/RN022KsUJU+zaaSb/9q9BSefSrE= +go-simpler.org/sloglint v0.9.0/go.mod h1:G/OrAF6uxj48sHahCzrbarVMptL2kjWTaUeC8+fOGww= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -624,8 +621,8 @@ golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWB golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f h1:WTyX8eCCyfdqiPYkRGm0MqElSfYFH3yR1+rl/mct9sA= -golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac h1:TSSpLIG4v+p0rPv1pNOQtl1I8knsO4S9trOxNMOLVP4= +golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -655,8 +652,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -692,14 +689,13 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= 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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -721,8 +717,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -769,20 +765,17 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc 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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= @@ -796,13 +789,12 @@ golang.org/x/text v0.3.3/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.6.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.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -812,7 +804,6 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190321232350-e250d351ecad/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -820,7 +811,6 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -860,13 +850,12 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= -golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= -golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= -golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -946,8 +935,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj 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.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= +google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -974,8 +963,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I= -honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= +honnef.co/go/tools v0.6.0 h1:TAODvD3knlq75WCp2nyGJtT4LeRV/o7NN9nYPeVJXf8= +honnef.co/go/tools v0.6.0/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU= mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo= mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f h1:lMpcwN6GxNbWtbpI1+xzFLSW8XzX0u72NttUGVFjO3U= From 5de45dd4993811df0b3d2f06c897be2da81bb5a4 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 20 Feb 2025 15:48:58 +0100 Subject: [PATCH 0380/1670] Appease golangci-lint golangci-lint failed with: cmd/authd-oidc/daemon/export_test.go:89:1: directive `//nolint:revive // DaemonConfig is a type alias for tests` is unused for linter "revive" (nolintlint) --- cmd/authd-oidc/daemon/export_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmd/authd-oidc/daemon/export_test.go b/cmd/authd-oidc/daemon/export_test.go index 7406168952..fda6f80346 100644 --- a/cmd/authd-oidc/daemon/export_test.go +++ b/cmd/authd-oidc/daemon/export_test.go @@ -85,8 +85,6 @@ func GenerateBrokerConfig(t *testing.T, p, providerURL string) { } // Config returns a DaemonConfig for tests. -// -//nolint:revive // DaemonConfig is a type alias for tests func (a App) Config() DaemonConfig { return a.config } From a39f6c44a3a756291593b9c0067f942f7baf8425 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 20 Feb 2025 15:49:58 +0100 Subject: [PATCH 0381/1670] Appease golangci-lint golangci-lint failed with: internal/providers/msentraid/msentraid.go:121:2: right hand must be only type assertion (forcetypeassert) userGroups, err := p.getGroups(accessToken, msgraphHost.(string)) --- internal/providers/msentraid/msentraid.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index c780ba6a81..fab264626f 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -108,9 +108,13 @@ func (p Provider) GetMetadata(provider *oidc.Provider) (map[string]interface{}, // GetUserInfo returns the user info from the ID token and the groups the user is a member of, which are retrieved via // the Microsoft Graph API. func (p Provider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken, providerMetadata map[string]interface{}) (info.User, error) { - msgraphHost := providerMetadata["msgraph_host"] - if msgraphHost == nil { - msgraphHost = defaultMSGraphHost + msgraphHost := defaultMSGraphHost + if providerMetadata["msgraph_host"] != nil { + var ok bool + msgraphHost, ok = providerMetadata["msgraph_host"].(string) + if !ok { + return info.User{}, fmt.Errorf("failed to cast msgraph_host to string: %v", providerMetadata["msgraph_host"]) + } } userClaims, err := p.userClaims(idToken) @@ -118,7 +122,7 @@ func (p Provider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, id return info.User{}, err } - userGroups, err := p.getGroups(accessToken, msgraphHost.(string)) + userGroups, err := p.getGroups(accessToken, msgraphHost) if err != nil { return info.User{}, err } From fdeac50f22bc35aa21ced5c8d30b0cc30f178186 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 08:37:12 +0000 Subject: [PATCH 0382/1670] deps(go): bump golang.org/x/crypto from 0.33.0 to 0.34.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.33.0 to 0.34.0. - [Commits](https://github.com/golang/crypto/compare/v0.33.0...v0.34.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 428fe1d366..71ee98b60a 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/ubuntu/authd v0.4.2-0.20250203155723-a499f298f969 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 - golang.org/x/crypto v0.33.0 + golang.org/x/crypto v0.34.0 golang.org/x/oauth2 v0.26.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index ff0f20866e..784f4a26ea 100644 --- a/go.sum +++ b/go.sum @@ -138,8 +138,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/crypto v0.34.0 h1:+/C6tk6rf/+t5DhUketUbD1aNGqiSX3j15Z6xuIDlBA= +golang.org/x/crypto v0.34.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= From e1288f5b5ec719d82be2b02be4a4d3c465b4b46d Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 26 Feb 2025 16:29:49 +0100 Subject: [PATCH 0383/1670] Lower log level of noisy message --- internal/broker/broker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index e9bf6c7a8d..3fbc3b5257 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -230,7 +230,7 @@ func (b *Broker) GetAuthenticationModes(sessionID string, supportedUILayouts []m for _, mode := range availableModes { if !slices.Contains(modesSupportedByUI, mode) { - log.Infof(context.Background(), "Authentication mode %q is not supported by the UI", mode) + log.Debugf(context.Background(), "Authentication mode %q is not supported by the UI", mode) continue } From ba63056980e0392bc89294893f75d142dce6924b Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 26 Feb 2025 16:30:22 +0100 Subject: [PATCH 0384/1670] Fix device auth being the default when local password is available Commit 8ded61bf39b64dabd7f9543aee9e47874177c60e changed the order of the available modes, putting password authentication last. The order is actually important, because authd picks the first one. This commit fixes the order and adds a comment explaining why the order is important. --- internal/broker/broker.go | 9 ++++++--- .../Get_password_and_device_auth_qr_if_token_exists | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 3fbc3b5257..56a70d8469 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -261,15 +261,18 @@ func (b *Broker) availableAuthModes(session session) (availableModes []string, e switch session.mode { case sessionmode.ChangePassword, sessionmode.ChangePasswordOld: - // session is for changing the password + // Session is for changing the password. if !tokenExists(session) { return nil, errors.New("user has no cached token") } return []string{authmodes.Password}, nil default: - // session is for login - modes := append(b.provider.SupportedOIDCAuthModes(), authmodes.Password) + // Session is for login. Check which auth modes are available. + // The order of the modes is important, because authd picks the first supported one. + // Password authentication should be the first option if available, to avoid performing device authentication + // when it's not necessary. + modes := append([]string{authmodes.Password}, b.provider.SupportedOIDCAuthModes()...) for _, mode := range modes { if authModeIsAvailable(session, mode) { availableModes = append(availableModes, mode) diff --git a/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_password_and_device_auth_qr_if_token_exists b/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_password_and_device_auth_qr_if_token_exists index 309786925e..6327db6363 100644 --- a/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_password_and_device_auth_qr_if_token_exists +++ b/internal/broker/testdata/golden/TestGetAuthenticationModes/Get_password_and_device_auth_qr_if_token_exists @@ -1,4 +1,4 @@ -- id: device_auth_qr - label: Device Authentication - id: password label: Local Password Authentication +- id: device_auth_qr + label: Device Authentication From 664c3ca43e2fdef94a203b3b20fb6d2f9e26cf8f Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 26 Feb 2025 16:47:27 +0100 Subject: [PATCH 0385/1670] TestGetAuthenticationModes: Explicitly define the first expected mode Should avoid mistakes like the one which caused #812. --- internal/broker/broker_test.go | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 126844d034..40c6afb556 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -170,21 +170,22 @@ func TestGetAuthenticationModes(t *testing.T) { unavailableProvider bool deviceAuthUnsupported bool - wantErr bool + wantErr bool + wantFirstMode string }{ // Authentication session - "Get_device_auth_qr_if_there_is_no_token": {}, - "Get_password_and_device_auth_qr_if_token_exists": {tokenExists: true}, - "Get_newpassword_if_next_auth_mode_is_newpassword": {nextAuthMode: authmodes.NewPassword}, - "Get_device_auth_qr_if_next_auth_mode_is_device_qr": {nextAuthMode: authmodes.DeviceQr}, + "Get_device_auth_qr_if_there_is_no_token": {wantFirstMode: authmodes.DeviceQr}, + "Get_password_and_device_auth_qr_if_token_exists": {tokenExists: true, wantFirstMode: authmodes.Password}, + "Get_newpassword_if_next_auth_mode_is_newpassword": {nextAuthMode: authmodes.NewPassword, wantFirstMode: authmodes.NewPassword}, + "Get_device_auth_qr_if_next_auth_mode_is_device_qr": {nextAuthMode: authmodes.DeviceQr, wantFirstMode: authmodes.DeviceQr}, - "Get_only_password_if_token_exists_and_provider_is_not_available": {tokenExists: true, providerAddress: "127.0.0.1:31310", unavailableProvider: true}, - "Get_only_password_if_token_exists_and_provider_does_not_support_device_auth_qr": {tokenExists: true, providerAddress: "127.0.0.1:31311", deviceAuthUnsupported: true}, + "Get_only_password_if_token_exists_and_provider_is_not_available": {tokenExists: true, providerAddress: "127.0.0.1:31310", unavailableProvider: true, wantFirstMode: authmodes.Password}, + "Get_only_password_if_token_exists_and_provider_does_not_support_device_auth_qr": {tokenExists: true, providerAddress: "127.0.0.1:31311", deviceAuthUnsupported: true, wantFirstMode: authmodes.Password}, // Change password session - "Get_only_password_if_token_exists_and_session_is_for_changing_password": {sessionMode: sessionmode.ChangePassword, tokenExists: true}, - "Get_newpassword_if_session_is_for changing_password_and_next_auth_mode_is_newpassword": {sessionMode: sessionmode.ChangePassword, tokenExists: true, nextAuthMode: authmodes.NewPassword}, - "Get_only_password_if_token_exists_and_session_mode_is_the_old_passwd_value": {sessionMode: sessionmode.ChangePasswordOld, tokenExists: true}, + "Get_only_password_if_token_exists_and_session_is_for_changing_password": {sessionMode: sessionmode.ChangePassword, tokenExists: true, wantFirstMode: authmodes.Password}, + "Get_newpassword_if_session_is_for changing_password_and_next_auth_mode_is_newpassword": {sessionMode: sessionmode.ChangePassword, tokenExists: true, nextAuthMode: authmodes.NewPassword, wantFirstMode: authmodes.NewPassword}, + "Get_only_password_if_token_exists_and_session_mode_is_the_old_passwd_value": {sessionMode: sessionmode.ChangePasswordOld, tokenExists: true, wantFirstMode: authmodes.Password}, "Error_if_there_is_no_session": {sessionID: "-", wantErr: true}, @@ -256,6 +257,10 @@ func TestGetAuthenticationModes(t *testing.T) { } require.NoError(t, err, "GetAuthenticationModes should not have returned an error") + if tc.wantFirstMode != "" { + require.Equal(t, tc.wantFirstMode, got[0]["id"], "First mode should be the expected one") + } + golden.CheckOrUpdateYAML(t, got) }) } From 45cf8e24ea31ba53c99e082b92056d2c104d579f Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Wed, 26 Feb 2025 07:13:05 -0400 Subject: [PATCH 0386/1670] Rename noprovider pkg to genericprovider --- internal/providers/default.go | 4 +- .../genericprovider.go} | 38 +++++++++---------- internal/providers/google/google.go | 6 +-- internal/testutils/provider.go | 8 ++-- 4 files changed, 28 insertions(+), 28 deletions(-) rename internal/providers/{noprovider/noprovider.go => genericprovider/genericprovider.go} (67%) diff --git a/internal/providers/default.go b/internal/providers/default.go index 68ce946ee6..92f5b358a6 100644 --- a/internal/providers/default.go +++ b/internal/providers/default.go @@ -3,10 +3,10 @@ package providers import ( - "github.com/ubuntu/authd-oidc-brokers/internal/providers/noprovider" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/genericprovider" ) // CurrentProvider returns a generic oidc provider implementation. func CurrentProvider() Provider { - return noprovider.New() + return genericprovider.New() } diff --git a/internal/providers/noprovider/noprovider.go b/internal/providers/genericprovider/genericprovider.go similarity index 67% rename from internal/providers/noprovider/noprovider.go rename to internal/providers/genericprovider/genericprovider.go index 9d07c8f08b..388a39ac80 100644 --- a/internal/providers/noprovider/noprovider.go +++ b/internal/providers/genericprovider/genericprovider.go @@ -1,5 +1,5 @@ -// Package noprovider is the generic oidc extension. -package noprovider +// Package genericprovider is the generic oidc extension. +package genericprovider import ( "context" @@ -12,42 +12,42 @@ import ( "golang.org/x/oauth2" ) -// NoProvider is a generic OIDC provider. -type NoProvider struct{} +// GenericProvider is a generic OIDC provider. +type GenericProvider struct{} -// New returns a new NoProvider. -func New() NoProvider { - return NoProvider{} +// New returns a new GenericProvider. +func New() GenericProvider { + return GenericProvider{} } // CheckTokenScopes should check the token scopes, but we're not sure // if there is a generic way to do this, so for now it's a no-op. -func (p NoProvider) CheckTokenScopes(token *oauth2.Token) error { +func (p GenericProvider) CheckTokenScopes(token *oauth2.Token) error { return nil } // AdditionalScopes returns the generic scopes required by the provider. -func (p NoProvider) AdditionalScopes() []string { +func (p GenericProvider) AdditionalScopes() []string { return []string{oidc.ScopeOfflineAccess} } // AuthOptions is a no-op when no specific provider is in use. -func (p NoProvider) AuthOptions() []oauth2.AuthCodeOption { +func (p GenericProvider) AuthOptions() []oauth2.AuthCodeOption { return []oauth2.AuthCodeOption{} } // GetExtraFields returns the extra fields of the token which should be stored persistently. -func (p NoProvider) GetExtraFields(token *oauth2.Token) map[string]interface{} { +func (p GenericProvider) GetExtraFields(token *oauth2.Token) map[string]interface{} { return nil } // GetMetadata is a no-op when no specific provider is in use. -func (p NoProvider) GetMetadata(provider *oidc.Provider) (map[string]interface{}, error) { +func (p GenericProvider) GetMetadata(provider *oidc.Provider) (map[string]interface{}, error) { return nil, nil } // GetUserInfo is a no-op when no specific provider is in use. -func (p NoProvider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken, providerMetadata map[string]interface{}) (info.User, error) { +func (p GenericProvider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken, providerMetadata map[string]interface{}) (info.User, error) { userClaims, err := p.userClaims(idToken) if err != nil { return info.User{}, err @@ -69,12 +69,12 @@ func (p NoProvider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, } // NormalizeUsername parses a username into a normalized version. -func (p NoProvider) NormalizeUsername(username string) string { +func (p GenericProvider) NormalizeUsername(username string) string { return username } // VerifyUsername checks if the requested username matches the authenticated user. -func (p NoProvider) VerifyUsername(requestedUsername, username string) error { +func (p GenericProvider) VerifyUsername(requestedUsername, username string) error { if p.NormalizeUsername(requestedUsername) != p.NormalizeUsername(username) { return fmt.Errorf("requested username %q does not match the authenticated user %q", requestedUsername, username) } @@ -82,7 +82,7 @@ func (p NoProvider) VerifyUsername(requestedUsername, username string) error { } // SupportedOIDCAuthModes returns the OIDC authentication modes supported by the provider. -func (p NoProvider) SupportedOIDCAuthModes() []string { +func (p GenericProvider) SupportedOIDCAuthModes() []string { return []string{authmodes.Device, authmodes.DeviceQr} } @@ -95,7 +95,7 @@ type claims struct { } // userClaims returns the user claims parsed from the ID token. -func (p NoProvider) userClaims(idToken *oidc.IDToken) (claims, error) { +func (p GenericProvider) userClaims(idToken *oidc.IDToken) (claims, error) { var userClaims claims if err := idToken.Claims(&userClaims); err != nil { return claims{}, fmt.Errorf("failed to get ID token claims: %v", err) @@ -104,12 +104,12 @@ func (p NoProvider) userClaims(idToken *oidc.IDToken) (claims, error) { } // getGroups is a no-op when no specific provider is in use. -func (p NoProvider) getGroups(_ *oauth2.Token) ([]info.Group, error) { +func (p GenericProvider) getGroups(_ *oauth2.Token) ([]info.Group, error) { return nil, nil } // IsTokenExpiredError returns true if the reason for the error is that the refresh token is expired. -func (p NoProvider) IsTokenExpiredError(err oauth2.RetrieveError) bool { +func (p GenericProvider) IsTokenExpiredError(err oauth2.RetrieveError) bool { // TODO: This is an msentraid specific error code and description. // Change it to the ones from Google once we know them. return err.ErrorCode == "invalid_grant" && strings.HasPrefix(err.ErrorDescription, "AADSTS50173:") diff --git a/internal/providers/google/google.go b/internal/providers/google/google.go index d3c2ac4f48..fe8badc69b 100644 --- a/internal/providers/google/google.go +++ b/internal/providers/google/google.go @@ -2,18 +2,18 @@ package google import ( - "github.com/ubuntu/authd-oidc-brokers/internal/providers/noprovider" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/genericprovider" ) // Provider is the google provider implementation. type Provider struct { - noprovider.NoProvider + genericprovider.GenericProvider } // New returns a new GoogleProvider. func New() Provider { return Provider{ - NoProvider: noprovider.New(), + GenericProvider: genericprovider.New(), } } diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index c660307024..d9a3900b34 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -22,8 +22,8 @@ import ( "github.com/go-jose/go-jose/v4" "github.com/golang-jwt/jwt/v5" "github.com/ubuntu/authd-oidc-brokers/internal/consts" + "github.com/ubuntu/authd-oidc-brokers/internal/providers/genericprovider" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" - "github.com/ubuntu/authd-oidc-brokers/internal/providers/noprovider" "github.com/ubuntu/authd/log" "golang.org/x/oauth2" ) @@ -348,7 +348,7 @@ func ExpiryDeviceAuthHandler() EndpointHandler { // MockProvider is a mock that implements the Provider interface. type MockProvider struct { - noprovider.NoProvider + genericprovider.GenericProvider Scopes []string Options []oauth2.AuthCodeOption GetGroupsFunc func() ([]info.Group, error) @@ -385,7 +385,7 @@ func (p *MockProvider) AdditionalScopes() []string { if p.Scopes != nil { return p.Scopes } - return p.NoProvider.AdditionalScopes() + return p.GenericProvider.AdditionalScopes() } // AuthOptions returns the additional options required by the provider. @@ -393,7 +393,7 @@ func (p *MockProvider) AuthOptions() []oauth2.AuthCodeOption { if p.Options != nil { return p.Options } - return p.NoProvider.AuthOptions() + return p.GenericProvider.AuthOptions() } // NormalizeUsername parses a username into a normalized version. From ba336fccae2b3c7dd66786ac88b7356d1499af70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 08:46:20 +0000 Subject: [PATCH 0387/1670] deps(go-tools): bump github.com/golangci/golangci-lint in /tools Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.64.5 to 1.64.6. - [Release notes](https://github.com/golangci/golangci-lint/releases) - [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md) - [Commits](https://github.com/golangci/golangci-lint/compare/v1.64.5...v1.64.6) --- updated-dependencies: - dependency-name: github.com/golangci/golangci-lint dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- tools/go.mod | 32 +++++++++++++------------- tools/go.sum | 65 ++++++++++++++++++++++++++-------------------------- 2 files changed, 49 insertions(+), 48 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index 03b6f161b1..186ec9225a 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -5,14 +5,14 @@ go 1.23.0 toolchain go1.23.6 require ( - github.com/golangci/golangci-lint v1.64.5 + github.com/golangci/golangci-lint v1.64.6 golang.org/x/mod v0.23.0 ) require ( - 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect + 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect 4d63.com/gochecknoglobals v0.2.2 // indirect - github.com/4meepo/tagalign v1.4.1 // indirect + github.com/4meepo/tagalign v1.4.2 // indirect github.com/Abirdcfly/dupword v0.1.3 // indirect github.com/Antonboom/errname v1.0.0 // indirect github.com/Antonboom/nilnil v1.0.1 // indirect @@ -20,7 +20,7 @@ require ( github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect github.com/Crocmagnon/fatcontext v0.7.1 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect - github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect + github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect github.com/alecthomas/go-check-sumtype v0.3.1 // indirect @@ -38,7 +38,7 @@ require ( github.com/breml/errchkjson v0.4.0 // indirect github.com/butuzov/ireturn v0.3.1 // indirect github.com/butuzov/mirror v1.3.0 // indirect - github.com/catenacyber/perfsprint v0.8.1 // indirect + github.com/catenacyber/perfsprint v0.8.2 // indirect github.com/ccojocar/zxcvbn-go v1.0.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charithe/durationcheck v0.0.10 // indirect @@ -75,10 +75,10 @@ require ( github.com/golangci/plugin-module-register v0.1.1 // indirect github.com/golangci/revgrep v0.8.0 // indirect github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect - github.com/google/go-cmp v0.6.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/gordonklaus/ineffassign v0.1.0 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect - github.com/gostaticanalysis/comment v1.4.2 // indirect + github.com/gostaticanalysis/comment v1.5.0 // indirect github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect github.com/gostaticanalysis/nilerr v0.1.1 // indirect github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect @@ -92,12 +92,12 @@ require ( github.com/jjti/go-spancheck v0.6.4 // indirect github.com/julz/importas v0.2.0 // indirect github.com/karamaru-alpha/copyloopvar v1.2.1 // indirect - github.com/kisielk/errcheck v1.8.0 // indirect - github.com/kkHAIKE/contextcheck v1.1.5 // indirect + github.com/kisielk/errcheck v1.9.0 // indirect + github.com/kkHAIKE/contextcheck v1.1.6 // indirect github.com/kulti/thelper v0.6.3 // indirect github.com/kunwardeep/paralleltest v1.0.10 // indirect github.com/lasiar/canonicalheader v1.1.2 // indirect - github.com/ldez/exptostd v0.4.1 // indirect + github.com/ldez/exptostd v0.4.2 // indirect github.com/ldez/gomoddirectives v0.6.1 // indirect github.com/ldez/grignotin v0.9.0 // indirect github.com/ldez/tagliatelle v0.7.1 // indirect @@ -112,14 +112,14 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mgechev/revive v1.6.1 // indirect + github.com/mgechev/revive v1.7.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moricho/tparallel v0.3.2 // indirect github.com/nakabonne/nestif v0.3.1 // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect - github.com/nunnatsa/ginkgolinter v0.19.0 // indirect + github.com/nunnatsa/ginkgolinter v0.19.1 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect @@ -136,7 +136,7 @@ require ( github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect github.com/raeperd/recvcheck v0.2.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rogpeppe/go-internal v1.13.1 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/ryancurrah/gomodguard v1.3.5 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect @@ -151,7 +151,7 @@ require ( github.com/sourcegraph/go-diff v0.7.0 // indirect github.com/spf13/afero v1.12.0 // indirect github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/cobra v1.8.1 // indirect + github.com/spf13/cobra v1.9.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/spf13/viper v1.12.0 // indirect @@ -160,8 +160,8 @@ require ( github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/testify v1.10.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect - github.com/tdakkota/asciicheck v0.4.0 // indirect - github.com/tetafro/godot v1.4.20 // indirect + github.com/tdakkota/asciicheck v0.4.1 // indirect + github.com/tetafro/godot v1.5.0 // indirect github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 // indirect github.com/timonwong/loggercheck v0.10.1 // indirect github.com/tomarrell/wrapcheck/v2 v2.10.0 // indirect diff --git a/tools/go.sum b/tools/go.sum index ad7fb72fe8..ee7fce45f5 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -1,5 +1,5 @@ -4d63.com/gocheckcompilerdirectives v1.2.1 h1:AHcMYuw56NPjq/2y615IGg2kYkBdTvOaojYCBcRE7MA= -4d63.com/gocheckcompilerdirectives v1.2.1/go.mod h1:yjDJSxmDTtIHHCqX0ufRYZDL6vQtMG7tJdKVeWwsqvs= +4d63.com/gocheckcompilerdirectives v1.3.0 h1:Ew5y5CtcAAQeTVKUVFrE7EwHMrTO6BggtEj8BZSjZ3A= +4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY= 4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= 4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= @@ -35,8 +35,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/4meepo/tagalign v1.4.1 h1:GYTu2FaPGOGb/xJalcqHeD4il5BiCywyEYZOA55P6J4= -github.com/4meepo/tagalign v1.4.1/go.mod h1:2H9Yu6sZ67hmuraFgfZkNcg5Py9Ch/Om9l2K/2W1qS4= +github.com/4meepo/tagalign v1.4.2 h1:0hcLHPGMjDyM1gHG58cS73aQF8J4TdVR96TZViorO9E= +github.com/4meepo/tagalign v1.4.2/go.mod h1:+p4aMyFM+ra7nb41CnFG6aSDXqRxU/w1VQqScKqDARI= github.com/Abirdcfly/dupword v0.1.3 h1:9Pa1NuAsZvpFPi9Pqkd93I7LIYRURj+A//dFd5tgBeE= github.com/Abirdcfly/dupword v0.1.3/go.mod h1:8VbB2t7e10KRNdwTVoxdBaxla6avbhGzb8sCTygUMhw= github.com/Antonboom/errname v1.0.0 h1:oJOOWR07vS1kRusl6YRSlat7HFnb3mSfMl6sDMRoTBA= @@ -53,8 +53,8 @@ github.com/Crocmagnon/fatcontext v0.7.1 h1:SC/VIbRRZQeQWj/TcQBS6JmrXcfA+BU4OGSVU github.com/Crocmagnon/fatcontext v0.7.1/go.mod h1:1wMvv3NXEBJucFGfwOJBxSVWcoIO6emV215SMkW9MFU= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 h1:/fTUt5vmbkAcMBt4YQiuC23cV0kEsN1MVMNqeOW43cU= -github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0/go.mod h1:ONJg5sxcbsdQQ4pOW8TGdTidT2TMAUy/2Xhr8mrYaao= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 h1:Sz1JIXEcSfhz7fUi7xHnhpIE0thVASYjvosApmHuD2k= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1/go.mod h1:n/LSCXNuIYqVfBlVXyHfMQkZDdp1/mmxfSjADd3z1Zg= github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= @@ -102,8 +102,8 @@ github.com/butuzov/ireturn v0.3.1 h1:mFgbEI6m+9W8oP/oDdfA34dLisRFCj2G6o/yiI1yZrY github.com/butuzov/ireturn v0.3.1/go.mod h1:ZfRp+E7eJLC0NQmk1Nrm1LOrn/gQlOykv+cVPdiXH5M= github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc= github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= -github.com/catenacyber/perfsprint v0.8.1 h1:bGOHuzHe0IkoGeY831RW4aSlt1lPRd3WRAScSWOaV7E= -github.com/catenacyber/perfsprint v0.8.1/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= +github.com/catenacyber/perfsprint v0.8.2 h1:+o9zVmCSVa7M4MvabsWvESEhpsMkhfE7k0sHNGL95yw= +github.com/catenacyber/perfsprint v0.8.2/go.mod h1:q//VWC2fWbcdSLEY1R3l8n0zQCDPdE4IjZwyY1HMunM= github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -122,7 +122,7 @@ github.com/ckaznocha/intrange v0.3.0 h1:VqnxtK32pxgkhJgYQEeOArVidIPg+ahLP7WBOXZd github.com/ckaznocha/intrange v0.3.0/go.mod h1:+I/o2d2A1FBHgGELbGxzIcyd3/9l9DuwjM8FsbSS3Lo= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= github.com/daixiang0/gci v0.13.5 h1:kThgmH1yBmZSBCh1EJVxQ7JsHpm5Oms0AMed/0LaH4c= @@ -235,8 +235,8 @@ github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUP github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s= github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE= github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY= -github.com/golangci/golangci-lint v1.64.5 h1:5omC86XFBKXZgCrVdUWU+WNHKd+CWCxNx717KXnzKZY= -github.com/golangci/golangci-lint v1.64.5/go.mod h1:WZnwq8TF0z61h3jLQ7Sk5trcP7b3kUFxLD6l1ivtdvU= +github.com/golangci/golangci-lint v1.64.6 h1:jOLaQN41IV7bMzXuNC4UnQGll7N1xY6eFDXkXEPGKAs= +github.com/golangci/golangci-lint v1.64.6/go.mod h1:Wz9q+6EVuqGQ94GQ96RB2mjpcZYTOGhBhbt4O7REPu4= github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= @@ -259,8 +259,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -281,8 +281,9 @@ github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= -github.com/gostaticanalysis/comment v1.4.2 h1:hlnx5+S2fY9Zo9ePo4AhgYsYHbM2+eAv8m/s1JiCd6Q= github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= +github.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8= +github.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc= github.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk= github.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY= github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk= @@ -327,11 +328,11 @@ github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ= github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY= github.com/karamaru-alpha/copyloopvar v1.2.1 h1:wmZaZYIjnJ0b5UoKDjUHrikcV0zuPyyxI4SVplLd2CI= github.com/karamaru-alpha/copyloopvar v1.2.1/go.mod h1:nFmMlFNlClC2BPvNaHMdkirmTJxVCY0lhxBtlfOypMM= -github.com/kisielk/errcheck v1.8.0 h1:ZX/URYa7ilESY19ik/vBmCn6zdGQLxACwjAcWbHlYlg= -github.com/kisielk/errcheck v1.8.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= +github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M= +github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kkHAIKE/contextcheck v1.1.5 h1:CdnJh63tcDe53vG+RebdpdXJTc9atMgGqdx8LXxiilg= -github.com/kkHAIKE/contextcheck v1.1.5/go.mod h1:O930cpht4xb1YQpK+1+AgoM3mFsvxr7uyFptcnWTYUA= +github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE= +github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -348,8 +349,8 @@ github.com/kunwardeep/paralleltest v1.0.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCT github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= -github.com/ldez/exptostd v0.4.1 h1:DIollgQ3LWZMp3HJbSXsdE2giJxMfjyHj3eX4oiD6JU= -github.com/ldez/exptostd v0.4.1/go.mod h1:iZBRYaUmcW5jwCR3KROEZ1KivQQp6PHXbDPk9hqJKCQ= +github.com/ldez/exptostd v0.4.2 h1:l5pOzHBz8mFOlbcifTxzfyYbgEmoUqjxLFHZkjlbHXs= +github.com/ldez/exptostd v0.4.2/go.mod h1:iZBRYaUmcW5jwCR3KROEZ1KivQQp6PHXbDPk9hqJKCQ= github.com/ldez/gomoddirectives v0.6.1 h1:Z+PxGAY+217f/bSGjNZr/b2KTXcyYLgiWI6geMBN2Qc= github.com/ldez/gomoddirectives v0.6.1/go.mod h1:cVBiu3AHR9V31em9u2kwfMKD43ayN5/XDgr+cdaFaKs= github.com/ldez/grignotin v0.9.0 h1:MgOEmjZIVNn6p5wPaGp/0OKWyvq42KnzAt/DAb8O4Ow= @@ -381,8 +382,8 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mgechev/revive v1.6.1 h1:ncK0ZCMWtb8GXwVAmk+IeWF2ULIDsvRxSRfg5sTwQ2w= -github.com/mgechev/revive v1.6.1/go.mod h1:/2tfHWVO8UQi/hqJsIYNEKELi+DJy/e+PQpLgTB1v88= +github.com/mgechev/revive v1.7.0 h1:JyeQ4yO5K8aZhIKf5rec56u0376h8AlKNQEmjfkjKlY= +github.com/mgechev/revive v1.7.0/go.mod h1:qZnwcNhoguE58dfi96IJeSTPeZQejNeoMQLUZGi4SW4= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -402,8 +403,8 @@ github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhK github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/nunnatsa/ginkgolinter v0.19.0 h1:CnHRFAeBS3LdLI9h+Jidbcc5KH71GKOmaBZQk8Srnto= -github.com/nunnatsa/ginkgolinter v0.19.0/go.mod h1:jkQ3naZDmxaZMXPWaS9rblH+i+GWXQCaS/JFIWcOH2s= +github.com/nunnatsa/ginkgolinter v0.19.1 h1:mjwbOlDQxZi9Cal+KfbEJTCz327OLNfwNvoZ70NJ+c4= +github.com/nunnatsa/ginkgolinter v0.19.1/go.mod h1:jkQ3naZDmxaZMXPWaS9rblH+i+GWXQCaS/JFIWcOH2s= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= @@ -469,8 +470,8 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.3.5 h1:cShyguSwUEeC0jS7ylOiG/idnd1TpJ1LfHGpV3oJmPU= github.com/ryancurrah/gomodguard v1.3.5/go.mod h1:MXlEPQRxgfPQa62O8wzK3Ozbkv9Rkqr+wKjSxTdsNJE= @@ -505,8 +506,8 @@ github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -536,14 +537,14 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/tdakkota/asciicheck v0.4.0 h1:VZ13Itw4k1i7d+dpDSNS8Op645XgGHpkCEh/WHicgWw= -github.com/tdakkota/asciicheck v0.4.0/go.mod h1:0k7M3rCfRXb0Z6bwgvkEIMleKH3kXNz9UqJ9Xuqopr8= +github.com/tdakkota/asciicheck v0.4.1 h1:bm0tbcmi0jezRA2b5kg4ozmMuGAFotKI3RZfrhfovg8= +github.com/tdakkota/asciicheck v0.4.1/go.mod h1:0k7M3rCfRXb0Z6bwgvkEIMleKH3kXNz9UqJ9Xuqopr8= github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= -github.com/tetafro/godot v1.4.20 h1:z/p8Ek55UdNvzt4TFn2zx2KscpW4rWqcnUrdmvWJj7E= -github.com/tetafro/godot v1.4.20/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= +github.com/tetafro/godot v1.5.0 h1:aNwfVI4I3+gdxjMgYPus9eHmoBeJIbnajOyqZYStzuw= +github.com/tetafro/godot v1.5.0/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 h1:y4mJRFlM6fUyPhoXuFg/Yu02fg/nIPFMOY8tOqppoFg= github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= github.com/timonwong/loggercheck v0.10.1 h1:uVZYClxQFpw55eh+PIoqM7uAOHMrhVcDoWDery9R8Lg= From 71ad7d762c2d890e8fd1e190a32f83e89c62d990 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 09:05:13 +0000 Subject: [PATCH 0388/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.61.0 to 1.62.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.61.0...v1.62.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 71ee98b60a..399f3305e3 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.61.0 + github.com/microsoftgraph/msgraph-sdk-go v1.62.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 diff --git a/go.sum b/go.sum index 784f4a26ea..412cefc487 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.0.0 h1:3O5sb5Zj+moLBiJy github.com/microsoft/kiota-serialization-multipart-go v1.0.0/go.mod h1:yauLeBTpANk4L03XD985akNysG24SnRJGaveZf+p4so= github.com/microsoft/kiota-serialization-text-go v1.0.0 h1:XOaRhAXy+g8ZVpcq7x7a0jlETWnWrEum0RhmbYrTFnA= github.com/microsoft/kiota-serialization-text-go v1.0.0/go.mod h1:sM1/C6ecnQ7IquQOGUrUldaO5wj+9+v7G2W3sQ3fy6M= -github.com/microsoftgraph/msgraph-sdk-go v1.61.0 h1:/dqZXI7VAHTAeddhEfEPLf1EXszaDAkLcC9dkOARPxI= -github.com/microsoftgraph/msgraph-sdk-go v1.61.0/go.mod h1:2dZJTO/7S+UmfwKlPQg2vczpW7awcIEj1ZCgfZIf0V4= +github.com/microsoftgraph/msgraph-sdk-go v1.62.0 h1:esS0oR36BXzMUFw3hr7U+BMKWfidOVGMbvDXe9TTcsc= +github.com/microsoftgraph/msgraph-sdk-go v1.62.0/go.mod h1:2dZJTO/7S+UmfwKlPQg2vczpW7awcIEj1ZCgfZIf0V4= github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 h1:P1wpmn3xxfPMFJHg+PJPcusErfRkl63h6OdAnpDbkS8= github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1/go.mod h1:vFmWQGWyLlhxCESNLv61vlE4qesBU+eWmEVH7DJSESA= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= From c6eb923de9e71f9e8a0e16e51593547b0312085e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 09:05:29 +0000 Subject: [PATCH 0389/1670] deps(go): bump golang.org/x/oauth2 from 0.26.0 to 0.27.0 Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.26.0 to 0.27.0. - [Commits](https://github.com/golang/oauth2/compare/v0.26.0...v0.27.0) --- updated-dependencies: - dependency-name: golang.org/x/oauth2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 71ee98b60a..3a79d178a8 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 golang.org/x/crypto v0.34.0 - golang.org/x/oauth2 v0.26.0 + golang.org/x/oauth2 v0.27.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 784f4a26ea..4220a902d9 100644 --- a/go.sum +++ b/go.sum @@ -148,8 +148,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= -golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= From 04262f1eb3a08cce048b34143415919961e00a74 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Mar 2025 08:08:55 +0000 Subject: [PATCH 0390/1670] deps(go-tools): bump golang.org/x/mod from 0.23.0 to 0.24.0 in /tools Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.23.0 to 0.24.0. - [Commits](https://github.com/golang/mod/compare/v0.23.0...v0.24.0) --- updated-dependencies: - dependency-name: golang.org/x/mod dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- tools/go.mod | 2 +- tools/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index 186ec9225a..beefaf6f41 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -6,7 +6,7 @@ toolchain go1.23.6 require ( github.com/golangci/golangci-lint v1.64.6 - golang.org/x/mod v0.23.0 + golang.org/x/mod v0.24.0 ) require ( diff --git a/tools/go.sum b/tools/go.sum index ee7fce45f5..3a0abc4a82 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -653,8 +653,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= -golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= From 2148100a04f4ad32a5197f7d0508b1f04c607b35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Mar 2025 08:12:21 +0000 Subject: [PATCH 0391/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.62.0 to 1.63.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.62.0...v1.63.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 71b81718d9..1ba3cac1ba 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.62.0 + github.com/microsoftgraph/msgraph-sdk-go v1.63.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 diff --git a/go.sum b/go.sum index 4e47eb0724..16b0fe62e7 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.0.0 h1:3O5sb5Zj+moLBiJy github.com/microsoft/kiota-serialization-multipart-go v1.0.0/go.mod h1:yauLeBTpANk4L03XD985akNysG24SnRJGaveZf+p4so= github.com/microsoft/kiota-serialization-text-go v1.0.0 h1:XOaRhAXy+g8ZVpcq7x7a0jlETWnWrEum0RhmbYrTFnA= github.com/microsoft/kiota-serialization-text-go v1.0.0/go.mod h1:sM1/C6ecnQ7IquQOGUrUldaO5wj+9+v7G2W3sQ3fy6M= -github.com/microsoftgraph/msgraph-sdk-go v1.62.0 h1:esS0oR36BXzMUFw3hr7U+BMKWfidOVGMbvDXe9TTcsc= -github.com/microsoftgraph/msgraph-sdk-go v1.62.0/go.mod h1:2dZJTO/7S+UmfwKlPQg2vczpW7awcIEj1ZCgfZIf0V4= +github.com/microsoftgraph/msgraph-sdk-go v1.63.0 h1:xoU48B+tfXSV2SSyFJ2nnwGYJwJfGaQjkpH+t9OEB7c= +github.com/microsoftgraph/msgraph-sdk-go v1.63.0/go.mod h1:2dZJTO/7S+UmfwKlPQg2vczpW7awcIEj1ZCgfZIf0V4= github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 h1:P1wpmn3xxfPMFJHg+PJPcusErfRkl63h6OdAnpDbkS8= github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1/go.mod h1:vFmWQGWyLlhxCESNLv61vlE4qesBU+eWmEVH7DJSESA= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= From 2f7294a942df2d0304d0c8a7b335fe6552db6c0a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Mar 2025 08:12:32 +0000 Subject: [PATCH 0392/1670] deps(go): bump golang.org/x/crypto from 0.34.0 to 0.36.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.34.0 to 0.36.0. - [Commits](https://github.com/golang/crypto/compare/v0.34.0...v0.36.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 71b81718d9..650fb42cef 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/ubuntu/authd v0.4.2-0.20250203155723-a499f298f969 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 - golang.org/x/crypto v0.34.0 + golang.org/x/crypto v0.36.0 golang.org/x/oauth2 v0.27.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 @@ -69,7 +69,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/net v0.34.0 // indirect - golang.org/x/sync v0.11.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/text v0.22.0 // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect ) diff --git a/go.sum b/go.sum index 4e47eb0724..34018269f6 100644 --- a/go.sum +++ b/go.sum @@ -138,8 +138,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.34.0 h1:+/C6tk6rf/+t5DhUketUbD1aNGqiSX3j15Z6xuIDlBA= -golang.org/x/crypto v0.34.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -152,8 +152,8 @@ golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -162,16 +162,16 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From b1eac571eecce72200ada7b6f2e75489ee088ec0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Mar 2025 10:50:55 +0000 Subject: [PATCH 0393/1670] deps(go): bump golang.org/x/oauth2 from 0.27.0 to 0.28.0 Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.27.0 to 0.28.0. - [Commits](https://github.com/golang/oauth2/compare/v0.27.0...v0.28.0) --- updated-dependencies: - dependency-name: golang.org/x/oauth2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e9d4455239..75317e17d4 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 golang.org/x/crypto v0.36.0 - golang.org/x/oauth2 v0.27.0 + golang.org/x/oauth2 v0.28.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 097c8bd32b..7e85a2d3e4 100644 --- a/go.sum +++ b/go.sum @@ -148,8 +148,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= -golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= +golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= From 03ae6e1e617adee62c84de505308c6cad4a1e72e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 08:22:15 +0000 Subject: [PATCH 0394/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.63.0 to 1.65.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.63.0...v1.65.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 35 +++++++++++++-------------- go.sum | 74 ++++++++++++++++++++++++++++++---------------------------- 2 files changed, 56 insertions(+), 53 deletions(-) diff --git a/go.mod b/go.mod index 75317e17d4..23531e70a8 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/ubuntu/authd-oidc-brokers go 1.23.0 -toolchain go1.23.6 +toolchain go1.24.1 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 @@ -13,8 +13,8 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.63.0 - github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 + github.com/microsoftgraph/msgraph-sdk-go v1.65.0 + github.com/microsoftgraph/msgraph-sdk-go-core v1.3.0 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/spf13/cobra v1.9.1 @@ -31,11 +31,11 @@ require ( require ( github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect - github.com/cjlapao/common-go v0.0.39 // indirect + github.com/cjlapao/common-go v0.0.41 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -44,13 +44,13 @@ require ( github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/microsoft/kiota-abstractions-go v1.8.1 // indirect - github.com/microsoft/kiota-authentication-azure-go v1.1.0 // indirect - github.com/microsoft/kiota-http-go v1.4.4 // indirect - github.com/microsoft/kiota-serialization-form-go v1.0.0 // indirect - github.com/microsoft/kiota-serialization-json-go v1.0.9 // indirect - github.com/microsoft/kiota-serialization-multipart-go v1.0.0 // indirect - github.com/microsoft/kiota-serialization-text-go v1.0.0 // indirect + github.com/microsoft/kiota-abstractions-go v1.9.0 // indirect + github.com/microsoft/kiota-authentication-azure-go v1.2.0 // indirect + github.com/microsoft/kiota-http-go v1.5.0 // indirect + github.com/microsoft/kiota-serialization-form-go v1.1.0 // indirect + github.com/microsoft/kiota-serialization-json-go v1.1.0 // indirect + github.com/microsoft/kiota-serialization-multipart-go v1.1.0 // indirect + github.com/microsoft/kiota-serialization-text-go v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/otiai10/mint v1.6.3 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect @@ -61,14 +61,15 @@ require ( github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.6 // indirect - github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.1 // indirect + github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3 // indirect github.com/subosito/gotenv v1.6.0 // indirect - go.opentelemetry.io/otel v1.24.0 // indirect - go.opentelemetry.io/otel/metric v1.24.0 // indirect - go.opentelemetry.io/otel/trace v1.24.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/otel/metric v1.35.0 // indirect + go.opentelemetry.io/otel/trace v1.35.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect - golang.org/x/net v0.34.0 // indirect + golang.org/x/net v0.37.0 // indirect golang.org/x/sync v0.12.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.23.0 // indirect diff --git a/go.sum b/go.sum index 7e85a2d3e4..4a23ab196a 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WW github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= -github.com/cjlapao/common-go v0.0.39 h1:bAAUrj2B9v0kMzbAOhzjSmiyDy+rd56r2sy7oEiQLlA= -github.com/cjlapao/common-go v0.0.39/go.mod h1:M3dzazLjTjEtZJbbxoA5ZDiGCiHmpwqW9l4UWaddwOA= +github.com/cjlapao/common-go v0.0.41 h1:j30UKZJWVWIllJ66x3EOslJvIk/VjkyenrhEcH64dGM= +github.com/cjlapao/common-go v0.0.41/go.mod h1:ao5wEp0hYMNehJiHoarSjc5dKK5wi4LvnwjXaC2SxUI= github.com/coreos/go-oidc/v3 v3.12.0 h1:sJk+8G2qq94rDI6ehZ71Bol3oUHy63qNYmkiSjrc/Jo= github.com/coreos/go-oidc/v3 v3.12.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= @@ -22,8 +22,8 @@ github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyT github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -31,8 +31,8 @@ github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -56,24 +56,24 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/microsoft/kiota-abstractions-go v1.8.1 h1:0gtK3KERmbKYm5AxJLZ8WPlNR9eACUGWuofFIa01PnA= -github.com/microsoft/kiota-abstractions-go v1.8.1/go.mod h1:YO2QCJyNM9wzvlgGLepw6s9XrPgNHODOYGVDCqQWdLI= -github.com/microsoft/kiota-authentication-azure-go v1.1.0 h1:HudH57Enel9zFQ4TEaJw6lMiyZ5RbBdrRHwdU0NP2RY= -github.com/microsoft/kiota-authentication-azure-go v1.1.0/go.mod h1:zfPFOiLdEqM77Hua5B/2vpcXrVaGqSWjHSRzlvAWEgc= -github.com/microsoft/kiota-http-go v1.4.4 h1:HM0KT/Q7o+JsGatFkkbTIqJL24Jzo5eMI5NNe9N4TQ4= -github.com/microsoft/kiota-http-go v1.4.4/go.mod h1:Kup5nMDD3a9sjdgRKHCqZWqtrv3FbprjcPaGjLR6FzM= -github.com/microsoft/kiota-serialization-form-go v1.0.0 h1:UNdrkMnLFqUCccQZerKjblsyVgifS11b3WCx+eFEsAI= -github.com/microsoft/kiota-serialization-form-go v1.0.0/go.mod h1:h4mQOO6KVTNciMF6azi1J9QB19ujSw3ULKcSNyXXOMA= -github.com/microsoft/kiota-serialization-json-go v1.0.9 h1:lJivec0G0tI6T8McBTnucyyYXczXytwcu1pt0UjWSBY= -github.com/microsoft/kiota-serialization-json-go v1.0.9/go.mod h1:AxrS/Gbmr8y/hIp2pJcpTup/2wCE8ED+VEXkf/9xKb4= -github.com/microsoft/kiota-serialization-multipart-go v1.0.0 h1:3O5sb5Zj+moLBiJympbXNaeV07K0d46IfuEd5v9+pBs= -github.com/microsoft/kiota-serialization-multipart-go v1.0.0/go.mod h1:yauLeBTpANk4L03XD985akNysG24SnRJGaveZf+p4so= -github.com/microsoft/kiota-serialization-text-go v1.0.0 h1:XOaRhAXy+g8ZVpcq7x7a0jlETWnWrEum0RhmbYrTFnA= -github.com/microsoft/kiota-serialization-text-go v1.0.0/go.mod h1:sM1/C6ecnQ7IquQOGUrUldaO5wj+9+v7G2W3sQ3fy6M= -github.com/microsoftgraph/msgraph-sdk-go v1.63.0 h1:xoU48B+tfXSV2SSyFJ2nnwGYJwJfGaQjkpH+t9OEB7c= -github.com/microsoftgraph/msgraph-sdk-go v1.63.0/go.mod h1:2dZJTO/7S+UmfwKlPQg2vczpW7awcIEj1ZCgfZIf0V4= -github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1 h1:P1wpmn3xxfPMFJHg+PJPcusErfRkl63h6OdAnpDbkS8= -github.com/microsoftgraph/msgraph-sdk-go-core v1.2.1/go.mod h1:vFmWQGWyLlhxCESNLv61vlE4qesBU+eWmEVH7DJSESA= +github.com/microsoft/kiota-abstractions-go v1.9.0 h1:kJ8ANzjVB5+BUmeX9ixe1UNwbDplqOWC0ftNKWKTzj4= +github.com/microsoft/kiota-abstractions-go v1.9.0/go.mod h1:dqP19EIArR8hdsWUxXZ5bKaItB4regdWVKBniP8yHpo= +github.com/microsoft/kiota-authentication-azure-go v1.2.0 h1:OaoS4T6jDpxtmSRzuMCAYwxVzFWJKREdX7KKYa6Z6OI= +github.com/microsoft/kiota-authentication-azure-go v1.2.0/go.mod h1:HYNxHxHXgzlDDv/QqEvpKk2QDZdpWIijVO9xAltMvU8= +github.com/microsoft/kiota-http-go v1.5.0 h1:Iz/Ur+xBNQKQxsi7WOvhwybuah2kS7ELMWOhVXev9hM= +github.com/microsoft/kiota-http-go v1.5.0/go.mod h1:6w+50/9KDeFUI6D0Z6JFQ6ea9LDxrZnWHl7zCsbrprc= +github.com/microsoft/kiota-serialization-form-go v1.1.0 h1:gCLxwnd8HNaswpBdCLODcz2oVEoQrXPU/o6/J3QS9Bs= +github.com/microsoft/kiota-serialization-form-go v1.1.0/go.mod h1:ZDmGk8u+1DoOZoPwiZ74jkRnVEe4GApEAi6K/+bHX3g= +github.com/microsoft/kiota-serialization-json-go v1.1.0 h1:C2XTRLfcyPvbYWMDx8xM3c1cRLGWcD3434mo2J6jNyo= +github.com/microsoft/kiota-serialization-json-go v1.1.0/go.mod h1:jMdF/jpb49pDur616ZoJAQt8ZxpqbCQRr+41g66Xw8Q= +github.com/microsoft/kiota-serialization-multipart-go v1.1.0 h1:rALPMWJU749Bb81QzOEmIf7ZvWy5StUmZUYL6/xFZsE= +github.com/microsoft/kiota-serialization-multipart-go v1.1.0/go.mod h1:GBDmHOLO3+DhBnMmJC7OCR2Jq05VJuYKSpx7jKNrGmE= +github.com/microsoft/kiota-serialization-text-go v1.1.0 h1:PfX+asvJW9KdkbC4MikRgekoRqtAclqkGm1/d2QTQWw= +github.com/microsoft/kiota-serialization-text-go v1.1.0/go.mod h1:UgXgpLZ3a98nVF8A1nuEXmkv1HTMhJ+bXHDM1yRaFuw= +github.com/microsoftgraph/msgraph-sdk-go v1.65.0 h1:Z5QWWMbZHV3RfTbYoZzX82zUQpJy3K7fupKeuKxYMro= +github.com/microsoftgraph/msgraph-sdk-go v1.65.0/go.mod h1:oF9nvRZxvf33VEspYTjDqT+dhm+sioT0fiyf0PRp2as= +github.com/microsoftgraph/msgraph-sdk-go-core v1.3.0 h1:2eHvehXGH0i6Ngsq/5LMAEHqspe6xfYVhFnSRfDq1Hg= +github.com/microsoftgraph/msgraph-sdk-go-core v1.3.0/go.mod h1:Y61FHnaNhoQGjf+bWCxH05RJeioZhbnADjkCkYt51wA= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= @@ -85,8 +85,8 @@ github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= @@ -106,8 +106,8 @@ github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= -github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.1 h1:/m2cTZHpqgofDsrwPqsASI6fSNMNhb+9EmUYtHEV2Uk= -github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.1/go.mod h1:Z5KcoM0YLC7INlNhEezeIZ0TZNYf7WSNO0Lvah4DSeQ= +github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3 h1:7hth9376EoQEd1hH4lAp3vnaLP2UMyxuMMghLKzDHyU= +github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3/go.mod h1:Z5KcoM0YLC7INlNhEezeIZ0TZNYf7WSNO0Lvah4DSeQ= github.com/stretchr/objx v0.1.0/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= @@ -128,12 +128,14 @@ github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5/go.mod h1:PUpwIgUu github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 h1:CA2dVorxvzdsGtszqhSjyvkrXxZi4bS52ZKvP0Ko634= github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47/go.mod h1:ZRhdDyx6YkKz/YiMWi0gS3uMCltgdaKz9IpkiNf/GRg= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= -go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= -go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= -go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= -go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= -go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -146,8 +148,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 12e65b4ad042c2b08466343950fc95eae29fa5e1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 09:21:48 +0000 Subject: [PATCH 0395/1670] deps(go-tools): bump github.com/golangci/golangci-lint in /tools Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.64.6 to 1.64.7. - [Release notes](https://github.com/golangci/golangci-lint/releases) - [Changelog](https://github.com/golangci/golangci-lint/blob/main/CHANGELOG.md) - [Commits](https://github.com/golangci/golangci-lint/compare/v1.64.6...v1.64.7) --- updated-dependencies: - dependency-name: github.com/golangci/golangci-lint dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- tools/go.mod | 20 ++++++++++---------- tools/go.sum | 40 ++++++++++++++++++++-------------------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index beefaf6f41..4092006420 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -2,10 +2,10 @@ module github.com/ubuntu/authd-oidc-brokers/tools go 1.23.0 -toolchain go1.23.6 +toolchain go1.24.1 require ( - github.com/golangci/golangci-lint v1.64.6 + github.com/golangci/golangci-lint v1.64.7 golang.org/x/mod v0.24.0 ) @@ -22,7 +22,7 @@ require ( github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect - github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect + github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect github.com/alecthomas/go-check-sumtype v0.3.1 // indirect github.com/alexkohler/nakedret/v2 v2.0.5 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect @@ -68,7 +68,7 @@ require ( github.com/gobwas/glob v0.2.3 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect + github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect github.com/golangci/go-printf-func-name v0.1.0 // indirect github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect github.com/golangci/misspell v0.6.0 // indirect @@ -143,7 +143,7 @@ require ( github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.28.0 // indirect - github.com/securego/gosec/v2 v2.22.1 // indirect + github.com/securego/gosec/v2 v2.22.2 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect github.com/sivchari/tenv v1.12.1 // indirect @@ -182,15 +182,15 @@ require ( go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect - golang.org/x/sync v0.11.0 // indirect - golang.org/x/sys v0.30.0 // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.22.0 // indirect - golang.org/x/tools v0.30.0 // indirect - google.golang.org/protobuf v1.36.4 // indirect + golang.org/x/tools v0.31.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - honnef.co/go/tools v0.6.0 // indirect + honnef.co/go/tools v0.6.1 // indirect mvdan.cc/gofumpt v0.7.0 // indirect mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect ) diff --git a/tools/go.sum b/tools/go.sum index 3a0abc4a82..07f08703c3 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -57,8 +57,8 @@ github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 h1:Sz1JIXEcSfhz7fUi7xHnh github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1/go.mod h1:n/LSCXNuIYqVfBlVXyHfMQkZDdp1/mmxfSjADd3z1Zg= github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= -github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ= +github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4= +github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo= github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU= @@ -229,14 +229,14 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS 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/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= -github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= +github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw= +github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E= github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU= github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s= github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE= github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY= -github.com/golangci/golangci-lint v1.64.6 h1:jOLaQN41IV7bMzXuNC4UnQGll7N1xY6eFDXkXEPGKAs= -github.com/golangci/golangci-lint v1.64.6/go.mod h1:Wz9q+6EVuqGQ94GQ96RB2mjpcZYTOGhBhbt4O7REPu4= +github.com/golangci/golangci-lint v1.64.7 h1:Xk1EyxoXqZabn5b4vnjNKSjCx1whBK53NP+mzLfX7HA= +github.com/golangci/golangci-lint v1.64.7/go.mod h1:5cEsUQBSr6zi8XI8OjmcY2Xmliqc4iYL7YoPrL+zLJ4= github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= @@ -485,8 +485,8 @@ github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tM github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.28.0 h1:jZnudE2zKCtYlGzLVreNp5pmCdOxXUzwsMDBkR21cyQ= github.com/sashamelentyev/usestdlibvars v1.28.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= -github.com/securego/gosec/v2 v2.22.1 h1:IcBt3TpI5Y9VN1YlwjSpM2cHu0i3Iw52QM+PQeg7jN8= -github.com/securego/gosec/v2 v2.22.1/go.mod h1:4bb95X4Jz7VSEPdVjC0hD7C/yR6kdeUBvCPOy9gDQ0g= +github.com/securego/gosec/v2 v2.22.2 h1:IXbuI7cJninj0nRpZSLCUlotsj8jGusohfONMrHoF6g= +github.com/securego/gosec/v2 v2.22.2/go.mod h1:UEBGA+dSKb+VqM6TdehR7lnQtIIMorYJ4/9CW1KVQBE= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -695,8 +695,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -718,8 +718,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -772,8 +772,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -855,8 +855,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= -golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= -golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= +golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= +golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -936,8 +936,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj 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.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= -google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -964,8 +964,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.6.0 h1:TAODvD3knlq75WCp2nyGJtT4LeRV/o7NN9nYPeVJXf8= -honnef.co/go/tools v0.6.0/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= +honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= +honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU= mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo= mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f h1:lMpcwN6GxNbWtbpI1+xzFLSW8XzX0u72NttUGVFjO3U= From e0c271bedd685d053ba5f573f0cc5311a6dc9a24 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 17 Mar 2025 11:28:34 +0100 Subject: [PATCH 0396/1670] Bump Go version to 1.24.0 --- go.mod | 2 +- tools/go.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 23531e70a8..821367c0cd 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/ubuntu/authd-oidc-brokers -go 1.23.0 +go 1.24.0 toolchain go1.24.1 diff --git a/tools/go.mod b/tools/go.mod index 4092006420..0956d0220a 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -1,6 +1,6 @@ module github.com/ubuntu/authd-oidc-brokers/tools -go 1.23.0 +go 1.24.0 toolchain go1.24.1 From 07880f741871d7e76441d7970e5b03b7fbff95bc Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 17 Mar 2025 12:44:57 +0100 Subject: [PATCH 0397/1670] Fix checkGroupIsDuplicate It returned false if there was a duplicate and true if there was none. --- internal/providers/msentraid/msentraid.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 4647adee6e..29d3487d6e 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -240,7 +240,7 @@ func checkGroupIsDuplicate(groupName string, groupNames []string) bool { // We don't want to treat local groups without the prefix as duplicates of non-local groups // (e.g. "linux-sudo" and "sudo"), so we compare the names as returned by the Graph API - except that we // ignore the case, because we use the group names in lowercase. - if strings.EqualFold(name, groupName) { + if !strings.EqualFold(name, groupName) { // Not a duplicate continue } From 5b8d8da928bc54f544c7a69587b8e86e3d1a2b89 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Fri, 28 Feb 2025 08:03:45 -0400 Subject: [PATCH 0398/1670] Add Claimer interface for fetching ID token claims Since we only care about the .Claims() method in the ID Token, we can switch to using a interface in the providers definitions so we can mock an ID token in the tests. --- internal/providers/genericprovider/genericprovider.go | 4 ++-- internal/providers/info/info.go | 5 +++++ internal/providers/msentraid/msentraid.go | 4 ++-- internal/providers/providers.go | 2 +- internal/testutils/provider.go | 4 ++-- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/internal/providers/genericprovider/genericprovider.go b/internal/providers/genericprovider/genericprovider.go index 388a39ac80..9543c5c041 100644 --- a/internal/providers/genericprovider/genericprovider.go +++ b/internal/providers/genericprovider/genericprovider.go @@ -47,7 +47,7 @@ func (p GenericProvider) GetMetadata(provider *oidc.Provider) (map[string]interf } // GetUserInfo is a no-op when no specific provider is in use. -func (p GenericProvider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken, providerMetadata map[string]interface{}) (info.User, error) { +func (p GenericProvider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken info.Claimer, providerMetadata map[string]interface{}) (info.User, error) { userClaims, err := p.userClaims(idToken) if err != nil { return info.User{}, err @@ -95,7 +95,7 @@ type claims struct { } // userClaims returns the user claims parsed from the ID token. -func (p GenericProvider) userClaims(idToken *oidc.IDToken) (claims, error) { +func (p GenericProvider) userClaims(idToken info.Claimer) (claims, error) { var userClaims claims if err := idToken.Claims(&userClaims); err != nil { return claims{}, fmt.Errorf("failed to get ID token claims: %v", err) diff --git a/internal/providers/info/info.go b/internal/providers/info/info.go index b42ca8dbb8..e2eb936a28 100644 --- a/internal/providers/info/info.go +++ b/internal/providers/info/info.go @@ -42,3 +42,8 @@ func NewUser(name, home, uuid, shell, gecos string, groups []Group) User { return u } + +// Claimer is an interface that defines a method to extract the claims from the ID token. +type Claimer interface { + Claims(any) error +} diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 29d3487d6e..e4a2cc0ce0 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -107,7 +107,7 @@ func (p Provider) GetMetadata(provider *oidc.Provider) (map[string]interface{}, // GetUserInfo returns the user info from the ID token and the groups the user is a member of, which are retrieved via // the Microsoft Graph API. -func (p Provider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken, providerMetadata map[string]interface{}) (info.User, error) { +func (p Provider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken info.Claimer, providerMetadata map[string]interface{}) (info.User, error) { msgraphHost := defaultMSGraphHost if providerMetadata["msgraph_host"] != nil { var ok bool @@ -146,7 +146,7 @@ type claims struct { } // userClaims returns the user claims parsed from the ID token. -func (p Provider) userClaims(idToken *oidc.IDToken) (claims, error) { +func (p Provider) userClaims(idToken info.Claimer) (claims, error) { var userClaims claims if err := idToken.Claims(&userClaims); err != nil { return claims{}, fmt.Errorf("failed to get ID token claims: %v", err) diff --git a/internal/providers/providers.go b/internal/providers/providers.go index 511ef0bffa..df804999d1 100644 --- a/internal/providers/providers.go +++ b/internal/providers/providers.go @@ -16,7 +16,7 @@ type Provider interface { CheckTokenScopes(token *oauth2.Token) error GetExtraFields(token *oauth2.Token) map[string]interface{} GetMetadata(provider *oidc.Provider) (map[string]interface{}, error) - GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken, providerMetadata map[string]interface{}) (info.User, error) + GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken info.Claimer, providerMetadata map[string]interface{}) (info.User, error) NormalizeUsername(username string) string SupportedOIDCAuthModes() []string VerifyUsername(requestedUsername, authenticatedUsername string) error diff --git a/internal/testutils/provider.go b/internal/testutils/provider.go index d9a3900b34..52978bc061 100644 --- a/internal/testutils/provider.go +++ b/internal/testutils/provider.go @@ -407,7 +407,7 @@ func (p *MockProvider) GetMetadata(provider *oidc.Provider) (map[string]interfac } // GetUserInfo is a no-op when no specific provider is in use. -func (p *MockProvider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken *oidc.IDToken, providerMetadata map[string]interface{}) (info.User, error) { +func (p *MockProvider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken info.Claimer, providerMetadata map[string]interface{}) (info.User, error) { if p.GetUserInfoFails { return info.User{}, errors.New("error requested in the mock") } @@ -459,7 +459,7 @@ type claims struct { } // userClaims returns the user claims parsed from the ID token. -func (p *MockProvider) userClaims(idToken *oidc.IDToken) (claims, error) { +func (p *MockProvider) userClaims(idToken info.Claimer) (claims, error) { var userClaims claims if err := idToken.Claims(&userClaims); err != nil { return claims{}, fmt.Errorf("failed to get ID token claims: %v", err) From 5fc98520c45294ffb85bb27a81929e2ec1b89fcb Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Fri, 28 Feb 2025 08:05:39 -0400 Subject: [PATCH 0399/1670] Use the full MSGraph URL from the start MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We used to parse it only when needed, but that created some issues when testing it, since the https protocol could not be overriden and we don´t want to install certificates when running tests. --- internal/providers/msentraid/msentraid.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index e4a2cc0ce0..15fedb7d99 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -101,14 +101,14 @@ func (p Provider) GetMetadata(provider *oidc.Provider) (map[string]interface{}, } return map[string]interface{}{ - "msgraph_host": claims.MSGraphHost, + "msgraph_host": fmt.Sprintf("https://%s/%s", claims.MSGraphHost, msgraphAPIVersion), }, nil } // GetUserInfo returns the user info from the ID token and the groups the user is a member of, which are retrieved via // the Microsoft Graph API. func (p Provider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, idToken info.Claimer, providerMetadata map[string]interface{}) (info.User, error) { - msgraphHost := defaultMSGraphHost + msgraphHost := fmt.Sprintf("https://%s/%s", defaultMSGraphHost, msgraphAPIVersion) if providerMetadata["msgraph_host"] != nil { var ok bool msgraphHost, ok = providerMetadata["msgraph_host"].(string) @@ -177,7 +177,7 @@ func (p Provider) getGroups(token *oauth2.Token, msgraphHost string) ([]info.Gro if err != nil { return nil, fmt.Errorf("failed to create GraphRequestAdapter: %v", err) } - adapter.SetBaseUrl(fmt.Sprintf("https://%s/%s", msgraphHost, msgraphAPIVersion)) + adapter.SetBaseUrl(msgraphHost) client := msgraphsdk.NewGraphServiceClient(adapter) From 5129afb2f258a4ce0ed0ae5e33c602d171b8d1c1 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Fri, 28 Feb 2025 08:08:00 -0400 Subject: [PATCH 0400/1670] Fix accessing pointer without checking for None This would panic if the group did not have a displayName. It's unlikely to happen often (if it happens at all), but we should cover this possibility nonetheless. --- internal/providers/msentraid/msentraid.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 15fedb7d99..65678f24db 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -262,7 +262,12 @@ func removeNonSecurityGroups(groups []msgraphmodels.Groupable) []msgraphmodels.G var securityGroups []msgraphmodels.Groupable for _, group := range groups { if !isSecurityGroup(group) { - log.Debugf(context.Background(), "Removing non-security group %s", *group.GetDisplayName()) + groupNamePtr := group.GetDisplayName() + if groupNamePtr == nil { + log.Debugf(context.Background(), "Removing unnamed non-security group") + continue + } + log.Debugf(context.Background(), "Removing non-security group %s", *groupNamePtr) continue } securityGroups = append(securityGroups, group) From 99c10b595baaaf9d2616e36bac30866365e97883 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 13:17:46 +0000 Subject: [PATCH 0401/1670] deps(go): bump github.com/spf13/viper from 1.19.0 to 1.20.0 Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.19.0 to 1.20.0. - [Release notes](https://github.com/spf13/viper/releases) - [Commits](https://github.com/spf13/viper/compare/v1.19.0...v1.20.0) --- updated-dependencies: - dependency-name: github.com/spf13/viper dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 18 +++++++----------- go.sum | 43 ++++++++++++++----------------------------- 2 files changed, 21 insertions(+), 40 deletions(-) diff --git a/go.mod b/go.mod index 821367c0cd..c3c316ae77 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/spf13/cobra v1.9.1 - github.com/spf13/viper v1.19.0 + github.com/spf13/viper v1.20.0 github.com/stretchr/testify v1.10.0 github.com/ubuntu/authd v0.4.2-0.20250203155723-a499f298f969 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 @@ -34,14 +34,13 @@ require ( github.com/cjlapao/common-go v0.0.41 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 // indirect - github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/microsoft/kiota-abstractions-go v1.9.0 // indirect @@ -51,15 +50,13 @@ require ( github.com/microsoft/kiota-serialization-json-go v1.1.0 // indirect github.com/microsoft/kiota-serialization-multipart-go v1.1.0 // indirect github.com/microsoft/kiota-serialization-text-go v1.1.0 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/otiai10/mint v1.6.3 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect - github.com/sagikazarmark/locafero v0.4.0 // indirect - github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/cast v1.7.1 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3 // indirect github.com/subosito/gotenv v1.6.0 // indirect @@ -68,7 +65,6 @@ require ( go.opentelemetry.io/otel/metric v1.35.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/net v0.37.0 // indirect golang.org/x/sync v0.12.0 // indirect golang.org/x/sys v0.31.0 // indirect diff --git a/go.sum b/go.sum index 4a23ab196a..fcbc6cae92 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -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/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -26,6 +26,8 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -35,8 +37,6 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= @@ -49,8 +49,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 h1:9ZM/t54vDjI3XRw2+HC2DxrH2Giv/jAyv7+Vq2QgXmg= github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069/go.mod h1:AT4NpQrOmyj1L/+hLja6aR0lk81yYYL4ePnj2kp7d6M= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -74,49 +72,38 @@ github.com/microsoftgraph/msgraph-sdk-go v1.65.0 h1:Z5QWWMbZHV3RfTbYoZzX82zUQpJy github.com/microsoftgraph/msgraph-sdk-go v1.65.0/go.mod h1:oF9nvRZxvf33VEspYTjDqT+dhm+sioT0fiyf0PRp2as= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.0 h1:2eHvehXGH0i6Ngsq/5LMAEHqspe6xfYVhFnSRfDq1Hg= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.0/go.mod h1:Y61FHnaNhoQGjf+bWCxH05RJeioZhbnADjkCkYt51wA= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I= github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs= github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= -github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= -github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= -github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= -github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY= +github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3 h1:7hth9376EoQEd1hH4lAp3vnaLP2UMyxuMMghLKzDHyU= github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3/go.mod h1:Z5KcoM0YLC7INlNhEezeIZ0TZNYf7WSNO0Lvah4DSeQ= github.com/stretchr/objx v0.1.0/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.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/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= @@ -142,8 +129,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= -golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= From 365305f97913cf6e025d616c426adf8f8414bde0 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Fri, 28 Feb 2025 08:12:22 -0400 Subject: [PATCH 0402/1670] Add utilities to mock MSGraph This is mainly focused on mocking the groups endpoint, but we could extend it later if necessary. --- .../providers/msentraid/msgraphmock_test.go | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 internal/providers/msentraid/msgraphmock_test.go diff --git a/internal/providers/msentraid/msgraphmock_test.go b/internal/providers/msentraid/msgraphmock_test.go new file mode 100644 index 0000000000..45193cab37 --- /dev/null +++ b/internal/providers/msentraid/msgraphmock_test.go @@ -0,0 +1,93 @@ +package msentraid_test + +import ( + "encoding/json" + "net/http" + "net/http/httptest" +) + +// startMSGraphServerMock creates a mock for the MS Graph server to simulate Graph API responses. +func startMSGraphServerMock(handler http.HandlerFunc) (mockServerURL string, stopFunc func()) { + if handler == nil { + handler = simpleGroupHandler + } + mockServer := httptest.NewServer(handler) + return mockServer.URL, mockServer.Close +} + +// simpleGroupHandler simulates a successful response with a list of groups. +func simpleGroupHandler(w http.ResponseWriter, r *http.Request) { + response := map[string]any{ + "value": []map[string]any{ + {"id": "id1", "displayName": "Group1", "securityEnabled": true}, + {"id": "id2", "displayName": "Group2", "securityEnabled": true}, + }, + } + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(response) +} + +// localGroupHandler simulates a successful response with a list of local groups. +func localGroupHandler(w http.ResponseWriter, r *http.Request) { + response := map[string]any{ + "value": []map[string]any{ + {"id": "local-id1", "displayName": "linux-local1", "securityEnabled": true}, + {"id": "local-id2", "displayName": "linux-local2", "securityEnabled": true}, + }, + } + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(response) +} + +// mixedGroupHandler simulates a successful response with a list of mixed remote and local groups. +func mixedGroupHandler(w http.ResponseWriter, r *http.Request) { + response := map[string]any{ + "value": []map[string]any{ + {"id": "id1", "displayName": "Group1", "securityEnabled": true}, + {"id": "local-id1", "displayName": "linux-local1", "securityEnabled": true}, + }, + } + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(response) +} + +// nonSecurityGroupHandler simulates a successful response with a list of groups including non-security groups. +func nonSecurityGroupHandler(w http.ResponseWriter, r *http.Request) { + response := map[string]any{ + "value": []map[string]any{ + {"id": "id1", "displayName": "Group1", "securityEnabled": true}, + {"id": "non-security-id", "displayName": "non-security"}, + }, + } + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(response) +} + +// missingIDGroupHandler simulates a successful response with a list of groups missing the ID field. +func missingIDGroupHandler(w http.ResponseWriter, r *http.Request) { + response := map[string]any{ + "value": []map[string]any{ + {"displayName": "Group1", "securityEnabled": true}, + {"id": "id2", "displayName": "Group2", "securityEnabled": true}, + }, + } + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(response) +} + +// missingDisplayNameGroupHandler simulates a successful response with a list of groups missing the displayName field. +func missingDisplayNameGroupHandler(w http.ResponseWriter, r *http.Request) { + response := map[string]any{ + "value": []map[string]any{ + {"id": "id1", "securityEnabled": true}, + {"id": "id2", "displayName": "Group2", "securityEnabled": true}, + }, + } + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(response) +} + +// errorGroupHandler simulates an error response from the server. +func errorGroupHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) +} From 41bc23b994cf3ebab48466bfd8410163970905f2 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Fri, 28 Feb 2025 08:13:42 -0400 Subject: [PATCH 0403/1670] Add tests for MSEntraID specific GetUserInfo --- internal/providers/msentraid/helper_test.go | 36 +++++++++++ .../providers/msentraid/msentraid_test.go | 62 +++++++++++++++++++ .../Successfully_get_user_info | 10 +++ ...et_user_info_filtering_non_security_groups | 8 +++ ...ccessfully_get_user_info_with_local_groups | 10 +++ ...ccessfully_get_user_info_with_mixed_groups | 10 +++ 6 files changed, 136 insertions(+) create mode 100644 internal/providers/msentraid/helper_test.go create mode 100644 internal/providers/msentraid/testdata/golden/TestGetUserInfo/Successfully_get_user_info create mode 100644 internal/providers/msentraid/testdata/golden/TestGetUserInfo/Successfully_get_user_info_filtering_non_security_groups create mode 100644 internal/providers/msentraid/testdata/golden/TestGetUserInfo/Successfully_get_user_info_with_local_groups create mode 100644 internal/providers/msentraid/testdata/golden/TestGetUserInfo/Successfully_get_user_info_with_mixed_groups diff --git a/internal/providers/msentraid/helper_test.go b/internal/providers/msentraid/helper_test.go new file mode 100644 index 0000000000..b60d1417bf --- /dev/null +++ b/internal/providers/msentraid/helper_test.go @@ -0,0 +1,36 @@ +package msentraid_test + +import ( + "encoding/json" + "time" + + "golang.org/x/oauth2" +) + +var ( + validAccessToken = &oauth2.Token{ + AccessToken: "accesstoken", + RefreshToken: "refreshtoken", + Expiry: time.Now().Add(1000 * time.Hour), + } + + validIDToken = &testIDToken{ + claims: `{"preferred_username": "valid-user", + "sub": "valid-sub", + "home": "/home/valid-user", + "shell": "/bin/bash", + "gecos": "Valid User"}`, + } + + invalidIDToken = &testIDToken{ + claims: "invalid claims", + } +) + +type testIDToken struct { + claims string +} + +func (t *testIDToken) Claims(v interface{}) error { + return json.Unmarshal([]byte(t.claims), v) +} diff --git a/internal/providers/msentraid/msentraid_test.go b/internal/providers/msentraid/msentraid_test.go index 34aee2427d..9c63fbadff 100644 --- a/internal/providers/msentraid/msentraid_test.go +++ b/internal/providers/msentraid/msentraid_test.go @@ -1,10 +1,13 @@ package msentraid_test import ( + "context" + "net/http" "testing" "github.com/stretchr/testify/require" "github.com/ubuntu/authd-oidc-brokers/internal/providers/msentraid" + "github.com/ubuntu/authd-oidc-brokers/internal/testutils/golden" "golang.org/x/oauth2" ) @@ -115,3 +118,62 @@ func TestVerifyUsername(t *testing.T) { }) } } + +func TestGetUserInfo(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + invalidIDToken bool + tokenScopes map[string]any + providerMetadata map[string]any + + groupEndpointHandler http.HandlerFunc + + wantErr bool + }{ + "Successfully_get_user_info": {}, + "Successfully_get_user_info_with_local_groups": {groupEndpointHandler: localGroupHandler}, + "Successfully_get_user_info_with_mixed_groups": {groupEndpointHandler: mixedGroupHandler}, + "Successfully_get_user_info_filtering_non_security_groups": {groupEndpointHandler: nonSecurityGroupHandler}, + + "Error_when_msgraph_host_is_invalid": {providerMetadata: map[string]any{"msgraph_host": "invalid"}, wantErr: true}, + "Error_when_id_token_claims_are_invalid": {invalidIDToken: true, wantErr: true}, + "Error_when_token_scopes_have_incorrect_type": {tokenScopes: map[string]any{"scope": struct{ notAString int }{10}}, wantErr: true}, + "Error_when_token_does_not_have_required_scopes": {tokenScopes: map[string]any{"scope": "not the required scopes"}, wantErr: true}, + "Error_when_getting_user_groups_fails": {groupEndpointHandler: errorGroupHandler, wantErr: true}, + "Error_when_group_is_missing_id": {groupEndpointHandler: missingIDGroupHandler, wantErr: true}, + "Error_when_group_is_missing_display_name": {groupEndpointHandler: missingDisplayNameGroupHandler, wantErr: true}, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + accessToken := validAccessToken + if tc.tokenScopes == nil { + tc.tokenScopes = map[string]any{"scope": msentraid.AllExpectedScopes()} + } + accessToken = accessToken.WithExtra(tc.tokenScopes) + + idToken := validIDToken + if tc.invalidIDToken { + idToken = invalidIDToken + } + + if tc.providerMetadata == nil { + msGraphMockURL, stopFunc := startMSGraphServerMock(tc.groupEndpointHandler) + t.Cleanup(stopFunc) + tc.providerMetadata = map[string]any{"msgraph_host": msGraphMockURL} + } + + p := msentraid.New() + got, err := p.GetUserInfo(context.Background(), accessToken, idToken, tc.providerMetadata) + if tc.wantErr { + require.Error(t, err, "GetUserInfo should return an error") + return + } + require.NoError(t, err, "GetUserInfo should not return an error") + + golden.CheckOrUpdateYAML(t, got) + }) + } +} diff --git a/internal/providers/msentraid/testdata/golden/TestGetUserInfo/Successfully_get_user_info b/internal/providers/msentraid/testdata/golden/TestGetUserInfo/Successfully_get_user_info new file mode 100644 index 0000000000..877a1ca98b --- /dev/null +++ b/internal/providers/msentraid/testdata/golden/TestGetUserInfo/Successfully_get_user_info @@ -0,0 +1,10 @@ +name: valid-user +uuid: valid-sub +home: /home/valid-user +shell: /bin/bash +gecos: Valid User +groups: + - name: group1 + ugid: id1 + - name: group2 + ugid: id2 diff --git a/internal/providers/msentraid/testdata/golden/TestGetUserInfo/Successfully_get_user_info_filtering_non_security_groups b/internal/providers/msentraid/testdata/golden/TestGetUserInfo/Successfully_get_user_info_filtering_non_security_groups new file mode 100644 index 0000000000..1c47773c0a --- /dev/null +++ b/internal/providers/msentraid/testdata/golden/TestGetUserInfo/Successfully_get_user_info_filtering_non_security_groups @@ -0,0 +1,8 @@ +name: valid-user +uuid: valid-sub +home: /home/valid-user +shell: /bin/bash +gecos: Valid User +groups: + - name: group1 + ugid: id1 diff --git a/internal/providers/msentraid/testdata/golden/TestGetUserInfo/Successfully_get_user_info_with_local_groups b/internal/providers/msentraid/testdata/golden/TestGetUserInfo/Successfully_get_user_info_with_local_groups new file mode 100644 index 0000000000..ca50f2a180 --- /dev/null +++ b/internal/providers/msentraid/testdata/golden/TestGetUserInfo/Successfully_get_user_info_with_local_groups @@ -0,0 +1,10 @@ +name: valid-user +uuid: valid-sub +home: /home/valid-user +shell: /bin/bash +gecos: Valid User +groups: + - name: local1 + ugid: "" + - name: local2 + ugid: "" diff --git a/internal/providers/msentraid/testdata/golden/TestGetUserInfo/Successfully_get_user_info_with_mixed_groups b/internal/providers/msentraid/testdata/golden/TestGetUserInfo/Successfully_get_user_info_with_mixed_groups new file mode 100644 index 0000000000..8ebf28d9d1 --- /dev/null +++ b/internal/providers/msentraid/testdata/golden/TestGetUserInfo/Successfully_get_user_info_with_mixed_groups @@ -0,0 +1,10 @@ +name: valid-user +uuid: valid-sub +home: /home/valid-user +shell: /bin/bash +gecos: Valid User +groups: + - name: group1 + ugid: id1 + - name: local1 + ugid: "" From 3318ece9497b6de49360d9c8fed22590fa9768ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 13:49:18 +0000 Subject: [PATCH 0404/1670] deps(go): bump github.com/coreos/go-oidc/v3 from 3.12.0 to 3.13.0 Bumps [github.com/coreos/go-oidc/v3](https://github.com/coreos/go-oidc) from 3.12.0 to 3.13.0. - [Release notes](https://github.com/coreos/go-oidc/releases) - [Commits](https://github.com/coreos/go-oidc/compare/v3.12.0...v3.13.0) --- updated-dependencies: - dependency-name: github.com/coreos/go-oidc/v3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index c3c316ae77..9ff4f1aa27 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,9 @@ toolchain go1.24.1 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 - github.com/coreos/go-oidc/v3 v3.12.0 + github.com/coreos/go-oidc/v3 v3.13.0 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf - github.com/go-jose/go-jose/v4 v4.0.4 + github.com/go-jose/go-jose/v4 v4.0.5 github.com/godbus/dbus/v5 v5.1.0 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum index fcbc6cae92..b10301560d 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xP github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= github.com/cjlapao/common-go v0.0.41 h1:j30UKZJWVWIllJ66x3EOslJvIk/VjkyenrhEcH64dGM= github.com/cjlapao/common-go v0.0.41/go.mod h1:ao5wEp0hYMNehJiHoarSjc5dKK5wi4LvnwjXaC2SxUI= -github.com/coreos/go-oidc/v3 v3.12.0 h1:sJk+8G2qq94rDI6ehZ71Bol3oUHy63qNYmkiSjrc/Jo= -github.com/coreos/go-oidc/v3 v3.12.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= +github.com/coreos/go-oidc/v3 v3.13.0 h1:M66zd0pcc5VxvBNM4pB331Wrsanby+QomQYjN8HamW8= +github.com/coreos/go-oidc/v3 v3.13.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= @@ -19,8 +19,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= -github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= +github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= +github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= From 9a748abec41dd04a322ab63bde1e21f4898914bd Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 18 Mar 2025 10:57:42 -0400 Subject: [PATCH 0405/1670] Add tags to ignore static check on helper files We have a lot of helper files to help us test our project and those are protected to not be built with "go build" to avoid having those in the release binary. Since they are not part of what we use in production, we don't want them listed in the static analysis checks as it would give us wrong metrics on the results. To avoid having to update tics configuration every time we rename those files or add new ones, let's tag them and filter those out in the analysis. --- internal/consts/generate.go | 2 ++ internal/dbusservice/localbus.go | 2 ++ po/mofiles.go | 2 ++ tools/tools.go | 2 ++ 4 files changed, 8 insertions(+) diff --git a/internal/consts/generate.go b/internal/consts/generate.go index 58a788f8ea..549821cfcb 100644 --- a/internal/consts/generate.go +++ b/internal/consts/generate.go @@ -1,3 +1,5 @@ +// TiCS: disabled // This is a helper file to generate the consts used based on which broker was built. + //go:build generate package main diff --git a/internal/dbusservice/localbus.go b/internal/dbusservice/localbus.go index add4e4b4c6..4ba74f7134 100644 --- a/internal/dbusservice/localbus.go +++ b/internal/dbusservice/localbus.go @@ -1,3 +1,5 @@ +// TiCS: disabled // This is a helper file for tests. + //go:build withlocalbus package dbusservice diff --git a/po/mofiles.go b/po/mofiles.go index db9b3e2108..5609fa7d6b 100644 --- a/po/mofiles.go +++ b/po/mofiles.go @@ -1,3 +1,5 @@ +// TiCS: disabled // This is a helper file. + //go:build withmo && !windows package po diff --git a/tools/tools.go b/tools/tools.go index 41172d6fd9..7aa9846004 100644 --- a/tools/tools.go +++ b/tools/tools.go @@ -1,3 +1,5 @@ +// TiCS: disabled // This is a helper file to pin the tools versions that we use. + //go:build tools package tools From ea960cd8a1a19bee7c53fd4d3fc50d7bbe066a64 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 18 Mar 2025 18:18:41 +0100 Subject: [PATCH 0406/1670] Add option force_provider_authentication This option forces authentication with the identity provider during login, so that user accounts which were disabled or removed from the identity provider can't log in anymore. --- conf/broker.conf | 12 ++++++++++++ internal/broker/broker.go | 6 +++++- internal/broker/config.go | 11 +++++++++++ internal/broker/config_test.go | 9 +++++++++ .../config.txt | 1 + .../Successfully_parse_config_file/config.txt | 1 + .../config.txt | 1 + .../config.txt | 1 + 8 files changed, 41 insertions(+), 1 deletion(-) diff --git a/conf/broker.conf b/conf/broker.conf index 6118d1f3a9..ffb0058485 100644 --- a/conf/broker.conf +++ b/conf/broker.conf @@ -6,6 +6,18 @@ client_id = ## client secret to authenticate with the provider. #client_secret = +## Always authenticate with the identity provider during login, even if +## a local method (e.g. local password) is used. +## This works by forcing a token refresh at login, which fails if the +## user does not have the necessary permissions in the identity provider. +## +## If set to false (the default), authentication with the identity +## provider is only done if the provider is reachable during login. +## +## Important: Enabling this option prevents authd users from logging in +## if the identity provider is unreachable (e.g. due to network issues). +#force_provider_authentication = false + [users] ## The directory where the home directories of new users are created. ## Existing users will keep their current home directory. diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 56a70d8469..78cdc9282e 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -626,8 +626,12 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au return AuthNext, nil } + if b.cfg.forceProviderAuthentication && session.isOffline { + return AuthDenied, errorMessage{Message: "could not refresh token: provider is not reachable"} + } + // Refresh the token if we're online even if the token has not expired - if !session.isOffline { + if b.cfg.forceProviderAuthentication || !session.isOffline { authInfo, err = b.refreshToken(ctx, session.oauth2Config, authInfo) var retrieveErr *oauth2.RetrieveError if errors.As(err, &retrieveErr) && b.provider.IsTokenExpiredError(*retrieveErr) { diff --git a/internal/broker/config.go b/internal/broker/config.go index 04cd8f42c5..5462436f85 100644 --- a/internal/broker/config.go +++ b/internal/broker/config.go @@ -16,6 +16,9 @@ import ( // Configuration sections and keys. const ( + // forceProviderAuthenticationKey is the key in the config file for the option to force provider authentication during login. + forceProviderAuthenticationKey = "force_provider_authentication" + // oidcSection is the section name in the config file for the OIDC specific configuration. oidcSection = "oidc" // issuerKey is the key in the config file for the issuer. @@ -64,6 +67,8 @@ type userConfig struct { clientSecret string issuerURL string + forceProviderAuthentication bool + allowedUsers map[string]struct{} allUsersAllowed bool ownerAllowed bool @@ -180,6 +185,12 @@ func parseConfigFile(cfgPath string, p provider) (userConfig, error) { cfg.issuerURL = oidc.Key(issuerKey).String() cfg.clientID = oidc.Key(clientIDKey).String() cfg.clientSecret = oidc.Key(clientSecret).String() + if oidc.HasKey(forceProviderAuthenticationKey) { + cfg.forceProviderAuthentication, err = oidc.Key(forceProviderAuthenticationKey).Bool() + if err != nil { + return cfg, fmt.Errorf("error parsing '%s': %w", forceProviderAuthenticationKey, err) + } + } } cfg.populateUsersConfig(iniCfg.Section(usersSection)) diff --git a/internal/broker/config_test.go b/internal/broker/config_test.go index f20e9d501e..9787e20652 100644 --- a/internal/broker/config_test.go +++ b/internal/broker/config_test.go @@ -26,10 +26,18 @@ client_id = client_id [oidc] issuer = https://issuer.url.com client_id = client_id +force_provider_authentication = true [users] home_base_dir = /home allowed_ssh_suffixes = @issuer.url.com +`, + + "invalid_boolean_value": ` +[oidc] +issuer = https://issuer.url.com +client_id = client_id +force_provider_authentication = invalid `, "singles": ` @@ -78,6 +86,7 @@ func TestParseConfig(t *testing.T) { "Error_if_file_is_not_updated": {configType: "template", wantErr: true}, "Error_if_drop_in_directory_is_unreadable": {dropInType: "unreadable-dir", wantErr: true}, "Error_if_drop_in_file_is_unreadable": {dropInType: "unreadable-file", wantErr: true}, + "Error_if_config_contains_invalid_values": {configType: "invalid_boolean_value", wantErr: true}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { diff --git a/internal/broker/testdata/golden/TestParseConfig/Do_not_fail_if_values_contain_a_single_template_delimiter/config.txt b/internal/broker/testdata/golden/TestParseConfig/Do_not_fail_if_values_contain_a_single_template_delimiter/config.txt index 8005e7b911..24d5771bc3 100644 --- a/internal/broker/testdata/golden/TestParseConfig/Do_not_fail_if_values_contain_a_single_template_delimiter/config.txt +++ b/internal/broker/testdata/golden/TestParseConfig/Do_not_fail_if_values_contain_a_single_template_delimiter/config.txt @@ -1,6 +1,7 @@ clientID= +forceProviderAuthentication=false allowedUsers=map[] allUsersAllowed=false ownerAllowed=true diff --git a/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file/config.txt b/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file/config.txt index 78d109ab51..bfb3f20a41 100644 --- a/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file/config.txt +++ b/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file/config.txt @@ -1,6 +1,7 @@ clientID=client_id clientSecret= issuerURL=https://issuer.url.com +forceProviderAuthentication=false allowedUsers=map[] allUsersAllowed=false ownerAllowed=true diff --git a/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file_with_optional_values/config.txt b/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file_with_optional_values/config.txt index 3066f32fc0..f318318119 100644 --- a/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file_with_optional_values/config.txt +++ b/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file_with_optional_values/config.txt @@ -1,6 +1,7 @@ clientID=client_id clientSecret= issuerURL=https://issuer.url.com +forceProviderAuthentication=true allowedUsers=map[] allUsersAllowed=false ownerAllowed=true diff --git a/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_with_drop_in_files/config.txt b/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_with_drop_in_files/config.txt index f651947fc8..19009a056d 100644 --- a/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_with_drop_in_files/config.txt +++ b/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_with_drop_in_files/config.txt @@ -1,6 +1,7 @@ clientID=lower_precedence_client_id clientSecret= issuerURL=https://higher-precedence-issuer.url.com +forceProviderAuthentication=true allowedUsers=map[] allUsersAllowed=false ownerAllowed=true From 1f476c84c5c19375c2377092f9fc69613cc8bcb3 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 18 Mar 2025 18:57:54 +0100 Subject: [PATCH 0407/1670] Test option force_provider_authentication --- internal/broker/broker_test.go | 27 ++++++++++++++----- internal/broker/export_test.go | 4 +++ internal/broker/helper_test.go | 22 ++++++++------- .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json | 1 + .../first_call | 3 +++ .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json | 1 + .../first_call | 3 +++ 9 files changed, 47 insertions(+), 16 deletions(-) create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_provider_authentication_is_forced/data/provider_url/test-user@email.com/password create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_provider_authentication_is_forced/data/provider_url/test-user@email.com/token.json create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_provider_authentication_is_forced/first_call create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provider_authentication_is_forced_and_session_is_offline/data/provider_url/test-user@email.com/password create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provider_authentication_is_forced_and_session_is_offline/data/provider_url/test-user@email.com/token.json create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provider_authentication_is_forced_and_session_is_offline/first_call diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 40c6afb556..eebfa99eb4 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -387,9 +387,10 @@ func TestIsAuthenticated(t *testing.T) { correctPassword := "password" tests := map[string]struct { - sessionMode string - sessionOffline bool - username string + sessionMode string + sessionOffline bool + username string + forceProviderAuthentication bool firstMode string firstSecret string @@ -476,6 +477,11 @@ func TestIsAuthenticated(t *testing.T) { wantSecondCall: true, secondMode: authmodes.DeviceQr, }, + "Authenticating_with_password_when_provider_authentication_is_forced": { + firstMode: authmodes.Password, + token: &tokenOptions{}, + forceProviderAuthentication: true, + }, "Error_when_authentication_data_is_invalid": {invalidAuthData: true}, "Error_when_secret_can_not_be_decrypted": {firstMode: authmodes.Password, badFirstKey: true}, @@ -545,6 +551,12 @@ func TestIsAuthenticated(t *testing.T) { "Error_when_mode_is_newpassword_and_session_has_no_token": {firstMode: authmodes.NewPassword}, // This test case also tests that errors with double quotes are marshaled to JSON correctly. "Error_when_selected_username_does_not_match_the_provider_one": {username: "not-matching", firstSecret: "-"}, + "Error_when_provider_authentication_is_forced_and_session_is_offline": { + firstMode: authmodes.Password, + token: &tokenOptions{}, + forceProviderAuthentication: true, + sessionOffline: true, + }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { @@ -567,10 +579,11 @@ func TestIsAuthenticated(t *testing.T) { require.NoError(t, err, "Setup: Mkdir should not have returned an error") cfg := &brokerForTestConfig{ - Config: broker.Config{DataDir: dataDir}, - getUserInfoFails: tc.getUserInfoFails, - ownerAllowed: true, - firstUserBecomesOwner: true, + Config: broker.Config{DataDir: dataDir}, + getUserInfoFails: tc.getUserInfoFails, + ownerAllowed: true, + firstUserBecomesOwner: true, + forceProviderAuthentication: tc.forceProviderAuthentication, } if tc.customHandlers == nil { // Use the default provider URL if no custom handlers are provided. diff --git a/internal/broker/export_test.go b/internal/broker/export_test.go index c0be5df818..7e0ce59396 100644 --- a/internal/broker/export_test.go +++ b/internal/broker/export_test.go @@ -20,6 +20,10 @@ func (cfg *Config) SetIssuerURL(issuerURL string) { cfg.issuerURL = issuerURL } +func (cfg *Config) SetForceProviderAuthentication(value bool) { + cfg.forceProviderAuthentication = value +} + func (cfg *Config) SetHomeBaseDir(homeBaseDir string) { cfg.homeBaseDir = homeBaseDir } diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index 7f6c4c2871..99b9316222 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -24,15 +24,16 @@ import ( type brokerForTestConfig struct { broker.Config - issuerURL string - allowedUsers map[string]struct{} - allUsersAllowed bool - ownerAllowed bool - firstUserBecomesOwner bool - owner string - homeBaseDir string - allowedSSHSuffixes []string - provider providers.Provider + issuerURL string + forceProviderAuthentication bool + allowedUsers map[string]struct{} + allUsersAllowed bool + ownerAllowed bool + firstUserBecomesOwner bool + owner string + homeBaseDir string + allowedSSHSuffixes []string + provider providers.Provider getUserInfoFails bool firstCallDelay int @@ -52,6 +53,9 @@ func newBrokerForTests(t *testing.T, cfg *brokerForTestConfig) (b *broker.Broker if cfg.issuerURL != "" { cfg.SetIssuerURL(cfg.issuerURL) } + if cfg.forceProviderAuthentication { + cfg.SetForceProviderAuthentication(cfg.forceProviderAuthentication) + } if cfg.homeBaseDir != "" { cfg.SetHomeBaseDir(cfg.homeBaseDir) } diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_provider_authentication_is_forced/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_provider_authentication_is_forced/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_provider_authentication_is_forced/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_provider_authentication_is_forced/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_provider_authentication_is_forced/data/provider_url/test-user@email.com/token.json new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_provider_authentication_is_forced/data/provider_url/test-user@email.com/token.json @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_provider_authentication_is_forced/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_provider_authentication_is_forced/first_call new file mode 100644 index 0000000000..f50b5eb551 --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_provider_authentication_is_forced/first_call @@ -0,0 +1,3 @@ +access: granted +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-test-group","ugid":"12345"},{"name":"local-test-group","ugid":""}]}}' +err: diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provider_authentication_is_forced_and_session_is_offline/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provider_authentication_is_forced_and_session_is_offline/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provider_authentication_is_forced_and_session_is_offline/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provider_authentication_is_forced_and_session_is_offline/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provider_authentication_is_forced_and_session_is_offline/data/provider_url/test-user@email.com/token.json new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provider_authentication_is_forced_and_session_is_offline/data/provider_url/test-user@email.com/token.json @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provider_authentication_is_forced_and_session_is_offline/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provider_authentication_is_forced_and_session_is_offline/first_call new file mode 100644 index 0000000000..91a362a5d6 --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provider_authentication_is_forced_and_session_is_offline/first_call @@ -0,0 +1,3 @@ +access: denied +data: '{"message":"authentication failure: could not refresh token: provider is not reachable"}' +err: From d104c8276b7091727a09f7b837e9a9d8617293a2 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 19 Mar 2025 12:11:31 +0100 Subject: [PATCH 0408/1670] Improve error message Include the path of the config file which we failed to parse. --- internal/broker/broker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 78cdc9282e..fd937ba2b4 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -102,7 +102,7 @@ func New(cfg Config, args ...Option) (b *Broker, err error) { if cfg.ConfigFile != "" { cfg.userConfig, err = parseConfigFile(cfg.ConfigFile, p) if err != nil { - return nil, fmt.Errorf("could not parse config: %v", err) + return nil, fmt.Errorf("could not parse config file '%s': %v", cfg.ConfigFile, err) } } From cd7a8586bfe8d20d28b61bd454c744e805455bf3 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 19 Mar 2025 12:12:03 +0100 Subject: [PATCH 0409/1670] Omit unhelpful part from error messages "could not create broker" does not add any useful information to the error message, neither for the users nor for us developers. For example, the error message: could not create broker: could not parse config file '/var/snap/authd-msentraid/x6/broker.conf': error parsing 'force_token_refresh_on_login': parsing "tru": invalid syntax becomes more readable by omitting the first part: could not parse config file '/var/snap/authd-msentraid/x6/broker.conf': error parsing 'force_token_refresh_on_login': parsing "tru": invalid syntax --- internal/broker/broker.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index fd937ba2b4..f5da7f8e6d 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -95,8 +95,6 @@ type Option func(*option) // New returns a new oidc Broker with the providers listed in the configuration file. func New(cfg Config, args ...Option) (b *Broker, err error) { - defer decorate.OnError(&err, "could not create broker") - p := providers.CurrentProvider() if cfg.ConfigFile != "" { From 4884492cbf9e0e5c789dd19f72e74deb0ab35378 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Mar 2025 08:13:45 +0000 Subject: [PATCH 0410/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.65.0 to 1.66.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.65.0...v1.66.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9ff4f1aa27..c533021f54 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.65.0 + github.com/microsoftgraph/msgraph-sdk-go v1.66.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.3.0 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 diff --git a/go.sum b/go.sum index b10301560d..f3a8cf4e3a 100644 --- a/go.sum +++ b/go.sum @@ -68,8 +68,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.1.0 h1:rALPMWJU749Bb81Q github.com/microsoft/kiota-serialization-multipart-go v1.1.0/go.mod h1:GBDmHOLO3+DhBnMmJC7OCR2Jq05VJuYKSpx7jKNrGmE= github.com/microsoft/kiota-serialization-text-go v1.1.0 h1:PfX+asvJW9KdkbC4MikRgekoRqtAclqkGm1/d2QTQWw= github.com/microsoft/kiota-serialization-text-go v1.1.0/go.mod h1:UgXgpLZ3a98nVF8A1nuEXmkv1HTMhJ+bXHDM1yRaFuw= -github.com/microsoftgraph/msgraph-sdk-go v1.65.0 h1:Z5QWWMbZHV3RfTbYoZzX82zUQpJy3K7fupKeuKxYMro= -github.com/microsoftgraph/msgraph-sdk-go v1.65.0/go.mod h1:oF9nvRZxvf33VEspYTjDqT+dhm+sioT0fiyf0PRp2as= +github.com/microsoftgraph/msgraph-sdk-go v1.66.0 h1:PTiaoHXsFzoBwUro7icuU6oqPIeWES5w+s50oGMQPJ0= +github.com/microsoftgraph/msgraph-sdk-go v1.66.0/go.mod h1:oF9nvRZxvf33VEspYTjDqT+dhm+sioT0fiyf0PRp2as= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.0 h1:2eHvehXGH0i6Ngsq/5LMAEHqspe6xfYVhFnSRfDq1Hg= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.0/go.mod h1:Y61FHnaNhoQGjf+bWCxH05RJeioZhbnADjkCkYt51wA= github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= From b6161c8f358f9446cde99fee02d06691a164191e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Mar 2025 08:14:59 +0000 Subject: [PATCH 0411/1670] deps(go): bump github.com/golang-jwt/jwt/v5 from 5.2.1 to 5.2.2 Bumps [github.com/golang-jwt/jwt/v5](https://github.com/golang-jwt/jwt) from 5.2.1 to 5.2.2. - [Release notes](https://github.com/golang-jwt/jwt/releases) - [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md) - [Commits](https://github.com/golang-jwt/jwt/compare/v5.2.1...v5.2.2) --- updated-dependencies: - dependency-name: github.com/golang-jwt/jwt/v5 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9ff4f1aa27..e2fff206ba 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/go-jose/go-jose/v4 v4.0.5 github.com/godbus/dbus/v5 v5.1.0 - github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/golang-jwt/jwt/v5 v5.2.2 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible github.com/microsoftgraph/msgraph-sdk-go v1.65.0 diff --git a/go.sum b/go.sum index b10301560d..38e48b08d9 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,8 @@ github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlnd github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= From e4f172edef6fc1bb7773a91757a522b9ef1a20f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Mar 2025 08:16:44 +0000 Subject: [PATCH 0412/1670] deps(go): bump github.com/Azure/azure-sdk-for-go/sdk/azcore Bumps [github.com/Azure/azure-sdk-for-go/sdk/azcore](https://github.com/Azure/azure-sdk-for-go) from 1.17.0 to 1.17.1. - [Release notes](https://github.com/Azure/azure-sdk-for-go/releases) - [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/release.md) - [Commits](https://github.com/Azure/azure-sdk-for-go/compare/sdk/azcore/v1.17.0...sdk/azcore/v1.17.1) --- updated-dependencies: - dependency-name: github.com/Azure/azure-sdk-for-go/sdk/azcore dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9ff4f1aa27..568f0f7e06 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.24.0 toolchain go1.24.1 require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 github.com/coreos/go-oidc/v3 v3.13.0 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/go-jose/go-jose/v4 v4.0.5 diff --git a/go.sum b/go.sum index b10301560d..8c9857e6ee 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 h1:DSDNVxqkoXJiko6x8a90zidoYqnYYa6c1MTzDKzKkTo= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1/go.mod h1:zGqV2R4Cr/k8Uye5w+dgQ06WJtEcbQG/8J7BB6hnCr4= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= github.com/cjlapao/common-go v0.0.41 h1:j30UKZJWVWIllJ66x3EOslJvIk/VjkyenrhEcH64dGM= From 5da782817aa4cb23b8de78ba4c7d3643c0656f79 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Mar 2025 08:48:50 +0000 Subject: [PATCH 0413/1670] deps(go-tools): bump github.com/golangci/golangci-lint in /tools Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.64.7 to 1.64.8. - [Release notes](https://github.com/golangci/golangci-lint/releases) - [Changelog](https://github.com/golangci/golangci-lint/blob/main/CHANGELOG.md) - [Commits](https://github.com/golangci/golangci-lint/compare/v1.64.7...v1.64.8) --- updated-dependencies: - dependency-name: github.com/golangci/golangci-lint dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- tools/go.mod | 2 +- tools/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index 0956d0220a..d650fe97e3 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -5,7 +5,7 @@ go 1.24.0 toolchain go1.24.1 require ( - github.com/golangci/golangci-lint v1.64.7 + github.com/golangci/golangci-lint v1.64.8 golang.org/x/mod v0.24.0 ) diff --git a/tools/go.sum b/tools/go.sum index 07f08703c3..0c52f62f0b 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -235,8 +235,8 @@ github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUP github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s= github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE= github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY= -github.com/golangci/golangci-lint v1.64.7 h1:Xk1EyxoXqZabn5b4vnjNKSjCx1whBK53NP+mzLfX7HA= -github.com/golangci/golangci-lint v1.64.7/go.mod h1:5cEsUQBSr6zi8XI8OjmcY2Xmliqc4iYL7YoPrL+zLJ4= +github.com/golangci/golangci-lint v1.64.8 h1:y5TdeVidMtBGG32zgSC7ZXTFNHrsJkDnpO4ItB3Am+I= +github.com/golangci/golangci-lint v1.64.8/go.mod h1:5cEsUQBSr6zi8XI8OjmcY2Xmliqc4iYL7YoPrL+zLJ4= github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= From b4293d52d52b7af2f38962828ad4aec8c72c8777 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 26 Mar 2025 13:43:34 +0100 Subject: [PATCH 0414/1670] Fix duplicate information in error message "failed to get user groups" is already added to the error message in `getSecurityGroups` if it's the call to `client.Me().TransitiveMemberOf().GraphGroup()` which fails, which resulted in duplicate information (even more than we already have): Could not fetch user info: could not get user info: failed to get user groups: failed to get user groups: Get "graph.microsoft.com/me/transitiveMemberOf/graph.group": unsupported protocol scheme "". Using cached user info. `getSecurityGroups` is only called in this one location, so we don't need to add information to the error message to reconstruct the call stack. --- internal/providers/msentraid/msentraid.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 65678f24db..a7a40e583b 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -185,7 +185,7 @@ func (p Provider) getGroups(token *oauth2.Token, msgraphHost string) ([]info.Gro // additional permissions) which the user is a member of. graphGroups, err := getSecurityGroups(client) if err != nil { - return nil, fmt.Errorf("failed to get user groups: %v", err) + return nil, err } var groups []info.Group From 9649956891dbac4a981302621d028f37d4419ed0 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 26 Mar 2025 13:50:46 +0100 Subject: [PATCH 0415/1670] Fix invalid msgraph host Since commit 5fc98520c45294ffb85bb27a81929e2ec1b89fcb, we store the full URL to the msgraph host in the provider metadata. However, the metadata on disk is not updated, so it can still contain only the domain name of the msgraph host, resulting in a failure to get the groups via msgraph: Could not fetch user info: could not get user info: failed to get user groups: failed to get user groups: Get "graph.microsoft.com/me/transitiveMemberOf/graph.group": unsupported protocol scheme "". Using cached user info. This commit fixes that by supporting both the old and new formats of the msgraph host field in the provider metadata. Closes #858 --- internal/providers/msentraid/msentraid.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index a7a40e583b..62b6f3645f 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -115,6 +115,12 @@ func (p Provider) GetUserInfo(ctx context.Context, accessToken *oauth2.Token, id if !ok { return info.User{}, fmt.Errorf("failed to cast msgraph_host to string: %v", providerMetadata["msgraph_host"]) } + + // Handle the case that the provider metadata only contains the host without the protocol and API version, + // as was the case before 5fc98520c45294ffb85bb27a81929e2ec1b89fcb. This fixes #858. + if !strings.Contains(msgraphHost, "://") { + msgraphHost = fmt.Sprintf("https://%s/%s", msgraphHost, msgraphAPIVersion) + } } userClaims, err := p.userClaims(idToken) From b68ab43527645f2802edc4069150ef626fce48c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 09:06:49 +0000 Subject: [PATCH 0416/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.66.0 to 1.67.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.66.0...v1.67.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 29 +++++++++++++---------------- go.sum | 54 ++++++++++++++++++++++++++---------------------------- 2 files changed, 39 insertions(+), 44 deletions(-) diff --git a/go.mod b/go.mod index 8258d10d7d..8f7a464d53 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,6 @@ module github.com/ubuntu/authd-oidc-brokers go 1.24.0 - -toolchain go1.24.1 - require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 github.com/coreos/go-oidc/v3 v3.13.0 @@ -13,8 +10,8 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.2 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.66.0 - github.com/microsoftgraph/msgraph-sdk-go-core v1.3.0 + github.com/microsoftgraph/msgraph-sdk-go v1.67.0 + github.com/microsoftgraph/msgraph-sdk-go-core v1.3.1 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/spf13/cobra v1.9.1 @@ -31,7 +28,7 @@ require ( require ( github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect - github.com/cjlapao/common-go v0.0.41 // indirect + github.com/cjlapao/common-go v0.0.48 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect @@ -41,21 +38,21 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/microsoft/kiota-abstractions-go v1.9.0 // indirect - github.com/microsoft/kiota-authentication-azure-go v1.2.0 // indirect - github.com/microsoft/kiota-http-go v1.5.0 // indirect - github.com/microsoft/kiota-serialization-form-go v1.1.0 // indirect - github.com/microsoft/kiota-serialization-json-go v1.1.0 // indirect - github.com/microsoft/kiota-serialization-multipart-go v1.1.0 // indirect - github.com/microsoft/kiota-serialization-text-go v1.1.0 // indirect + github.com/microsoft/kiota-abstractions-go v1.9.1 // indirect + github.com/microsoft/kiota-authentication-azure-go v1.2.1 // indirect + github.com/microsoft/kiota-http-go v1.5.1 // indirect + github.com/microsoft/kiota-serialization-form-go v1.1.1 // indirect + github.com/microsoft/kiota-serialization-json-go v1.1.1 // indirect + github.com/microsoft/kiota-serialization-multipart-go v1.1.1 // indirect + github.com/microsoft/kiota-serialization-text-go v1.1.1 // indirect github.com/otiai10/mint v1.6.3 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect - github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/sagikazarmark/locafero v0.8.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/afero v1.14.0 // indirect github.com/spf13/cast v1.7.1 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3 // indirect diff --git a/go.sum b/go.sum index 57a5ac7ec1..ab5155496e 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 h1:DSDNVxqkoXJiko6x8a90zido github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1/go.mod h1:zGqV2R4Cr/k8Uye5w+dgQ06WJtEcbQG/8J7BB6hnCr4= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= -github.com/cjlapao/common-go v0.0.41 h1:j30UKZJWVWIllJ66x3EOslJvIk/VjkyenrhEcH64dGM= -github.com/cjlapao/common-go v0.0.41/go.mod h1:ao5wEp0hYMNehJiHoarSjc5dKK5wi4LvnwjXaC2SxUI= +github.com/cjlapao/common-go v0.0.48 h1:j2rMlSBoEG8P6WPV6aOoSHAWX6D9fl2jVpAqwnHKB4E= +github.com/cjlapao/common-go v0.0.48/go.mod h1:wWif40/s6IIjaT+BO01Bo6VeYzz7IQ23BOj6xi/pAqw= github.com/coreos/go-oidc/v3 v3.13.0 h1:M66zd0pcc5VxvBNM4pB331Wrsanby+QomQYjN8HamW8= github.com/coreos/go-oidc/v3 v3.13.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= @@ -49,29 +49,28 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 h1:9ZM/t54vDjI3XRw2+HC2DxrH2Giv/jAyv7+Vq2QgXmg= github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069/go.mod h1:AT4NpQrOmyj1L/+hLja6aR0lk81yYYL4ePnj2kp7d6M= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/microsoft/kiota-abstractions-go v1.9.0 h1:kJ8ANzjVB5+BUmeX9ixe1UNwbDplqOWC0ftNKWKTzj4= -github.com/microsoft/kiota-abstractions-go v1.9.0/go.mod h1:dqP19EIArR8hdsWUxXZ5bKaItB4regdWVKBniP8yHpo= -github.com/microsoft/kiota-authentication-azure-go v1.2.0 h1:OaoS4T6jDpxtmSRzuMCAYwxVzFWJKREdX7KKYa6Z6OI= -github.com/microsoft/kiota-authentication-azure-go v1.2.0/go.mod h1:HYNxHxHXgzlDDv/QqEvpKk2QDZdpWIijVO9xAltMvU8= -github.com/microsoft/kiota-http-go v1.5.0 h1:Iz/Ur+xBNQKQxsi7WOvhwybuah2kS7ELMWOhVXev9hM= -github.com/microsoft/kiota-http-go v1.5.0/go.mod h1:6w+50/9KDeFUI6D0Z6JFQ6ea9LDxrZnWHl7zCsbrprc= -github.com/microsoft/kiota-serialization-form-go v1.1.0 h1:gCLxwnd8HNaswpBdCLODcz2oVEoQrXPU/o6/J3QS9Bs= -github.com/microsoft/kiota-serialization-form-go v1.1.0/go.mod h1:ZDmGk8u+1DoOZoPwiZ74jkRnVEe4GApEAi6K/+bHX3g= -github.com/microsoft/kiota-serialization-json-go v1.1.0 h1:C2XTRLfcyPvbYWMDx8xM3c1cRLGWcD3434mo2J6jNyo= -github.com/microsoft/kiota-serialization-json-go v1.1.0/go.mod h1:jMdF/jpb49pDur616ZoJAQt8ZxpqbCQRr+41g66Xw8Q= -github.com/microsoft/kiota-serialization-multipart-go v1.1.0 h1:rALPMWJU749Bb81QzOEmIf7ZvWy5StUmZUYL6/xFZsE= -github.com/microsoft/kiota-serialization-multipart-go v1.1.0/go.mod h1:GBDmHOLO3+DhBnMmJC7OCR2Jq05VJuYKSpx7jKNrGmE= -github.com/microsoft/kiota-serialization-text-go v1.1.0 h1:PfX+asvJW9KdkbC4MikRgekoRqtAclqkGm1/d2QTQWw= -github.com/microsoft/kiota-serialization-text-go v1.1.0/go.mod h1:UgXgpLZ3a98nVF8A1nuEXmkv1HTMhJ+bXHDM1yRaFuw= -github.com/microsoftgraph/msgraph-sdk-go v1.66.0 h1:PTiaoHXsFzoBwUro7icuU6oqPIeWES5w+s50oGMQPJ0= -github.com/microsoftgraph/msgraph-sdk-go v1.66.0/go.mod h1:oF9nvRZxvf33VEspYTjDqT+dhm+sioT0fiyf0PRp2as= -github.com/microsoftgraph/msgraph-sdk-go-core v1.3.0 h1:2eHvehXGH0i6Ngsq/5LMAEHqspe6xfYVhFnSRfDq1Hg= -github.com/microsoftgraph/msgraph-sdk-go-core v1.3.0/go.mod h1:Y61FHnaNhoQGjf+bWCxH05RJeioZhbnADjkCkYt51wA= +github.com/microsoft/kiota-abstractions-go v1.9.1 h1:vkeluAj3ZLvOvG1NY5HcuBclhA9UXkXPeLxcBhQ28cU= +github.com/microsoft/kiota-abstractions-go v1.9.1/go.mod h1:rlWmh9TR1j4EvkEp1Bz0rYTWunlvHDG9s6IMlkpDNrc= +github.com/microsoft/kiota-authentication-azure-go v1.2.1 h1:CwIMiIF/xFm9l+77hFgIsou5mIDz52tft6xWc6WszgU= +github.com/microsoft/kiota-authentication-azure-go v1.2.1/go.mod h1:+xHS60A+qaouiLaEvOyTZ8H/sVU+y7DNz1bp7ytcUqA= +github.com/microsoft/kiota-http-go v1.5.1 h1:f260q6brp9/xHY4pECVPqzDYwoqieQhhcDiMMGc1ox0= +github.com/microsoft/kiota-http-go v1.5.1/go.mod h1:r8HXDhMyLF4GJYdSmOGRquX3yI8KZpmbFCrTf78/kI0= +github.com/microsoft/kiota-serialization-form-go v1.1.1 h1:Me4mmLB+aukCA1zToqVfT2meCOsmGgAWAUIrlGuteWM= +github.com/microsoft/kiota-serialization-form-go v1.1.1/go.mod h1:NbmHzUt2fjQ5nNbQjS6/bBNqoRFtv+ZMtdxEPRVtEJw= +github.com/microsoft/kiota-serialization-json-go v1.1.1 h1:EG1dX7GmBM8UGV1+TwUtepNHthmF8LxJ54w7fsdfn1M= +github.com/microsoft/kiota-serialization-json-go v1.1.1/go.mod h1:qnVWoeGkngHK+YhKilbncI6j7DZ3Jd9QyIRzPTj2hcM= +github.com/microsoft/kiota-serialization-multipart-go v1.1.1 h1:tHmfffhGqd/TvrWh/0V2SU+SzHm+LHq1gKBkQz7i5IA= +github.com/microsoft/kiota-serialization-multipart-go v1.1.1/go.mod h1:o36BKK7IhIFptmrHugLsD4cj0xkodw34QXZtkzt3ADc= +github.com/microsoft/kiota-serialization-text-go v1.1.1 h1:i9qSjDh99yqkJOVtpASE4PWUU8++kLpP4NXl9ptqezs= +github.com/microsoft/kiota-serialization-text-go v1.1.1/go.mod h1:eyBbSGYNJbhjP574eIkAdVcmSdYxm1SiFcI7LbWCT1U= +github.com/microsoftgraph/msgraph-sdk-go v1.67.0 h1:DzDuf72hjuxRvtEW0RyqUNpi/EKQ/Sk5XyPwcbaa28g= +github.com/microsoftgraph/msgraph-sdk-go v1.67.0/go.mod h1:l1pUApVQBjluW2iBx89g9V9BTG4g8eE0+M0r/IETy4Y= +github.com/microsoftgraph/msgraph-sdk-go-core v1.3.1 h1:vJA+j+1z8c8adjd4YtxzF0L668nmFM9jeXvx2YvK7CA= +github.com/microsoftgraph/msgraph-sdk-go-core v1.3.1/go.mod h1:bjqNFhM9MasKdZ/UQfqtLNVB/uOZPUoPhOwUjmSGOgE= github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I= github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs= @@ -84,14 +83,14 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= -github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/sagikazarmark/locafero v0.8.0 h1:mXaMVw7IqxNBxfv3LdWt9MDmcWDQ1fagDH918lOdVaQ= +github.com/sagikazarmark/locafero v0.8.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= -github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= +github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= @@ -147,7 +146,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc 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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= From 3cce2a586ac4ef0636f008129c77ae3fff3d5014 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Thu, 13 Feb 2025 11:58:51 -0400 Subject: [PATCH 0417/1670] Parse coverage to Cobertura format and upload it as artifact --- .github/workflows/ci.yml | 43 ++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b149d7ec72..c909d80c49 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,14 @@ jobs: sudo apt update sudo apt install -y git-delta + - name: Install coverage collection dependencies + run: | + set -eu + + go install github.com/AlekSi/gocov-xml@latest + go install github.com/axw/gocov/gocov@latest + dotnet tool install -g dotnet-reportgenerator-globaltool + - name: Prepare tests artifacts path run: | set -eu @@ -49,9 +57,11 @@ jobs: set -eu # The coverage is not written if the output directory does not exist, so we need to create it. - raw_cov_dir="/tmp/raw_files" - rm -fr "${raw_cov_dir}" - mkdir -p "${raw_cov_dir}" + cov_dir=${PWD}/coverage + raw_cov_dir=${cov_dir}/raw_files + codecov_dir=${cov_dir}/codecov + + mkdir -p "${raw_cov_dir}" "${codecov_dir}" # Print executed commands to ease debugging set -x @@ -63,10 +73,31 @@ jobs: gotestfmt --logfile "${AUTHD_TEST_ARTIFACTS_DIR}/gotestfmt.cover.log" # Convert the raw coverage data into textfmt so we can merge the Rust one into it - go tool covdata textfmt -i="${raw_cov_dir}" -o="/tmp/coverage.out" + go tool covdata textfmt -i="${raw_cov_dir}" -o="${cov_dir}/coverage.out" + + # Filter out the testutils package + grep -v -e "testutils" "${cov_dir}/coverage.out" >"${cov_dir}/coverage.out.filtered" - # Filter out the testutils package and the pb.go file - grep -v -e "testutils" "/tmp/coverage.out" >"/tmp/coverage.out.filtered" + # Generate the Cobertura report for Go + gocov convert "${cov_dir}/coverage.out.filtered" | gocov-xml > "${cov_dir}/coverage.xml" + reportgenerator -reports:"${cov_dir}/coverage.xml" -targetdir:"${codecov_dir}" -reporttypes:Cobertura + + # Store the coverage directory for the next steps + echo COVERAGE_DIR="${codecov_dir}" >> ${GITHUB_ENV} + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + directory: ${{ env.COVERAGE_DIR }} + files: ${{ env.COVERAGE_DIR }}/Cobertura.xml + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload coverage report as artifact + if: github.ref == 'refs/heads/main' + uses: actions/upload-artifact@v4 + with: + name: coverage.zip + path: ${{ env.COVERAGE_DIR }} - name: Upload test artifacts if: failure() From 51eb4be32e7637a3b8b3460c1393760b44775a93 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 31 Mar 2025 16:44:06 +0200 Subject: [PATCH 0418/1670] go.mod: Re-add toolchain version which was removed by dependabot --- go.mod | 3 +++ 1 file changed, 3 insertions(+) diff --git a/go.mod b/go.mod index 8f7a464d53..4c15860a91 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,9 @@ module github.com/ubuntu/authd-oidc-brokers go 1.24.0 + +toolchain go1.24.1 + require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 github.com/coreos/go-oidc/v3 v3.13.0 From 920aa896c4691a0317c8bc77e4ac1ee6e6390290 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 09:06:27 +0000 Subject: [PATCH 0419/1670] deps(go): bump github.com/spf13/viper from 1.20.0 to 1.20.1 Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.20.0 to 1.20.1. - [Release notes](https://github.com/spf13/viper/releases) - [Commits](https://github.com/spf13/viper/compare/v1.20.0...v1.20.1) --- updated-dependencies: - dependency-name: github.com/spf13/viper dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 5 +---- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 4c15860a91..352bae29d9 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,6 @@ module github.com/ubuntu/authd-oidc-brokers go 1.24.0 - -toolchain go1.24.1 - require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 github.com/coreos/go-oidc/v3 v3.13.0 @@ -18,7 +15,7 @@ require ( github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/spf13/cobra v1.9.1 - github.com/spf13/viper v1.20.0 + github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.10.0 github.com/ubuntu/authd v0.4.2-0.20250203155723-a499f298f969 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 diff --git a/go.sum b/go.sum index ab5155496e..6609eb8a8a 100644 --- a/go.sum +++ b/go.sum @@ -97,8 +97,8 @@ github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY= -github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= +github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3 h1:7hth9376EoQEd1hH4lAp3vnaLP2UMyxuMMghLKzDHyU= github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3/go.mod h1:Z5KcoM0YLC7INlNhEezeIZ0TZNYf7WSNO0Lvah4DSeQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= From 5d1a843c7413c76a201ef6075aa635d03e7090c6 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 31 Mar 2025 16:44:06 +0200 Subject: [PATCH 0420/1670] go.mod: Re-add toolchain version which was removed by dependabot --- go.mod | 3 +++ 1 file changed, 3 insertions(+) diff --git a/go.mod b/go.mod index 352bae29d9..c8381c43f1 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,9 @@ module github.com/ubuntu/authd-oidc-brokers go 1.24.0 + +toolchain go1.24.1 + require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 github.com/coreos/go-oidc/v3 v3.13.0 From 0292525854bbf37a726339420c277632b0bafd29 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 08:36:32 +0000 Subject: [PATCH 0421/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go-core Bumps [github.com/microsoftgraph/msgraph-sdk-go-core](https://github.com/microsoftgraph/msgraph-sdk-go-core) from 1.3.1 to 1.3.2. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go-core/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go-core/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go-core/compare/v1.3.1...v1.3.2) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go-core dependency-version: 1.3.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 15 +++++++-------- go.sum | 30 ++++++++++++++---------------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index c8381c43f1..0b260ead97 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible github.com/microsoftgraph/msgraph-sdk-go v1.67.0 - github.com/microsoftgraph/msgraph-sdk-go-core v1.3.1 + github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/spf13/cobra v1.9.1 @@ -30,8 +30,7 @@ require ( ) require ( - github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect - github.com/cjlapao/common-go v0.0.48 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect @@ -43,11 +42,11 @@ require ( github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/microsoft/kiota-abstractions-go v1.9.1 // indirect - github.com/microsoft/kiota-authentication-azure-go v1.2.1 // indirect - github.com/microsoft/kiota-http-go v1.5.1 // indirect + github.com/microsoft/kiota-abstractions-go v1.9.2 // indirect + github.com/microsoft/kiota-authentication-azure-go v1.3.0 // indirect + github.com/microsoft/kiota-http-go v1.5.2 // indirect github.com/microsoft/kiota-serialization-form-go v1.1.1 // indirect - github.com/microsoft/kiota-serialization-json-go v1.1.1 // indirect + github.com/microsoft/kiota-serialization-json-go v1.1.2 // indirect github.com/microsoft/kiota-serialization-multipart-go v1.1.1 // indirect github.com/microsoft/kiota-serialization-text-go v1.1.1 // indirect github.com/otiai10/mint v1.6.3 // indirect @@ -65,7 +64,7 @@ require ( go.opentelemetry.io/otel/metric v1.35.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.37.0 // indirect + golang.org/x/net v0.38.0 // indirect golang.org/x/sync v0.12.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.23.0 // indirect diff --git a/go.sum b/go.sum index 6609eb8a8a..b8e9688db3 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,7 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 h1:DSDNVxqkoXJiko6x8a90zidoYqnYYa6c1MTzDKzKkTo= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1/go.mod h1:zGqV2R4Cr/k8Uye5w+dgQ06WJtEcbQG/8J7BB6hnCr4= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= -github.com/cjlapao/common-go v0.0.48 h1:j2rMlSBoEG8P6WPV6aOoSHAWX6D9fl2jVpAqwnHKB4E= -github.com/cjlapao/common-go v0.0.48/go.mod h1:wWif40/s6IIjaT+BO01Bo6VeYzz7IQ23BOj6xi/pAqw= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.0 h1:Bg8m3nq/X1DeePkAbCfb6ml6F3F0IunEhE8TMh+lY48= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.0/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= github.com/coreos/go-oidc/v3 v3.13.0 h1:M66zd0pcc5VxvBNM4pB331Wrsanby+QomQYjN8HamW8= github.com/coreos/go-oidc/v3 v3.13.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= @@ -53,24 +51,24 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/microsoft/kiota-abstractions-go v1.9.1 h1:vkeluAj3ZLvOvG1NY5HcuBclhA9UXkXPeLxcBhQ28cU= -github.com/microsoft/kiota-abstractions-go v1.9.1/go.mod h1:rlWmh9TR1j4EvkEp1Bz0rYTWunlvHDG9s6IMlkpDNrc= -github.com/microsoft/kiota-authentication-azure-go v1.2.1 h1:CwIMiIF/xFm9l+77hFgIsou5mIDz52tft6xWc6WszgU= -github.com/microsoft/kiota-authentication-azure-go v1.2.1/go.mod h1:+xHS60A+qaouiLaEvOyTZ8H/sVU+y7DNz1bp7ytcUqA= -github.com/microsoft/kiota-http-go v1.5.1 h1:f260q6brp9/xHY4pECVPqzDYwoqieQhhcDiMMGc1ox0= -github.com/microsoft/kiota-http-go v1.5.1/go.mod h1:r8HXDhMyLF4GJYdSmOGRquX3yI8KZpmbFCrTf78/kI0= +github.com/microsoft/kiota-abstractions-go v1.9.2 h1:3U5VgN2YGe3lsu1pyuS0t5jxv1llxX2ophwX8ewE6wQ= +github.com/microsoft/kiota-abstractions-go v1.9.2/go.mod h1:f06pl3qSyvUHEfVNkiRpXPkafx7khZqQEb71hN/pmuU= +github.com/microsoft/kiota-authentication-azure-go v1.3.0 h1:PWH6PgtzhJjnmvR6N1CFjriwX09Kv7S5K3vL6VbPVrg= +github.com/microsoft/kiota-authentication-azure-go v1.3.0/go.mod h1:l/MPGUVvD7xfQ+MYSdZaFPv0CsLDqgSOp8mXwVgArIs= +github.com/microsoft/kiota-http-go v1.5.2 h1:xqvo4ssWwSvCJw2yuRocKFTxm3Y1iN+a4rrhuTYtBWg= +github.com/microsoft/kiota-http-go v1.5.2/go.mod h1:L+5Ri+SzwELnUcNA0cpbFKp/pBbvypLh3Cd1PR6sjx0= github.com/microsoft/kiota-serialization-form-go v1.1.1 h1:Me4mmLB+aukCA1zToqVfT2meCOsmGgAWAUIrlGuteWM= github.com/microsoft/kiota-serialization-form-go v1.1.1/go.mod h1:NbmHzUt2fjQ5nNbQjS6/bBNqoRFtv+ZMtdxEPRVtEJw= -github.com/microsoft/kiota-serialization-json-go v1.1.1 h1:EG1dX7GmBM8UGV1+TwUtepNHthmF8LxJ54w7fsdfn1M= -github.com/microsoft/kiota-serialization-json-go v1.1.1/go.mod h1:qnVWoeGkngHK+YhKilbncI6j7DZ3Jd9QyIRzPTj2hcM= +github.com/microsoft/kiota-serialization-json-go v1.1.2 h1:eJrPWeQ665nbjO0gsHWJ0Bw6V/ZHHU1OfFPaYfRG39k= +github.com/microsoft/kiota-serialization-json-go v1.1.2/go.mod h1:deaGt7fjZarywyp7TOTiRsjfYiyWxwJJPQZytXwYQn8= github.com/microsoft/kiota-serialization-multipart-go v1.1.1 h1:tHmfffhGqd/TvrWh/0V2SU+SzHm+LHq1gKBkQz7i5IA= github.com/microsoft/kiota-serialization-multipart-go v1.1.1/go.mod h1:o36BKK7IhIFptmrHugLsD4cj0xkodw34QXZtkzt3ADc= github.com/microsoft/kiota-serialization-text-go v1.1.1 h1:i9qSjDh99yqkJOVtpASE4PWUU8++kLpP4NXl9ptqezs= github.com/microsoft/kiota-serialization-text-go v1.1.1/go.mod h1:eyBbSGYNJbhjP574eIkAdVcmSdYxm1SiFcI7LbWCT1U= github.com/microsoftgraph/msgraph-sdk-go v1.67.0 h1:DzDuf72hjuxRvtEW0RyqUNpi/EKQ/Sk5XyPwcbaa28g= github.com/microsoftgraph/msgraph-sdk-go v1.67.0/go.mod h1:l1pUApVQBjluW2iBx89g9V9BTG4g8eE0+M0r/IETy4Y= -github.com/microsoftgraph/msgraph-sdk-go-core v1.3.1 h1:vJA+j+1z8c8adjd4YtxzF0L668nmFM9jeXvx2YvK7CA= -github.com/microsoftgraph/msgraph-sdk-go-core v1.3.1/go.mod h1:bjqNFhM9MasKdZ/UQfqtLNVB/uOZPUoPhOwUjmSGOgE= +github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 h1:5jCUSosTKaINzPPQXsz7wsHWwknyBmJSu8+ZWxx3kdQ= +github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2/go.mod h1:iD75MK3LX8EuwjDYCmh0hkojKXK6VKME33u4daCo3cE= github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I= github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs= @@ -132,8 +130,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 69374bc65a88e997ebc61181a1c4704aa6178a88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 08:46:28 +0000 Subject: [PATCH 0422/1670] deps(go): bump golang.org/x/oauth2 from 0.28.0 to 0.29.0 Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.28.0 to 0.29.0. - [Commits](https://github.com/golang/oauth2/compare/v0.28.0...v0.29.0) --- updated-dependencies: - dependency-name: golang.org/x/oauth2 dependency-version: 0.29.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0b260ead97..e3b695cd35 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 golang.org/x/crypto v0.36.0 - golang.org/x/oauth2 v0.28.0 + golang.org/x/oauth2 v0.29.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index b8e9688db3..6f92ef0758 100644 --- a/go.sum +++ b/go.sum @@ -132,8 +132,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= -golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= +golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= From 3cae8ccf7a0ea980b9286b072fcd485feda7c2bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 08:46:30 +0000 Subject: [PATCH 0423/1670] deps(go): bump github.com/Azure/azure-sdk-for-go/sdk/azcore Bumps [github.com/Azure/azure-sdk-for-go/sdk/azcore](https://github.com/Azure/azure-sdk-for-go) from 1.17.1 to 1.18.0. - [Release notes](https://github.com/Azure/azure-sdk-for-go/releases) - [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/release.md) - [Commits](https://github.com/Azure/azure-sdk-for-go/compare/sdk/azcore/v1.17.1...sdk/azcore/v1.18.0) --- updated-dependencies: - dependency-name: github.com/Azure/azure-sdk-for-go/sdk/azcore dependency-version: 1.18.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0b260ead97..7c50538319 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.24.0 toolchain go1.24.1 require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 github.com/coreos/go-oidc/v3 v3.13.0 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/go-jose/go-jose/v4 v4.0.5 diff --git a/go.sum b/go.sum index b8e9688db3..ca19d362e8 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 h1:DSDNVxqkoXJiko6x8a90zidoYqnYYa6c1MTzDKzKkTo= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1/go.mod h1:zGqV2R4Cr/k8Uye5w+dgQ06WJtEcbQG/8J7BB6hnCr4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.0 h1:Bg8m3nq/X1DeePkAbCfb6ml6F3F0IunEhE8TMh+lY48= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.0/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= github.com/coreos/go-oidc/v3 v3.13.0 h1:M66zd0pcc5VxvBNM4pB331Wrsanby+QomQYjN8HamW8= From 1917f8898ca72d48a56d4326fe85418f2ef581eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 08:46:37 +0000 Subject: [PATCH 0424/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.67.0 to 1.67.1. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.67.0...v1.67.1) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-version: 1.67.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 0b260ead97..e705b8c750 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.2 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.67.0 + github.com/microsoftgraph/msgraph-sdk-go v1.67.1 github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 @@ -45,10 +45,10 @@ require ( github.com/microsoft/kiota-abstractions-go v1.9.2 // indirect github.com/microsoft/kiota-authentication-azure-go v1.3.0 // indirect github.com/microsoft/kiota-http-go v1.5.2 // indirect - github.com/microsoft/kiota-serialization-form-go v1.1.1 // indirect + github.com/microsoft/kiota-serialization-form-go v1.1.2 // indirect github.com/microsoft/kiota-serialization-json-go v1.1.2 // indirect - github.com/microsoft/kiota-serialization-multipart-go v1.1.1 // indirect - github.com/microsoft/kiota-serialization-text-go v1.1.1 // indirect + github.com/microsoft/kiota-serialization-multipart-go v1.1.2 // indirect + github.com/microsoft/kiota-serialization-text-go v1.1.2 // indirect github.com/otiai10/mint v1.6.3 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/sagikazarmark/locafero v0.8.0 // indirect diff --git a/go.sum b/go.sum index b8e9688db3..2f94a1b9d7 100644 --- a/go.sum +++ b/go.sum @@ -57,16 +57,16 @@ github.com/microsoft/kiota-authentication-azure-go v1.3.0 h1:PWH6PgtzhJjnmvR6N1C github.com/microsoft/kiota-authentication-azure-go v1.3.0/go.mod h1:l/MPGUVvD7xfQ+MYSdZaFPv0CsLDqgSOp8mXwVgArIs= github.com/microsoft/kiota-http-go v1.5.2 h1:xqvo4ssWwSvCJw2yuRocKFTxm3Y1iN+a4rrhuTYtBWg= github.com/microsoft/kiota-http-go v1.5.2/go.mod h1:L+5Ri+SzwELnUcNA0cpbFKp/pBbvypLh3Cd1PR6sjx0= -github.com/microsoft/kiota-serialization-form-go v1.1.1 h1:Me4mmLB+aukCA1zToqVfT2meCOsmGgAWAUIrlGuteWM= -github.com/microsoft/kiota-serialization-form-go v1.1.1/go.mod h1:NbmHzUt2fjQ5nNbQjS6/bBNqoRFtv+ZMtdxEPRVtEJw= +github.com/microsoft/kiota-serialization-form-go v1.1.2 h1:SD6MATqNw+Dc5beILlsb/D87C36HKC/Zw7l+N9+HY2A= +github.com/microsoft/kiota-serialization-form-go v1.1.2/go.mod h1:m4tY2JT42jAZmgbqFwPy3zGDF+NPJACuyzmjNXeuHio= github.com/microsoft/kiota-serialization-json-go v1.1.2 h1:eJrPWeQ665nbjO0gsHWJ0Bw6V/ZHHU1OfFPaYfRG39k= github.com/microsoft/kiota-serialization-json-go v1.1.2/go.mod h1:deaGt7fjZarywyp7TOTiRsjfYiyWxwJJPQZytXwYQn8= -github.com/microsoft/kiota-serialization-multipart-go v1.1.1 h1:tHmfffhGqd/TvrWh/0V2SU+SzHm+LHq1gKBkQz7i5IA= -github.com/microsoft/kiota-serialization-multipart-go v1.1.1/go.mod h1:o36BKK7IhIFptmrHugLsD4cj0xkodw34QXZtkzt3ADc= -github.com/microsoft/kiota-serialization-text-go v1.1.1 h1:i9qSjDh99yqkJOVtpASE4PWUU8++kLpP4NXl9ptqezs= -github.com/microsoft/kiota-serialization-text-go v1.1.1/go.mod h1:eyBbSGYNJbhjP574eIkAdVcmSdYxm1SiFcI7LbWCT1U= -github.com/microsoftgraph/msgraph-sdk-go v1.67.0 h1:DzDuf72hjuxRvtEW0RyqUNpi/EKQ/Sk5XyPwcbaa28g= -github.com/microsoftgraph/msgraph-sdk-go v1.67.0/go.mod h1:l1pUApVQBjluW2iBx89g9V9BTG4g8eE0+M0r/IETy4Y= +github.com/microsoft/kiota-serialization-multipart-go v1.1.2 h1:1pUyA1QgIeKslQwbk7/ox1TehjlCUUT3r1f8cNlkvn4= +github.com/microsoft/kiota-serialization-multipart-go v1.1.2/go.mod h1:j2K7ZyYErloDu7Kuuk993DsvfoP7LPWvAo7rfDpdPio= +github.com/microsoft/kiota-serialization-text-go v1.1.2 h1:7OfKFlzdjpPygca/+OtqafkEqCWR7+94efUFGC28cLw= +github.com/microsoft/kiota-serialization-text-go v1.1.2/go.mod h1:QNTcswkBPFY3QVBFmzfk00UMNViKQtV0AQKCrRw5ibM= +github.com/microsoftgraph/msgraph-sdk-go v1.67.1 h1:nHLN+Oti70OgzQIKvtlVd+FvikijDKUJVPuhNcS8bSY= +github.com/microsoftgraph/msgraph-sdk-go v1.67.1/go.mod h1:QFVfWlFF9M8C/DEJKnQ2EtqCWIyq7tKwbMjE7HhhUIE= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 h1:5jCUSosTKaINzPPQXsz7wsHWwknyBmJSu8+ZWxx3kdQ= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2/go.mod h1:iD75MK3LX8EuwjDYCmh0hkojKXK6VKME33u4daCo3cE= github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= From a9a8e39322d820189b2185847e2521f2714b4679 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 09:18:42 +0000 Subject: [PATCH 0425/1670] deps(go): bump golang.org/x/crypto from 0.36.0 to 0.37.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.36.0 to 0.37.0. - [Commits](https://github.com/golang/crypto/compare/v0.36.0...v0.37.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.37.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 228fd70e0a..ab1d826822 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/ubuntu/authd v0.4.2-0.20250203155723-a499f298f969 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 - golang.org/x/crypto v0.36.0 + golang.org/x/crypto v0.37.0 golang.org/x/oauth2 v0.29.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 @@ -65,7 +65,7 @@ require ( go.opentelemetry.io/otel/trace v1.35.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/net v0.38.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect ) diff --git a/go.sum b/go.sum index 034ffcdc06..e4d93b1632 100644 --- a/go.sum +++ b/go.sum @@ -124,8 +124,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -136,8 +136,8 @@ golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -145,16 +145,16 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc 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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From ed10a2d3ba90d88b86d9c91945cc6f0f2d88f7d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 09:19:05 +0000 Subject: [PATCH 0426/1670] deps(go): bump github.com/coreos/go-oidc/v3 from 3.13.0 to 3.14.1 Bumps [github.com/coreos/go-oidc/v3](https://github.com/coreos/go-oidc) from 3.13.0 to 3.14.1. - [Release notes](https://github.com/coreos/go-oidc/releases) - [Commits](https://github.com/coreos/go-oidc/compare/v3.13.0...v3.14.1) --- updated-dependencies: - dependency-name: github.com/coreos/go-oidc/v3 dependency-version: 3.14.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 228fd70e0a..52b147e1cc 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.24.1 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 - github.com/coreos/go-oidc/v3 v3.13.0 + github.com/coreos/go-oidc/v3 v3.14.1 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/go-jose/go-jose/v4 v4.0.5 github.com/godbus/dbus/v5 v5.1.0 diff --git a/go.sum b/go.sum index 034ffcdc06..b257a3a800 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.0 h1:Bg8m3nq/X1DeePkAbCfb6ml6F3F0IunEhE8TMh+lY48= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.0/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= -github.com/coreos/go-oidc/v3 v3.13.0 h1:M66zd0pcc5VxvBNM4pB331Wrsanby+QomQYjN8HamW8= -github.com/coreos/go-oidc/v3 v3.13.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU= +github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk= +github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= From 1adf2708969333f5df72bd20ca50e27602d1e9e5 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 7 Apr 2025 13:05:08 +0200 Subject: [PATCH 0427/1670] broker.conf: Improve the force_provider_authentication comment Based on the wording in the user documentation (see https://github.com/ubuntu/authd/pull/870). --- conf/broker.conf | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/conf/broker.conf b/conf/broker.conf index ffb0058485..a94571cf81 100644 --- a/conf/broker.conf +++ b/conf/broker.conf @@ -6,13 +6,14 @@ client_id = ## client secret to authenticate with the provider. #client_secret = -## Always authenticate with the identity provider during login, even if -## a local method (e.g. local password) is used. -## This works by forcing a token refresh at login, which fails if the +## Force remote authentication with the identity provider during login, +## even if a local method (e.g. local password) is used. +## This works by forcing a token refresh during login, which fails if the ## user does not have the necessary permissions in the identity provider. ## -## If set to false (the default), authentication with the identity -## provider is only done if the provider is reachable during login. +## If set to false (the default), remote authentication with the identity +## provider only happens if there is a working internet connection and +## the provider is reachable during login. ## ## Important: Enabling this option prevents authd users from logging in ## if the identity provider is unreachable (e.g. due to network issues). From a8ea6a86293ac7dd006948d8da4dc504751fd221 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 11 Apr 2025 13:16:36 +0200 Subject: [PATCH 0428/1670] Bump Go toolchain version to 1.24.2 govulncheck reports the following vulnerability in go1.24.1 Vulnerability #1: GO-2025-3563 Request smuggling due to acceptance of invalid chunked data in net/http More info: https://pkg.go.dev/vuln/GO-2025-3563 Standard library Found in: net/http/internal@go1.24.1 Fixed in: net/http/internal@go1.24.2 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index e574f8ed36..2aaf6085d8 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/ubuntu/authd-oidc-brokers go 1.24.0 -toolchain go1.24.1 +toolchain go1.24.2 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 From 3439d2b3ebeac72fe51ad1c98fdd26444b96ee45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 08:18:26 +0000 Subject: [PATCH 0429/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.67.1 to 1.68.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.67.1...v1.68.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-version: 1.68.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2aaf6085d8..56a11d135c 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.2 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.67.1 + github.com/microsoftgraph/msgraph-sdk-go v1.68.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 diff --git a/go.sum b/go.sum index b3cab7912c..71aff13b18 100644 --- a/go.sum +++ b/go.sum @@ -65,8 +65,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.1.2 h1:1pUyA1QgIeKslQwb github.com/microsoft/kiota-serialization-multipart-go v1.1.2/go.mod h1:j2K7ZyYErloDu7Kuuk993DsvfoP7LPWvAo7rfDpdPio= github.com/microsoft/kiota-serialization-text-go v1.1.2 h1:7OfKFlzdjpPygca/+OtqafkEqCWR7+94efUFGC28cLw= github.com/microsoft/kiota-serialization-text-go v1.1.2/go.mod h1:QNTcswkBPFY3QVBFmzfk00UMNViKQtV0AQKCrRw5ibM= -github.com/microsoftgraph/msgraph-sdk-go v1.67.1 h1:nHLN+Oti70OgzQIKvtlVd+FvikijDKUJVPuhNcS8bSY= -github.com/microsoftgraph/msgraph-sdk-go v1.67.1/go.mod h1:QFVfWlFF9M8C/DEJKnQ2EtqCWIyq7tKwbMjE7HhhUIE= +github.com/microsoftgraph/msgraph-sdk-go v1.68.0 h1:Tp7wSJBvbiC+3EorP5mnb04w1DH5Qak5le5YOQPdP8c= +github.com/microsoftgraph/msgraph-sdk-go v1.68.0/go.mod h1:5ncg4aauxM5XKHo/xvAq7Cjl6+Dqu6lOtoihSGKtDt4= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 h1:5jCUSosTKaINzPPQXsz7wsHWwknyBmJSu8+ZWxx3kdQ= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2/go.mod h1:iD75MK3LX8EuwjDYCmh0hkojKXK6VKME33u4daCo3cE= github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= From 35ef6730b79cb1bcfd7953c9cd6f323cf11f2f11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 08:18:45 +0000 Subject: [PATCH 0430/1670] deps(go): bump github.com/go-jose/go-jose/v4 from 4.0.5 to 4.1.0 Bumps [github.com/go-jose/go-jose/v4](https://github.com/go-jose/go-jose) from 4.0.5 to 4.1.0. - [Release notes](https://github.com/go-jose/go-jose/releases) - [Changelog](https://github.com/go-jose/go-jose/blob/main/CHANGELOG.md) - [Commits](https://github.com/go-jose/go-jose/compare/v4.0.5...v4.1.0) --- updated-dependencies: - dependency-name: github.com/go-jose/go-jose/v4 dependency-version: 4.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2aaf6085d8..c671f10170 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 github.com/coreos/go-oidc/v3 v3.14.1 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf - github.com/go-jose/go-jose/v4 v4.0.5 + github.com/go-jose/go-jose/v4 v4.1.0 github.com/godbus/dbus/v5 v5.1.0 github.com/golang-jwt/jwt/v5 v5.2.2 github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum index b3cab7912c..be9a987157 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= -github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= +github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY= +github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= From 9a30cbaf45832fbebb70c76e6996945b02ec4724 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 09:38:30 +0000 Subject: [PATCH 0431/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.68.0 to 1.69.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.68.0...v1.69.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-version: 1.69.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 055126ff31..89a29622ea 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.2 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.68.0 + github.com/microsoftgraph/msgraph-sdk-go v1.69.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 diff --git a/go.sum b/go.sum index 2ee8eb6573..85cb8d01d5 100644 --- a/go.sum +++ b/go.sum @@ -65,8 +65,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.1.2 h1:1pUyA1QgIeKslQwb github.com/microsoft/kiota-serialization-multipart-go v1.1.2/go.mod h1:j2K7ZyYErloDu7Kuuk993DsvfoP7LPWvAo7rfDpdPio= github.com/microsoft/kiota-serialization-text-go v1.1.2 h1:7OfKFlzdjpPygca/+OtqafkEqCWR7+94efUFGC28cLw= github.com/microsoft/kiota-serialization-text-go v1.1.2/go.mod h1:QNTcswkBPFY3QVBFmzfk00UMNViKQtV0AQKCrRw5ibM= -github.com/microsoftgraph/msgraph-sdk-go v1.68.0 h1:Tp7wSJBvbiC+3EorP5mnb04w1DH5Qak5le5YOQPdP8c= -github.com/microsoftgraph/msgraph-sdk-go v1.68.0/go.mod h1:5ncg4aauxM5XKHo/xvAq7Cjl6+Dqu6lOtoihSGKtDt4= +github.com/microsoftgraph/msgraph-sdk-go v1.69.0 h1:DVh6hIwOXxdI4pFocKC8YetJOhQamDkJC5z6BjtivmE= +github.com/microsoftgraph/msgraph-sdk-go v1.69.0/go.mod h1:5ncg4aauxM5XKHo/xvAq7Cjl6+Dqu6lOtoihSGKtDt4= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 h1:5jCUSosTKaINzPPQXsz7wsHWwknyBmJSu8+ZWxx3kdQ= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2/go.mod h1:iD75MK3LX8EuwjDYCmh0hkojKXK6VKME33u4daCo3cE= github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= From 211737aff5c44b24917d2cb50ff609dfab92e894 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 7 May 2025 15:33:54 +0200 Subject: [PATCH 0432/1670] Improve error message shown when user is not allowed Multiple users were confused by the default behavior that only the first user is allowed to log in and all other users get a permission denied. The documentation was improved in https://github.com/ubuntu/authd/pull/888. This commit also improves the error message that's shown when the user is not allowed, to explain better why the login failed. --- internal/broker/broker.go | 4 ++-- .../first_auth | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index d31fb76874..6e7766d14e 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -571,7 +571,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au if !b.userNameIsAllowed(authInfo.UserInfo.Name) { log.Warningf(context.Background(), "User %q is not in the list of allowed users", authInfo.UserInfo.Name) - return AuthDenied, errorMessage{Message: "permission denied"} + return AuthDenied, errorMessage{Message: "user not allowed in broker configuration"} } session.authInfo["auth_info"] = authInfo @@ -685,7 +685,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au if !b.userNameIsAllowed(authInfo.UserInfo.Name) { log.Warningf(context.Background(), "User %q is not in the list of allowed users", authInfo.UserInfo.Name) - return AuthDenied, errorMessage{Message: "permission denied"} + return AuthDenied, errorMessage{Message: "user not allowed in broker configuration"} } if session.isOffline { diff --git a/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/first_auth b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/first_auth index ffd251e4ae..6779bc41c8 100644 --- a/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/first_auth +++ b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/first_auth @@ -1,3 +1,3 @@ access: denied -data: '{"message":"authentication failure: permission denied"}' +data: '{"message":"authentication failure: user not allowed in broker configuration"}' err: From 089b7fe8785f1e86cf60f275216ea60ef63c3a79 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 7 May 2025 15:38:01 +0200 Subject: [PATCH 0433/1670] Improve log message printed when user is not allowed Explain how the user can be added to the list of allowed users. --- internal/broker/broker.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 6e7766d14e..8c84318714 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -570,7 +570,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au } if !b.userNameIsAllowed(authInfo.UserInfo.Name) { - log.Warningf(context.Background(), "User %q is not in the list of allowed users", authInfo.UserInfo.Name) + log.Warning(context.Background(), b.userNotAllowedLogMsg(authInfo.UserInfo.Name)) return AuthDenied, errorMessage{Message: "user not allowed in broker configuration"} } @@ -684,7 +684,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au } if !b.userNameIsAllowed(authInfo.UserInfo.Name) { - log.Warningf(context.Background(), "User %q is not in the list of allowed users", authInfo.UserInfo.Name) + log.Warning(context.Background(), b.userNotAllowedLogMsg(authInfo.UserInfo.Name)) return AuthDenied, errorMessage{Message: "user not allowed in broker configuration"} } @@ -709,6 +709,12 @@ func (b *Broker) userNameIsAllowed(userName string) bool { return b.cfg.userNameIsAllowed(b.provider.NormalizeUsername(userName)) } +func (b *Broker) userNotAllowedLogMsg(userName string) string { + logMsg := fmt.Sprintf("User %q is not in the list of allowed users.", userName) + logMsg += fmt.Sprintf("\nYou can add the user to allowed_users in %s", b.cfg.ConfigFile) + return logMsg +} + func (b *Broker) startAuthenticate(sessionID string) (context.Context, error) { session, err := b.getSession(sessionID) if err != nil { From a2b383ae19c4b0c74183ed4c82e0fb0d9a8d291d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 08:25:40 +0000 Subject: [PATCH 0434/1670] deps(go): bump golang.org/x/oauth2 from 0.29.0 to 0.30.0 Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.29.0 to 0.30.0. - [Commits](https://github.com/golang/oauth2/compare/v0.29.0...v0.30.0) --- updated-dependencies: - dependency-name: golang.org/x/oauth2 dependency-version: 0.30.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 89a29622ea..1685d05152 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 golang.org/x/crypto v0.37.0 - golang.org/x/oauth2 v0.29.0 + golang.org/x/oauth2 v0.30.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 85cb8d01d5..0394099334 100644 --- a/go.sum +++ b/go.sum @@ -132,8 +132,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= -golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= From 07995b23bff24a98ef94e8cc1fd98e3c25071a06 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 08:45:25 +0000 Subject: [PATCH 0435/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.69.0 to 1.71.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.69.0...v1.71.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-version: 1.71.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 89a29622ea..8317222709 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.2 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.69.0 + github.com/microsoftgraph/msgraph-sdk-go v1.71.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 diff --git a/go.sum b/go.sum index 85cb8d01d5..17986fa22e 100644 --- a/go.sum +++ b/go.sum @@ -65,8 +65,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.1.2 h1:1pUyA1QgIeKslQwb github.com/microsoft/kiota-serialization-multipart-go v1.1.2/go.mod h1:j2K7ZyYErloDu7Kuuk993DsvfoP7LPWvAo7rfDpdPio= github.com/microsoft/kiota-serialization-text-go v1.1.2 h1:7OfKFlzdjpPygca/+OtqafkEqCWR7+94efUFGC28cLw= github.com/microsoft/kiota-serialization-text-go v1.1.2/go.mod h1:QNTcswkBPFY3QVBFmzfk00UMNViKQtV0AQKCrRw5ibM= -github.com/microsoftgraph/msgraph-sdk-go v1.69.0 h1:DVh6hIwOXxdI4pFocKC8YetJOhQamDkJC5z6BjtivmE= -github.com/microsoftgraph/msgraph-sdk-go v1.69.0/go.mod h1:5ncg4aauxM5XKHo/xvAq7Cjl6+Dqu6lOtoihSGKtDt4= +github.com/microsoftgraph/msgraph-sdk-go v1.71.0 h1:vwkN8ebJmYv4KuXcrZTiKP1vhC0sCez8z5/MuL0ykZo= +github.com/microsoftgraph/msgraph-sdk-go v1.71.0/go.mod h1:5ncg4aauxM5XKHo/xvAq7Cjl6+Dqu6lOtoihSGKtDt4= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 h1:5jCUSosTKaINzPPQXsz7wsHWwknyBmJSu8+ZWxx3kdQ= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2/go.mod h1:iD75MK3LX8EuwjDYCmh0hkojKXK6VKME33u4daCo3cE= github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= From 12a8685fe0133dadfaaf41158e50f0d14b62c360 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 27 May 2025 11:43:20 +0200 Subject: [PATCH 0436/1670] Remove confusing log message "No configuration file" The log message refers to an undocumented config file and is confusing for users. refs: https://github.com/ubuntu/authd-oidc-brokers/issues/530 --- cmd/authd-oidc/daemon/config.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/cmd/authd-oidc/daemon/config.go b/cmd/authd-oidc/daemon/config.go index 1a7cf15079..3663367d9d 100644 --- a/cmd/authd-oidc/daemon/config.go +++ b/cmd/authd-oidc/daemon/config.go @@ -46,16 +46,12 @@ func initViperConfig(name string, cmd *cobra.Command, vip *viper.Viper) (err err } } - if err := vip.ReadInConfig(); err != nil { - var e viper.ConfigFileNotFoundError - if errors.As(err, &e) { - log.Infof(context.Background(), "No configuration file: %v.\nWe will only use the defaults, env variables or flags.", e) - } else { - return fmt.Errorf("invalid configuration file: %w", err) - } - } else { - log.Infof(context.Background(), "Using configuration file: %v", vip.ConfigFileUsed()) + var configNotFoundErr viper.ConfigFileNotFoundError + err = vip.ReadInConfig() + if err != nil && !errors.As(err, &configNotFoundErr) { + return fmt.Errorf("invalid configuration file: %w", err) } + log.Infof(context.Background(), "Using configuration file: %v", vip.ConfigFileUsed()) // Handle environment. vip.SetEnvPrefix(name) From 8ddf4339f3e47946303024e9846e2b262ab48d4a Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 23 May 2025 19:28:59 +0200 Subject: [PATCH 0437/1670] Fix `version` command trying to migrate The PersistentPreRunE method did things which should not be done for subcommands like `version`. We don't need to parse the config file to print the authd version, and we definitely don't need to migrate the database. Use PreRunE instead, which is not executed for subcommands. --- cmd/authd/daemon/daemon.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/authd/daemon/daemon.go b/cmd/authd/daemon/daemon.go index 1786a0cc95..c6f97f2323 100644 --- a/cmd/authd/daemon/daemon.go +++ b/cmd/authd/daemon/daemon.go @@ -56,12 +56,14 @@ func New() *App { Short:/*i18n.G(*/ "Authentication daemon", /*)*/ Long:/*i18n.G(*/ "Authentication daemon bridging the system with external brokers.", /*)*/ Args: cobra.NoArgs, - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + PersistentPreRun: func(cmd *cobra.Command, args []string) { // First thing, initialize the journal handler log.InitJournalHandler(false) // Command parsing has been successful. Returns to not print usage anymore. a.rootCmd.SilenceUsage = true + }, + PreRunE: func(cmd *cobra.Command, args []string) error { // TODO: before or after? cmd.LocalFlags() // Set config defaults From b3de1287192845706535dd43f11e4f6c762f74d6 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 3 Jun 2025 14:56:15 +0200 Subject: [PATCH 0438/1670] Add --check-config flag This can be used to make the daemon only parse the config and exit, which the version command was used for before in tests. --- cmd/authd/daemon/daemon.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/cmd/authd/daemon/daemon.go b/cmd/authd/daemon/daemon.go index c6f97f2323..c858d49299 100644 --- a/cmd/authd/daemon/daemon.go +++ b/cmd/authd/daemon/daemon.go @@ -63,9 +63,7 @@ func New() *App { // Command parsing has been successful. Returns to not print usage anymore. a.rootCmd.SilenceUsage = true }, - PreRunE: func(cmd *cobra.Command, args []string) error { - // TODO: before or after? cmd.LocalFlags() - + RunE: func(cmd *cobra.Command, args []string) error { // Set config defaults a.config = daemonConfig{ Paths: systemPaths{ @@ -87,6 +85,11 @@ func New() *App { setVerboseMode(a.config.Verbosity) log.Debugf(context.Background(), "Verbosity: %d", a.config.Verbosity) + // If we are only checking the configuration, we exit now. + if check, _ := cmd.Flags().GetBool("check-config"); check { + return nil + } + if err := maybeMigrateOldDBDir(oldDBDir, a.config.Paths.Database); err != nil { return err } @@ -95,9 +98,6 @@ func New() *App { return err } - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { return a.serve(a.config) }, // We display usage error ourselves @@ -109,6 +109,8 @@ func New() *App { installVerbosityFlag(&a.rootCmd, a.viper) installConfigFlag(&a.rootCmd) + // Install the --check-config flag to check the configuration and exit. + a.rootCmd.Flags().Bool("check-config", false /*i18n.G(*/, "check configuration and exit" /*)*/) // subcommands a.installVersion() From de6c5914f4f41041bd9408b0d6c008ed98c85586 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 3 Jun 2025 15:00:22 +0200 Subject: [PATCH 0439/1670] Use --check-config option in tests The version command does not parse the config anymore. --- cmd/authd/daemon/daemon_test.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cmd/authd/daemon/daemon_test.go b/cmd/authd/daemon/daemon_test.go index 7c703fe7ec..cb51c23b87 100644 --- a/cmd/authd/daemon/daemon_test.go +++ b/cmd/authd/daemon/daemon_test.go @@ -314,8 +314,8 @@ func TestAutoDetectConfig(t *testing.T) { func TestNoConfigSetDefaults(t *testing.T) { a := daemon.New() - // Use version to still run preExec to load no config but without running server - a.SetArgs("version") + + a.SetArgs("--check-config") err := a.Run() require.NoError(t, err, "Run should not return an error") @@ -328,8 +328,7 @@ func TestNoConfigSetDefaults(t *testing.T) { func TestBadConfigReturnsError(t *testing.T) { a := daemon.New() - // Use version to still run preExec to load no config but without running server - a.SetArgs("version", "--config", "/does/not/exist.yaml") + a.SetArgs("--check-config", "--config", "/does/not/exist.yaml") err := a.Run() require.Error(t, err, "Run should return an error on config file") From b1ffa2e6f0a5868cf36fc6c7b328b9b0b43c3c0b Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 5 Jun 2025 11:26:07 +0200 Subject: [PATCH 0440/1670] Add context to error message The error message returned as isAuthenticatedDataResponse is never logged. As a result, the errors we log miss context, so it's hard to figure out where in the code they originate from. Therefore, we add some context to those error messages here. --- internal/broker/broker.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 8c84318714..6d4edc77e0 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -521,7 +521,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au // Decrypt secret if present. secret, err := decodeRawSecret(b.privateKey, rawSecret) if err != nil { - log.Error(context.Background(), err.Error()) + log.Errorf(context.Background(), "could not decode secret: %s", err) return AuthRetry, errorMessage{Message: "could not decode secret"} } @@ -541,12 +541,12 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au defer cancel() t, err := session.oauth2Config.DeviceAccessToken(expiryCtx, response, b.provider.AuthOptions()...) if err != nil { - log.Error(context.Background(), err.Error()) + log.Errorf(context.Background(), "could not authenticate user remotely: %s", err) return AuthRetry, errorMessage{Message: "could not authenticate user remotely"} } if err = b.provider.CheckTokenScopes(t); err != nil { - log.Warning(context.Background(), err.Error()) + log.Warningf(context.Background(), "error checking token scopes: %s", err) } rawIDToken, ok := t.Extra("id_token").(string) @@ -559,13 +559,13 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au authInfo.ProviderMetadata, err = b.provider.GetMetadata(session.oidcServer) if err != nil { - log.Error(context.Background(), err.Error()) + log.Errorf(context.Background(), "could not get provider metadata: %s", err) return AuthDenied, errorMessage{Message: "could not get provider metadata"} } authInfo.UserInfo, err = b.fetchUserInfo(ctx, session, &authInfo) if err != nil { - log.Error(context.Background(), err.Error()) + log.Errorf(context.Background(), "could not fetch user info: %s", err) return AuthDenied, errorMessageForDisplay(err, "could not fetch user info") } From 4306cbf30d37f3310f73baec2ab26ddcf77229e1 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 5 Jun 2025 11:28:15 +0200 Subject: [PATCH 0441/1670] Print debug log statements with the device code expiry time We're seeing some context deadline errors when sending requests to the token endpoint, which might be due to the device code expiry time from the device auth response. Let's add some log statements to help debug this. --- internal/broker/broker.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 6d4edc77e0..acb30134dc 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -536,6 +536,9 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au if response.Expiry.IsZero() { response.Expiry = time.Now().Add(time.Hour) + log.Debugf(context.Background(), "Device code does not have an expiry time, using default of %s", response.Expiry) + } else { + log.Debugf(context.Background(), "Device code expiry time: %s", response.Expiry) } expiryCtx, cancel := context.WithDeadline(ctx, response.Expiry) defer cancel() From 05d3658deea0e1c2aec6bcdc2626cc1dd569e605 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 08:31:48 +0000 Subject: [PATCH 0442/1670] deps(go-tools): bump golang.org/x/mod from 0.24.0 to 0.25.0 in /tools Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.24.0 to 0.25.0. - [Commits](https://github.com/golang/mod/compare/v0.24.0...v0.25.0) --- updated-dependencies: - dependency-name: golang.org/x/mod dependency-version: 0.25.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- tools/go.mod | 2 +- tools/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index d650fe97e3..b2fb7adfa1 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -6,7 +6,7 @@ toolchain go1.24.1 require ( github.com/golangci/golangci-lint v1.64.8 - golang.org/x/mod v0.24.0 + golang.org/x/mod v0.25.0 ) require ( diff --git a/tools/go.sum b/tools/go.sum index 0c52f62f0b..c5cc590ec3 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -653,8 +653,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= -golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= From da561fb95c7710003adcf3ca156bb6e1841c3514 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 08:34:31 +0000 Subject: [PATCH 0443/1670] deps(go): bump golang.org/x/crypto from 0.37.0 to 0.39.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.37.0 to 0.39.0. - [Commits](https://github.com/golang/crypto/compare/v0.37.0...v0.39.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.39.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 51de70317a..46d0beee82 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/ubuntu/authd v0.4.2-0.20250203155723-a499f298f969 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 - golang.org/x/crypto v0.37.0 + golang.org/x/crypto v0.39.0 golang.org/x/oauth2 v0.30.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 @@ -65,7 +65,7 @@ require ( go.opentelemetry.io/otel/trace v1.35.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/net v0.38.0 // indirect - golang.org/x/sync v0.13.0 // indirect - golang.org/x/sys v0.32.0 // indirect - golang.org/x/text v0.24.0 // indirect + golang.org/x/sync v0.15.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.26.0 // indirect ) diff --git a/go.sum b/go.sum index 81562db0bb..b3c81a1b5f 100644 --- a/go.sum +++ b/go.sum @@ -124,8 +124,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -136,8 +136,8 @@ golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -145,16 +145,16 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc 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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From 08850072bf31e6563a06a9290e1f1ed2b1e4850d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 08:39:20 +0000 Subject: [PATCH 0444/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.71.0 to 1.73.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.71.0...v1.73.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-version: 1.73.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 51de70317a..8a2f8daf6d 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.2 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.71.0 + github.com/microsoftgraph/msgraph-sdk-go v1.73.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 diff --git a/go.sum b/go.sum index 81562db0bb..bccf2f8fed 100644 --- a/go.sum +++ b/go.sum @@ -65,8 +65,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.1.2 h1:1pUyA1QgIeKslQwb github.com/microsoft/kiota-serialization-multipart-go v1.1.2/go.mod h1:j2K7ZyYErloDu7Kuuk993DsvfoP7LPWvAo7rfDpdPio= github.com/microsoft/kiota-serialization-text-go v1.1.2 h1:7OfKFlzdjpPygca/+OtqafkEqCWR7+94efUFGC28cLw= github.com/microsoft/kiota-serialization-text-go v1.1.2/go.mod h1:QNTcswkBPFY3QVBFmzfk00UMNViKQtV0AQKCrRw5ibM= -github.com/microsoftgraph/msgraph-sdk-go v1.71.0 h1:vwkN8ebJmYv4KuXcrZTiKP1vhC0sCez8z5/MuL0ykZo= -github.com/microsoftgraph/msgraph-sdk-go v1.71.0/go.mod h1:5ncg4aauxM5XKHo/xvAq7Cjl6+Dqu6lOtoihSGKtDt4= +github.com/microsoftgraph/msgraph-sdk-go v1.73.0 h1:YPkpIokCWEZL8e/uRDmRG6VgaQYQ4GAh+BKdSNpCxy8= +github.com/microsoftgraph/msgraph-sdk-go v1.73.0/go.mod h1:5ncg4aauxM5XKHo/xvAq7Cjl6+Dqu6lOtoihSGKtDt4= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 h1:5jCUSosTKaINzPPQXsz7wsHWwknyBmJSu8+ZWxx3kdQ= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2/go.mod h1:iD75MK3LX8EuwjDYCmh0hkojKXK6VKME33u4daCo3cE= github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= From 625de2ac23ab69bb1dd50deeb752edbcc0102a81 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Jun 2025 11:38:22 +0000 Subject: [PATCH 0445/1670] deps(go): bump github.com/ubuntu/authd Bumps [github.com/ubuntu/authd](https://github.com/ubuntu/authd) from 0.4.2-0.20250203155723-a499f298f969 to 0.5.3. - [Commits](https://github.com/ubuntu/authd/commits/v0.5.3) --- updated-dependencies: - dependency-name: github.com/ubuntu/authd dependency-version: 0.5.3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ae35c098c0..659a0ce472 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/spf13/cobra v1.9.1 github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.10.0 - github.com/ubuntu/authd v0.4.2-0.20250203155723-a499f298f969 + github.com/ubuntu/authd v0.5.3 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 golang.org/x/crypto v0.39.0 diff --git a/go.sum b/go.sum index 3a1010b6dc..98e73604a4 100644 --- a/go.sum +++ b/go.sum @@ -105,8 +105,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/ubuntu/authd v0.4.2-0.20250203155723-a499f298f969 h1:93TjnT/gtCY09U2BY0sCSvCgpBNzjgtTNLOmSzGp7ek= -github.com/ubuntu/authd v0.4.2-0.20250203155723-a499f298f969/go.mod h1:QUhHmd7fuMzHkwGu41k3C/D+ul1+9ZnpUdFtGoOtsU4= +github.com/ubuntu/authd v0.5.3 h1:/R+K/KJvF0vMrpJPipRxYPgtn5IgZKxlXMnl23MfOqk= +github.com/ubuntu/authd v0.5.3/go.mod h1:ZKAUqzql1ekDc1T3aw/hDhrPEKhkbq2MPFe3LFqulV8= github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 h1:qO8m+4mLbo1HRpD5lfhEfr7R1PuqZvbAmjaRzYEy+tM= github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5/go.mod h1:PUpwIgUuCQyuCz/gwiq6WYbo7IvtXXd8JqL01ez+jZE= github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 h1:CA2dVorxvzdsGtszqhSjyvkrXxZi4bS52ZKvP0Ko634= From 62656fec5e21563c232f61f5f96de0a113c8b12d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Jun 2025 11:41:07 +0000 Subject: [PATCH 0446/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.73.0 to 1.74.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.73.0...v1.74.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-version: 1.74.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ae35c098c0..74664f2022 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.2 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.73.0 + github.com/microsoftgraph/msgraph-sdk-go v1.74.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 diff --git a/go.sum b/go.sum index 3a1010b6dc..7bc8ca4cf5 100644 --- a/go.sum +++ b/go.sum @@ -65,8 +65,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.1.2 h1:1pUyA1QgIeKslQwb github.com/microsoft/kiota-serialization-multipart-go v1.1.2/go.mod h1:j2K7ZyYErloDu7Kuuk993DsvfoP7LPWvAo7rfDpdPio= github.com/microsoft/kiota-serialization-text-go v1.1.2 h1:7OfKFlzdjpPygca/+OtqafkEqCWR7+94efUFGC28cLw= github.com/microsoft/kiota-serialization-text-go v1.1.2/go.mod h1:QNTcswkBPFY3QVBFmzfk00UMNViKQtV0AQKCrRw5ibM= -github.com/microsoftgraph/msgraph-sdk-go v1.73.0 h1:YPkpIokCWEZL8e/uRDmRG6VgaQYQ4GAh+BKdSNpCxy8= -github.com/microsoftgraph/msgraph-sdk-go v1.73.0/go.mod h1:5ncg4aauxM5XKHo/xvAq7Cjl6+Dqu6lOtoihSGKtDt4= +github.com/microsoftgraph/msgraph-sdk-go v1.74.0 h1:Qp8IQcMbTMwjA8J8MrGab4wgB2qHDUgp04DlKjNJFyk= +github.com/microsoftgraph/msgraph-sdk-go v1.74.0/go.mod h1:5ncg4aauxM5XKHo/xvAq7Cjl6+Dqu6lOtoihSGKtDt4= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 h1:5jCUSosTKaINzPPQXsz7wsHWwknyBmJSu8+ZWxx3kdQ= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2/go.mod h1:iD75MK3LX8EuwjDYCmh0hkojKXK6VKME33u4daCo3cE= github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= From 68d6da82ff62f93e23423b7531b29544b7afb003 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 12 Jun 2025 15:06:28 +0200 Subject: [PATCH 0447/1670] Bump Go toolchain version to 1.24.4 govulncheck reports the following vulnerabilities in go1.24.2 Vulnerability #1: GO-2025-3751 Sensitive headers not cleared on cross-origin redirect in net/http More info: https://pkg.go.dev/vuln/GO-2025-3751 Standard library Found in: net/http@go1.24.2 Fixed in: net/http@go1.24.4 Vulnerability #2: GO-2025-3750 Inconsistent handling of O_CREATE|O_EXCL on Unix and Windows in os in syscall More info: https://pkg.go.dev/vuln/GO-2025-3750 Standard library Found in: os@go1.24.2 Fixed in: os@go1.24.4 Platforms: windows --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 99c2a579d1..f42288f8d7 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/ubuntu/authd-oidc-brokers go 1.24.0 -toolchain go1.24.2 +toolchain go1.24.4 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 From b7a3701b1fe8de14c7b92d64a101743665509621 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 16 Jun 2025 15:44:49 +0200 Subject: [PATCH 0448/1670] Fix TestSSHAuthenticate The assertion that a user's UID and GID are equal failed for some testdata. --- ..._0.4.1_bbolt_with_mixed_case_users.db.yaml | 112 +++++++++--------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/pam/integration-tests/testdata/db/authd_0.4.1_bbolt_with_mixed_case_users.db.yaml b/pam/integration-tests/testdata/db/authd_0.4.1_bbolt_with_mixed_case_users.db.yaml index 5417dcf389..568fc1bedb 100644 --- a/pam/integration-tests/testdata/db/authd_0.4.1_bbolt_with_mixed_case_users.db.yaml +++ b/pam/integration-tests/testdata/db/authd_0.4.1_bbolt_with_mixed_case_users.db.yaml @@ -1,20 +1,20 @@ GroupByID: - "1060856841": '{"Name":"user-local-groups-UPPER-CASE","GID":1060856841,"UGID":"user-local-groups-UPPER-CASE"}' - "1100583035": '{"Name":"user-integration-WITH-Mixed-CaSe","GID":1100583035,"UGID":"user-integration-WITH-Mixed-CaSe"}' + "1674497680": '{"Name":"user-local-groups-UPPER-CASE","GID":1674497680,"UGID":"user-local-groups-UPPER-CASE"}' + "1031497592": '{"Name":"user-integration-WITH-Mixed-CaSe","GID":1031497592,"UGID":"user-integration-WITH-Mixed-CaSe"}' "1118334210": '{"Name":"group-user-pre-check","GID":1118334210,"UGID":"ugid-user-pre-check"}' "1127261046": '{"Name":"group-user-local-groups-lower-case","GID":1127261046,"UGID":"ugid-user-local-groups-lower-case"}' "1147556396": '{"Name":"group-user-integration-cached","GID":1147556396,"UGID":"ugid-user-integration-cached"}' - "1204499006": '{"Name":"user-integration-pre-check-WITH-UPPER-CASE","GID":1204499006,"UGID":"user-integration-pre-check-WITH-UPPER-CASE"}' - "1305862567": '{"Name":"user-integration-cached","GID":1305862567,"UGID":"user-integration-cached"}' + "1668487632": '{"Name":"user-integration-pre-check-WITH-UPPER-CASE","GID":1668487632,"UGID":"user-integration-pre-check-WITH-UPPER-CASE"}' + "1448654198": '{"Name":"user-integration-cached","GID":1448654198,"UGID":"user-integration-cached"}' "1308355719": '{"Name":"group-user-integration-WITH-Mixed-CaSe","GID":1308355719,"UGID":"ugid-user-integration-with-mixed-case"}' - "1308629880": '{"Name":"user-pre-check","GID":1308629880,"UGID":"user-pre-check"}' + "1974679960": '{"Name":"user-pre-check","GID":1974679960,"UGID":"user-pre-check"}' "1485242865": '{"Name":"group-user-integration-pre-check-WITH-UPPER-CASE","GID":1485242865,"UGID":"ugid-user-integration-pre-check-WITH-UPPER-CASE"}' "1637945796": '{"Name":"group-user-local-groups-UPPER-CASE","GID":1637945796,"UGID":"ugid-user-local-groups-UPPER-CASE"}' - "1722096224": '{"Name":"user-local-groups-lower-case","GID":1722096224,"UGID":"user-local-groups-lower-case"}' + "1690031958": '{"Name":"user-local-groups-lower-case","GID":1690031958,"UGID":"user-local-groups-lower-case"}' "1726859569": '{"Name":"group-user-integration-UPPER-CASE","GID":1726859569,"UGID":"ugid-user-integration-upper-case"}' "1888170398": '{"Name":"group-user-local-groups-Mixed-Case","GID":1888170398,"UGID":"ugid-user-local-groups-Mixed-Case"}' - "1926585439": '{"Name":"user-integration-UPPER-CASE","GID":1926585439,"UGID":"user-integration-UPPER-CASE"}' - "1974061242": '{"Name":"user-local-groups-Mixed-Case","GID":1974061242,"UGID":"user-local-groups-Mixed-Case"}' + "1911746559": '{"Name":"user-integration-UPPER-CASE","GID":1911746559,"UGID":"user-integration-UPPER-CASE"}' + "1047623009": '{"Name":"user-local-groups-Mixed-Case","GID":1047623009,"UGID":"user-local-groups-Mixed-Case"}' GroupByName: group-user-integration-UPPER-CASE: '{"Name":"group-user-integration-UPPER-CASE","GID":1726859569,"UGID":"ugid-user-integration-upper-case"}' group-user-integration-WITH-Mixed-CaSe: '{"Name":"group-user-integration-WITH-Mixed-CaSe","GID":1308355719,"UGID":"ugid-user-integration-with-mixed-case"}' @@ -24,14 +24,14 @@ GroupByName: group-user-local-groups-UPPER-CASE: '{"Name":"group-user-local-groups-UPPER-CASE","GID":1637945796,"UGID":"ugid-user-local-groups-UPPER-CASE"}' group-user-local-groups-lower-case: '{"Name":"group-user-local-groups-lower-case","GID":1127261046,"UGID":"ugid-user-local-groups-lower-case"}' group-user-pre-check: '{"Name":"group-user-pre-check","GID":1118334210,"UGID":"ugid-user-pre-check"}' - user-integration-UPPER-CASE: '{"Name":"user-integration-UPPER-CASE","GID":1926585439,"UGID":"user-integration-UPPER-CASE"}' - user-integration-WITH-Mixed-CaSe: '{"Name":"user-integration-WITH-Mixed-CaSe","GID":1100583035,"UGID":"user-integration-WITH-Mixed-CaSe"}' - user-integration-cached: '{"Name":"user-integration-cached","GID":1305862567,"UGID":"user-integration-cached"}' - user-integration-pre-check-WITH-UPPER-CASE: '{"Name":"user-integration-pre-check-WITH-UPPER-CASE","GID":1204499006,"UGID":"user-integration-pre-check-WITH-UPPER-CASE"}' - user-local-groups-Mixed-Case: '{"Name":"user-local-groups-Mixed-Case","GID":1974061242,"UGID":"user-local-groups-Mixed-Case"}' - user-local-groups-UPPER-CASE: '{"Name":"user-local-groups-UPPER-CASE","GID":1060856841,"UGID":"user-local-groups-UPPER-CASE"}' - user-local-groups-lower-case: '{"Name":"user-local-groups-lower-case","GID":1722096224,"UGID":"user-local-groups-lower-case"}' - user-pre-check: '{"Name":"user-pre-check","GID":1308629880,"UGID":"user-pre-check"}' + user-integration-UPPER-CASE: '{"Name":"user-integration-UPPER-CASE","GID":1911746559,"UGID":"user-integration-UPPER-CASE"}' + user-integration-WITH-Mixed-CaSe: '{"Name":"user-integration-WITH-Mixed-CaSe","GID":1031497592,"UGID":"user-integration-WITH-Mixed-CaSe"}' + user-integration-cached: '{"Name":"user-integration-cached","GID":1448654198,"UGID":"user-integration-cached"}' + user-integration-pre-check-WITH-UPPER-CASE: '{"Name":"user-integration-pre-check-WITH-UPPER-CASE","GID":1668487632,"UGID":"user-integration-pre-check-WITH-UPPER-CASE"}' + user-local-groups-Mixed-Case: '{"Name":"user-local-groups-Mixed-Case","GID":1047623009,"UGID":"user-local-groups-Mixed-Case"}' + user-local-groups-UPPER-CASE: '{"Name":"user-local-groups-UPPER-CASE","GID":1674497680,"UGID":"user-local-groups-UPPER-CASE"}' + user-local-groups-lower-case: '{"Name":"user-local-groups-lower-case","GID":1690031958,"UGID":"user-local-groups-lower-case"}' + user-pre-check: '{"Name":"user-pre-check","GID":1974679960,"UGID":"user-pre-check"}' GroupByUGID: ugid-user-integration-upper-case: '{"Name":"group-user-integration-UPPER-CASE","GID":1726859569,"UGID":"ugid-user-integration-upper-case"}' @@ -42,49 +42,49 @@ GroupByUGID: ugid-user-local-groups-UPPER-CASE: '{"Name":"group-user-local-groups-UPPER-CASE","GID":1637945796,"UGID":"ugid-user-local-groups-UPPER-CASE"}' ugid-user-local-groups-lower-case: '{"Name":"group-user-local-groups-lower-case","GID":1127261046,"UGID":"ugid-user-local-groups-lower-case"}' ugid-user-pre-check: '{"Name":"group-user-pre-check","GID":1118334210,"UGID":"ugid-user-pre-check"}' - user-integration-UPPER-CASE: '{"Name":"user-integration-UPPER-CASE","GID":1926585439,"UGID":"user-integration-UPPER-CASE"}' - user-integration-WITH-Mixed-CaSe: '{"Name":"user-integration-WITH-Mixed-CaSe","GID":1100583035,"UGID":"user-integration-WITH-Mixed-CaSe"}' - user-integration-cached: '{"Name":"user-integration-cached","GID":1305862567,"UGID":"user-integration-cached"}' - user-integration-pre-check-WITH-UPPER-CASE: '{"Name":"user-integration-pre-check-WITH-UPPER-CASE","GID":1204499006,"UGID":"user-integration-pre-check-WITH-UPPER-CASE"}' - user-local-groups-Mixed-Case: '{"Name":"user-local-groups-Mixed-Case","GID":1974061242,"UGID":"user-local-groups-Mixed-Case"}' - user-local-groups-UPPER-CASE: '{"Name":"user-local-groups-UPPER-CASE","GID":1060856841,"UGID":"user-local-groups-UPPER-CASE"}' - user-local-groups-lower-case: '{"Name":"user-local-groups-lower-case","GID":1722096224,"UGID":"user-local-groups-lower-case"}' - user-pre-check: '{"Name":"user-pre-check","GID":1308629880,"UGID":"user-pre-check"}' + user-integration-UPPER-CASE: '{"Name":"user-integration-UPPER-CASE","GID":1911746559,"UGID":"user-integration-UPPER-CASE"}' + user-integration-WITH-Mixed-CaSe: '{"Name":"user-integration-WITH-Mixed-CaSe","GID":1031497592,"UGID":"user-integration-WITH-Mixed-CaSe"}' + user-integration-cached: '{"Name":"user-integration-cached","GID":1448654198,"UGID":"user-integration-cached"}' + user-integration-pre-check-WITH-UPPER-CASE: '{"Name":"user-integration-pre-check-WITH-UPPER-CASE","GID":1668487632,"UGID":"user-integration-pre-check-WITH-UPPER-CASE"}' + user-local-groups-Mixed-Case: '{"Name":"user-local-groups-Mixed-Case","GID":1047623009,"UGID":"user-local-groups-Mixed-Case"}' + user-local-groups-UPPER-CASE: '{"Name":"user-local-groups-UPPER-CASE","GID":1674497680,"UGID":"user-local-groups-UPPER-CASE"}' + user-local-groups-lower-case: '{"Name":"user-local-groups-lower-case","GID":1690031958,"UGID":"user-local-groups-lower-case"}' + user-pre-check: '{"Name":"user-pre-check","GID":1974679960,"UGID":"user-pre-check"}' GroupToUsers: - "1060856841": '{"GID":1060856841,"UIDs":[1674497680]}' - "1100583035": '{"GID":1100583035,"UIDs":[1031497592]}' + "1674497680": '{"GID":1674497680,"UIDs":[1674497680]}' + "1031497592": '{"GID":1031497592,"UIDs":[1031497592]}' "1118334210": '{"GID":1118334210,"UIDs":[1974679960]}' "1127261046": '{"GID":1127261046,"UIDs":[1690031958]}' "1147556396": '{"GID":1147556396,"UIDs":[1448654198]}' - "1204499006": '{"GID":1204499006,"UIDs":[1668487632]}' - "1305862567": '{"GID":1305862567,"UIDs":[1448654198]}' + "1668487632": '{"GID":1668487632,"UIDs":[1668487632]}' + "1448654198": '{"GID":1448654198,"UIDs":[1448654198]}' "1308355719": '{"GID":1308355719,"UIDs":[1031497592]}' - "1308629880": '{"GID":1308629880,"UIDs":[1974679960]}' + "1974679960": '{"GID":1974679960,"UIDs":[1974679960]}' "1485242865": '{"GID":1485242865,"UIDs":[1668487632]}' "1637945796": '{"GID":1637945796,"UIDs":[1674497680]}' - "1722096224": '{"GID":1722096224,"UIDs":[1690031958]}' + "1690031958": '{"GID":1690031958,"UIDs":[1690031958]}' "1726859569": '{"GID":1726859569,"UIDs":[1911746559]}' "1888170398": '{"GID":1888170398,"UIDs":[1047623009]}' - "1926585439": '{"GID":1926585439,"UIDs":[1911746559]}' - "1974061242": '{"GID":1974061242,"UIDs":[1047623009]}' + "1911746559": '{"GID":1911746559,"UIDs":[1911746559]}' + "1047623009": '{"GID":1047623009,"UIDs":[1047623009]}' UserByID: - "1031497592": '{"Name":"user-integration-WITH-Mixed-CaSe","UID":1031497592,"GID":1100583035,"Gecos":"gecos for user-integration-WITH-Mixed-CaSe","Dir":"/home/user-integration-WITH-Mixed-CaSe","Shell":"/bin/sh","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-05-30T18:17:18.01191855+02:00"}' - "1047623009": '{"Name":"user-local-groups-Mixed-Case","UID":1047623009,"GID":1974061242,"Gecos":"gecos for user-local-groups-Mixed-Case","Dir":"/home/user-local-groups-Mixed-Case","Shell":"/usr/bin/bash","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-06-02T01:05:19.742290239+02:00"}' - "1448654198": '{"Name":"user-integration-cached","UID":1448654198,"GID":1305862567,"Gecos":"gecos for user-integration-cached","Dir":"/home/user-integration-cached","Shell":"/bin/sh","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-05-30T18:16:55.049670042+02:00"}' - "1668487632": '{"Name":"user-integration-pre-check-WITH-UPPER-CASE","UID":1668487632,"GID":1204499006,"Gecos":"gecos for user-integration-pre-check-WITH-UPPER-CASE","Dir":"/home/user-integration-pre-check-WITH-UPPER-CASE","Shell":"/bin/sh","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-05-30T18:26:39.600082108+02:00"}' - "1674497680": '{"Name":"user-local-groups-UPPER-CASE","UID":1674497680,"GID":1060856841,"Gecos":"gecos for user-local-groups-UPPER-CASE","Dir":"/home/user-local-groups-UPPER-CASE","Shell":"/usr/bin/bash","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-06-02T01:08:33.759143141+02:00"}' - "1690031958": '{"Name":"user-local-groups-lower-case","UID":1690031958,"GID":1722096224,"Gecos":"gecos for user-local-groups-lower-case","Dir":"/home/user-local-groups-lower-case","Shell":"/usr/bin/bash","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-06-02T01:08:07.712846646+02:00"}' - "1911746559": '{"Name":"user-integration-UPPER-CASE","UID":1911746559,"GID":1926585439,"Gecos":"gecos for user-integration-UPPER-CASE","Dir":"/home/user-integration-UPPER-CASE","Shell":"/bin/sh","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-05-30T18:20:04.201035581+02:00"}' - "1974679960": '{"Name":"user-pre-check","UID":1974679960,"GID":1308629880,"Gecos":"gecos for user-pre-check","Dir":"/home/user-pre-check","Shell":"/bin/sh","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-05-30T18:24:42.349318925+02:00"}' + "1031497592": '{"Name":"user-integration-WITH-Mixed-CaSe","UID":1031497592,"GID":1031497592,"Gecos":"gecos for user-integration-WITH-Mixed-CaSe","Dir":"/home/user-integration-WITH-Mixed-CaSe","Shell":"/bin/sh","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-05-30T18:17:18.01191855+02:00"}' + "1047623009": '{"Name":"user-local-groups-Mixed-Case","UID":1047623009,"GID":1047623009,"Gecos":"gecos for user-local-groups-Mixed-Case","Dir":"/home/user-local-groups-Mixed-Case","Shell":"/usr/bin/bash","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-06-02T01:05:19.742290239+02:00"}' + "1448654198": '{"Name":"user-integration-cached","UID":1448654198,"GID":1448654198,"Gecos":"gecos for user-integration-cached","Dir":"/home/user-integration-cached","Shell":"/bin/sh","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-05-30T18:16:55.049670042+02:00"}' + "1668487632": '{"Name":"user-integration-pre-check-WITH-UPPER-CASE","UID":1668487632,"GID":1668487632,"Gecos":"gecos for user-integration-pre-check-WITH-UPPER-CASE","Dir":"/home/user-integration-pre-check-WITH-UPPER-CASE","Shell":"/bin/sh","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-05-30T18:26:39.600082108+02:00"}' + "1674497680": '{"Name":"user-local-groups-UPPER-CASE","UID":1674497680,"GID":1674497680,"Gecos":"gecos for user-local-groups-UPPER-CASE","Dir":"/home/user-local-groups-UPPER-CASE","Shell":"/usr/bin/bash","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-06-02T01:08:33.759143141+02:00"}' + "1690031958": '{"Name":"user-local-groups-lower-case","UID":1690031958,"GID":1690031958,"Gecos":"gecos for user-local-groups-lower-case","Dir":"/home/user-local-groups-lower-case","Shell":"/usr/bin/bash","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-06-02T01:08:07.712846646+02:00"}' + "1911746559": '{"Name":"user-integration-UPPER-CASE","UID":1911746559,"GID":1911746559,"Gecos":"gecos for user-integration-UPPER-CASE","Dir":"/home/user-integration-UPPER-CASE","Shell":"/bin/sh","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-05-30T18:20:04.201035581+02:00"}' + "1974679960": '{"Name":"user-pre-check","UID":1974679960,"GID":1974679960,"Gecos":"gecos for user-pre-check","Dir":"/home/user-pre-check","Shell":"/bin/sh","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-05-30T18:24:42.349318925+02:00"}' UserByName: - user-integration-UPPER-CASE: '{"Name":"user-integration-UPPER-CASE","UID":1911746559,"GID":1926585439,"Gecos":"gecos for user-integration-UPPER-CASE","Dir":"/home/user-integration-UPPER-CASE","Shell":"/bin/sh","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-05-30T18:20:04.201035581+02:00"}' - user-integration-WITH-Mixed-CaSe: '{"Name":"user-integration-WITH-Mixed-CaSe","UID":1031497592,"GID":1100583035,"Gecos":"gecos for user-integration-WITH-Mixed-CaSe","Dir":"/home/user-integration-WITH-Mixed-CaSe","Shell":"/bin/sh","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-05-30T18:17:18.01191855+02:00"}' - user-integration-cached: '{"Name":"user-integration-cached","UID":1448654198,"GID":1305862567,"Gecos":"gecos for user-integration-cached","Dir":"/home/user-integration-cached","Shell":"/bin/sh","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-05-30T18:16:55.049670042+02:00"}' - user-integration-pre-check-WITH-UPPER-CASE: '{"Name":"user-integration-pre-check-WITH-UPPER-CASE","UID":1668487632,"GID":1204499006,"Gecos":"gecos for user-integration-pre-check-WITH-UPPER-CASE","Dir":"/home/user-integration-pre-check-WITH-UPPER-CASE","Shell":"/bin/sh","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-05-30T18:26:39.600082108+02:00"}' - user-local-groups-Mixed-Case: '{"Name":"user-local-groups-Mixed-Case","UID":1047623009,"GID":1974061242,"Gecos":"gecos for user-local-groups-Mixed-Case","Dir":"/home/user-local-groups-Mixed-Case","Shell":"/usr/bin/bash","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-06-02T01:05:19.742290239+02:00"}' - user-local-groups-UPPER-CASE: '{"Name":"user-local-groups-UPPER-CASE","UID":1674497680,"GID":1060856841,"Gecos":"gecos for user-local-groups-UPPER-CASE","Dir":"/home/user-local-groups-UPPER-CASE","Shell":"/usr/bin/bash","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-06-02T01:08:33.759143141+02:00"}' - user-local-groups-lower-case: '{"Name":"user-local-groups-lower-case","UID":1690031958,"GID":1722096224,"Gecos":"gecos for user-local-groups-lower-case","Dir":"/home/user-local-groups-lower-case","Shell":"/usr/bin/bash","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-06-02T01:08:07.712846646+02:00"}' - user-pre-check: '{"Name":"user-pre-check","UID":1974679960,"GID":1308629880,"Gecos":"gecos for user-pre-check","Dir":"/home/user-pre-check","Shell":"/bin/sh","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-05-30T18:24:42.349318925+02:00"}' + user-integration-UPPER-CASE: '{"Name":"user-integration-UPPER-CASE","UID":1911746559,"GID":1911746559,"Gecos":"gecos for user-integration-UPPER-CASE","Dir":"/home/user-integration-UPPER-CASE","Shell":"/bin/sh","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-05-30T18:20:04.201035581+02:00"}' + user-integration-WITH-Mixed-CaSe: '{"Name":"user-integration-WITH-Mixed-CaSe","UID":1031497592,"GID":1031497592,"Gecos":"gecos for user-integration-WITH-Mixed-CaSe","Dir":"/home/user-integration-WITH-Mixed-CaSe","Shell":"/bin/sh","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-05-30T18:17:18.01191855+02:00"}' + user-integration-cached: '{"Name":"user-integration-cached","UID":1448654198,"GID":1448654198,"Gecos":"gecos for user-integration-cached","Dir":"/home/user-integration-cached","Shell":"/bin/sh","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-05-30T18:16:55.049670042+02:00"}' + user-integration-pre-check-WITH-UPPER-CASE: '{"Name":"user-integration-pre-check-WITH-UPPER-CASE","UID":1668487632,"GID":1668487632,"Gecos":"gecos for user-integration-pre-check-WITH-UPPER-CASE","Dir":"/home/user-integration-pre-check-WITH-UPPER-CASE","Shell":"/bin/sh","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-05-30T18:26:39.600082108+02:00"}' + user-local-groups-Mixed-Case: '{"Name":"user-local-groups-Mixed-Case","UID":1047623009,"GID":1047623009,"Gecos":"gecos for user-local-groups-Mixed-Case","Dir":"/home/user-local-groups-Mixed-Case","Shell":"/usr/bin/bash","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-06-02T01:05:19.742290239+02:00"}' + user-local-groups-UPPER-CASE: '{"Name":"user-local-groups-UPPER-CASE","UID":1674497680,"GID":1674497680,"Gecos":"gecos for user-local-groups-UPPER-CASE","Dir":"/home/user-local-groups-UPPER-CASE","Shell":"/usr/bin/bash","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-06-02T01:08:33.759143141+02:00"}' + user-local-groups-lower-case: '{"Name":"user-local-groups-lower-case","UID":1690031958,"GID":1690031958,"Gecos":"gecos for user-local-groups-lower-case","Dir":"/home/user-local-groups-lower-case","Shell":"/usr/bin/bash","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-06-02T01:08:07.712846646+02:00"}' + user-pre-check: '{"Name":"user-pre-check","UID":1974679960,"GID":1974679960,"Gecos":"gecos for user-pre-check","Dir":"/home/user-pre-check","Shell":"/bin/sh","LastPwdChange":-1,"MaxPwdAge":-1,"PwdWarnPeriod":-1,"PwdInactivity":-1,"MinPwdAge":-1,"ExpirationDate":-1,"LastLogin":"2025-05-30T18:24:42.349318925+02:00"}' UserToBroker: "1031497592": '"2221040704"' "1047623009": '"2221040704"' @@ -95,14 +95,14 @@ UserToBroker: "1911746559": '"2221040704"' "1974679960": '"2221040704"' UserToGroups: - "1031497592": '{"UID":1031497592,"GIDs":[1100583035,1308355719]}' - "1047623009": '{"UID":1047623009,"GIDs":[1974061242,1888170398]}' - "1448654198": '{"UID":1448654198,"GIDs":[1305862567,1147556396]}' - "1668487632": '{"UID":1668487632,"GIDs":[1204499006,1485242865]}' - "1674497680": '{"UID":1674497680,"GIDs":[1060856841,1637945796]}' - "1690031958": '{"UID":1690031958,"GIDs":[1722096224,1127261046]}' - "1911746559": '{"UID":1911746559,"GIDs":[1926585439,1726859569]}' - "1974679960": '{"UID":1974679960,"GIDs":[1308629880,1118334210]}' + "1031497592": '{"UID":1031497592,"GIDs":[1031497592,1308355719]}' + "1047623009": '{"UID":1047623009,"GIDs":[1047623009,1888170398]}' + "1448654198": '{"UID":1448654198,"GIDs":[1448654198,1147556396]}' + "1668487632": '{"UID":1668487632,"GIDs":[1668487632,1485242865]}' + "1674497680": '{"UID":1674497680,"GIDs":[1674497680,1637945796]}' + "1690031958": '{"UID":1690031958,"GIDs":[1690031958,1127261046]}' + "1911746559": '{"UID":1911746559,"GIDs":[1911746559,1726859569]}' + "1974679960": '{"UID":1974679960,"GIDs":[1974679960,1118334210]}' UserToLocalGroups: "1031497592": "null" "1047623009": '["localGROUP"]' From ab76b86cf98982d31c4c00ad1b22528c5e6cd620 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 16 Jun 2025 15:48:02 +0200 Subject: [PATCH 0449/1670] Print error message when UID and GID don't match Makes it easier to debug tests failing due to this assertion. --- pam/integration-tests/sshd_preloader/sshd_preloader.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pam/integration-tests/sshd_preloader/sshd_preloader.c b/pam/integration-tests/sshd_preloader/sshd_preloader.c index 8f8668bbd4..37dcb69e4b 100644 --- a/pam/integration-tests/sshd_preloader/sshd_preloader.c +++ b/pam/integration-tests/sshd_preloader/sshd_preloader.c @@ -153,7 +153,12 @@ getpwnam (const char *name) /* Ensure the GID we got matches the UID. * See: https://wiki.debian.org/UserPrivateGroups#UPGs */ - assert (passwd_entity->pw_gid == passwd_entity->pw_uid); + if (passwd_entity->pw_uid != passwd_entity->pw_gid) + { + fprintf (stderr, "sshd_preloader[%d]: User %s has different UID and GID (%d:%d)\n", + getpid (), name, passwd_entity->pw_uid, passwd_entity->pw_gid); + abort(); + } } else if (!is_supported_test_fake_user (name)) { From 38eec7c7a4d9833af811f59657e7590e116155dc Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 23 May 2025 19:37:09 +0200 Subject: [PATCH 0450/1670] Remove the completion subcommand authd is not a CLI tool. --- cmd/authd/daemon/daemon.go | 4 ++++ cmd/authd/daemon/daemon_test.go | 11 +---------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/cmd/authd/daemon/daemon.go b/cmd/authd/daemon/daemon.go index 1e43ac224c..852904f087 100644 --- a/cmd/authd/daemon/daemon.go +++ b/cmd/authd/daemon/daemon.go @@ -102,6 +102,10 @@ func New() *App { }, // We display usage error ourselves SilenceErrors: true, + // Don't add a completion subcommand, authd is not a CLI tool. + CompletionOptions: cobra.CompletionOptions{ + DisableDefaultCmd: true, + }, } viper := viper.New() diff --git a/cmd/authd/daemon/daemon_test.go b/cmd/authd/daemon/daemon_test.go index 3943af6893..cc26310d06 100644 --- a/cmd/authd/daemon/daemon_test.go +++ b/cmd/authd/daemon/daemon_test.go @@ -29,15 +29,6 @@ func TestHelp(t *testing.T) { require.NoErrorf(t, err, "Run should not return an error with argument --help. Stdout: %v", getStdout()) } -func TestCompletion(t *testing.T) { - a := daemon.NewForTests(t, nil, "completion", "bash") - - getStdout := captureStdout(t) - - err := a.Run() - require.NoError(t, err, "Completion should not start the daemon. Stdout: %v", getStdout()) -} - func TestVersion(t *testing.T) { a := daemon.NewForTests(t, nil, "version") @@ -58,7 +49,7 @@ func TestVersion(t *testing.T) { } func TestNoUsageError(t *testing.T) { - a := daemon.NewForTests(t, nil, "completion", "bash") + a := daemon.NewForTests(t, nil, "version") getStdout := captureStdout(t) err := a.Run() From 2063881530021b7d10d366b9bd0c9a3bb03957fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 16 Jun 2025 17:54:31 +0200 Subject: [PATCH 0451/1670] qa: Show the go race files output if the testing failed We may not have races, but if we do let's show them in the CI logs --- .github/workflows/qa.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/qa.yaml b/.github/workflows/qa.yaml index 07921a1cb0..6807afa843 100644 --- a/.github/workflows/qa.yaml +++ b/.github/workflows/qa.yaml @@ -294,7 +294,12 @@ jobs: GORACE: log_path=${{ env.AUTHD_TESTS_ARTIFACTS_PATH }}/gorace.log run: | go test -json -timeout ${GO_TESTS_TIMEOUT} -race -failfast ./... | \ - gotestfmt --logfile "${AUTHD_TESTS_ARTIFACTS_PATH}/gotestfmt.race.log" + gotestfmt --logfile "${AUTHD_TESTS_ARTIFACTS_PATH}/gotestfmt.race.log" || exit_code=$? + + if [ "${exit_code:-0}" -ne 0 ]; then + cat "${AUTHD_TESTS_ARTIFACTS_PATH}"/gorace.log* || true + exit ${exit_code} + fi - name: Run PAM tests (with Address Sanitizer) if: matrix.test == 'asan' From 43deea98a3b71bdf1aad6afb45b9a25ef4e80562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 16 Jun 2025 17:56:53 +0200 Subject: [PATCH 0452/1670] pam/go-exec: Allow to replicate env variables Use a syntax similar to the docker one, so if an env variable argument includes a defined variable, then set the new value otherwise replicate the one in the environment Also add debugging bits to get them printed when set. --- pam/go-exec/module.c | 10 +++++++++- pam/integration-tests/ssh_test.go | 10 ++-------- pam/tools/pam-runner/pam-runner.go | 21 ++++++++++----------- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/pam/go-exec/module.c b/pam/go-exec/module.c index 9034c56b45..8a9aaa4092 100644 --- a/pam/go-exec/module.c +++ b/pam/go-exec/module.c @@ -1157,7 +1157,13 @@ do_pam_action_thread (pam_handle_t *pamh, } for (int i = 0; env_variables && env_variables[i]; ++i) - g_ptr_array_add (envp, g_strdup (env_variables[i])); + { + if (strchr (env_variables[i], '=')) + g_ptr_array_add (envp, g_strdup (env_variables[i])); + else + maybe_replicate_env (envp, env_variables[i]); + } + g_ptr_array_add (envp, g_strdup_printf ("AUTHD_PAM_SERVER_ADDRESS=%s", g_dbus_server_get_client_address (server))); /* FIXME: use g_ptr_array_new_null_terminated when we can use newer GLib. */ @@ -1173,8 +1179,10 @@ do_pam_action_thread (pam_handle_t *pamh, if (is_debug_logging_enabled ()) { + g_autofree char *exec_env = g_strjoinv ("\n ", (char **) envp->pdata); g_autofree char *exec_str_args = g_strjoinv (" ", (char **) args->pdata); + g_debug ("Environment:%s%s", exec_env && *exec_env ? "\n " : "", exec_env); g_debug ("Launching '%s'", exec_str_args); } diff --git a/pam/integration-tests/ssh_test.go b/pam/integration-tests/ssh_test.go index 31e12240a6..d60f3b3579 100644 --- a/pam/integration-tests/ssh_test.go +++ b/pam/integration-tests/ssh_test.go @@ -549,14 +549,8 @@ func createSshdServiceFile(t *testing.T, module, execChild, mkHomeModule, socket moduleArgs = append(moduleArgs, "--exec-env", env) } if testutils.IsAsan() { - if o := os.Getenv("ASAN_OPTIONS"); o != "" { - moduleArgs = append(moduleArgs, "--exec-env", - fmt.Sprintf("ASAN_OPTIONS=%s", o)) - } - if o := os.Getenv("LSAN_OPTIONS"); o != "" { - moduleArgs = append(moduleArgs, "--exec-env", - fmt.Sprintf("LSAN_OPTIONS=%s", o)) - } + moduleArgs = append(moduleArgs, "--exec-env", "ASAN_OPTIONS") + moduleArgs = append(moduleArgs, "--exec-env", "LSAN_OPTIONS") } outDir := t.TempDir() diff --git a/pam/tools/pam-runner/pam-runner.go b/pam/tools/pam-runner/pam-runner.go index f48bccea4a..288da528d0 100644 --- a/pam/tools/pam-runner/pam-runner.go +++ b/pam/tools/pam-runner/pam-runner.go @@ -61,17 +61,16 @@ func main() { defaultArgs = append(defaultArgs, "--exec-debug", "--exec-log", logFile) } - if coverDir := os.Getenv("GOCOVERDIR"); coverDir != "" { - defaultArgs = append(defaultArgs, "--exec-env", fmt.Sprintf("GOCOVERDIR=%s", coverDir)) - } - if goRace := os.Getenv("GORACE"); goRace != "" { - defaultArgs = append(defaultArgs, "--exec-env", fmt.Sprintf("GORACE=%s", goRace)) - } - if asanOptions := os.Getenv("ASAN_OPTIONS"); asanOptions != "" { - defaultArgs = append(defaultArgs, "--exec-env", fmt.Sprintf("ASAN_OPTIONS=%s", asanOptions)) - } - if lsanOptions := os.Getenv("LSAN_OPTIONS"); lsanOptions != "" { - defaultArgs = append(defaultArgs, "--exec-env", fmt.Sprintf("LSAN_OPTIONS=%s", lsanOptions)) + for _, env := range []string{ + "GOCOVERDIR", + "GORACE", + "ASAN_OPTIONS", + "LSAN_OPTIONS", + } { + if _, ok := os.LookupEnv(env); !ok { + continue + } + defaultArgs = append(defaultArgs, "--exec-env", env) } if len(os.Args) < 2 { From 3599182020a7f0d54551bb16ee38be1cebd533a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 16 Jun 2025 18:03:05 +0200 Subject: [PATCH 0453/1670] pam/integration-tests: Expose GORACE and ASAN related variables to tests We were missing some cases --- pam/integration-tests/exec_test.go | 7 +++++++ pam/integration-tests/ssh_test.go | 3 +++ 2 files changed, 10 insertions(+) diff --git a/pam/integration-tests/exec_test.go b/pam/integration-tests/exec_test.go index 41a5015b4d..22a9ecba14 100644 --- a/pam/integration-tests/exec_test.go +++ b/pam/integration-tests/exec_test.go @@ -860,6 +860,13 @@ func getModuleArgs(t *testing.T, clientPath string, args []string) []string { if env := testutils.CoverDirEnv(); env != "" { moduleArgs = append(moduleArgs, "--exec-env", env) } + if testutils.IsRace() { + moduleArgs = append(moduleArgs, "--exec-env", "GORACE") + } + if testutils.IsAsan() { + moduleArgs = append(moduleArgs, "--exec-env", "ASAN_OPTIONS") + moduleArgs = append(moduleArgs, "--exec-env", "LSAN_OPTIONS") + } logFile := os.Stderr.Name() if !testutils.IsVerbose() { diff --git a/pam/integration-tests/ssh_test.go b/pam/integration-tests/ssh_test.go index d60f3b3579..d6f0f9a30a 100644 --- a/pam/integration-tests/ssh_test.go +++ b/pam/integration-tests/ssh_test.go @@ -548,6 +548,9 @@ func createSshdServiceFile(t *testing.T, module, execChild, mkHomeModule, socket if env := testutils.CoverDirEnv(); env != "" { moduleArgs = append(moduleArgs, "--exec-env", env) } + if testutils.IsRace() { + moduleArgs = append(moduleArgs, "--exec-env", "GORACE") + } if testutils.IsAsan() { moduleArgs = append(moduleArgs, "--exec-env", "ASAN_OPTIONS") moduleArgs = append(moduleArgs, "--exec-env", "LSAN_OPTIONS") From 9b645164f9bf10836cd9302258f4a5457157bea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 16 Jun 2025 18:03:26 +0200 Subject: [PATCH 0454/1670] pam/integration-test/exec: Lock the OS thread during pam-go operations We're using PAM go client to run the exec tests, this may be unreliable at times as it goes through cgo, so let's lock the OS operations while calling the transaction actions --- pam/integration-tests/exec_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pam/integration-tests/exec_test.go b/pam/integration-tests/exec_test.go index 22a9ecba14..0f29710004 100644 --- a/pam/integration-tests/exec_test.go +++ b/pam/integration-tests/exec_test.go @@ -7,6 +7,7 @@ import ( "os/exec" "path/filepath" "reflect" + "runtime" "strings" "syscall" "testing" @@ -923,6 +924,9 @@ func preparePamTransactionForServiceFile(t *testing.T, serviceFile string, user var tx *pam.Transaction var err error + runtime.LockOSThread() + t.Cleanup(runtime.UnlockOSThread) + // FIXME: pam.Transaction doesn't handle well pam.ConversationHandler(nil) if conv != nil && !reflect.ValueOf(conv).IsNil() { tx, err = pam.StartConfDir(filepath.Base(serviceFile), user, conv, filepath.Dir(serviceFile)) From 130b2b7b837a3c6550bbd2808dcfc0a4387bed01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 16 Jun 2025 18:33:55 +0200 Subject: [PATCH 0455/1670] pam/integration-tests/vhs-helpers: Use ASAN options to run test clients --- pam/integration-tests/vhs-helpers_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pam/integration-tests/vhs-helpers_test.go b/pam/integration-tests/vhs-helpers_test.go index ce3771ce95..c4289cf5c5 100644 --- a/pam/integration-tests/vhs-helpers_test.go +++ b/pam/integration-tests/vhs-helpers_test.go @@ -266,6 +266,15 @@ func (td tapeData) RunVhs(t *testing.T, testType vhsTestType, outDir string, cli cmd.Env = append(cmd.Env, fmt.Sprintf("GORACE=log_path=%s exitcode=0", raceLog)) } + if testutils.IsAsan() { + if asanOptions := os.Getenv("ASAN_OPTIONS"); asanOptions != "" { + cmd.Env = append(cmd.Env, fmt.Sprintf("ASAN_OPTIONS=%s", asanOptions)) + } + if lsanOptions := os.Getenv("LSAN_OPTIONS"); lsanOptions != "" { + cmd.Env = append(cmd.Env, fmt.Sprintf("LSAN_OPTIONS=%s", lsanOptions)) + } + } + cmd.Args = append(cmd.Args, td.PrepareTape(t, testType, outDir)) out, err := cmd.CombinedOutput() if raceLog != "" { From b2c3579092d60c32cf091a35168507b129af5712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 16 Jun 2025 16:45:56 +0000 Subject: [PATCH 0456/1670] testutils/args: Drop custom IsVerbose and replace with testing.Verbose() Ours didn't catch when -v was set to some other value, such as test2json --- internal/testutils/args.go | 17 ----------------- internal/testutils/bubblewrap.go | 4 ++-- internal/users/locking/locking_test.go | 2 +- pam/integration-tests/exec_test.go | 2 +- pam/integration-tests/gdm_test.go | 6 +++--- pam/integration-tests/helpers_test.go | 4 ++-- pam/integration-tests/ssh_test.go | 2 +- pam/integration-tests/vhs-helpers_test.go | 2 +- 8 files changed, 11 insertions(+), 28 deletions(-) diff --git a/internal/testutils/args.go b/internal/testutils/args.go index ee70bcadca..6ade22d0c2 100644 --- a/internal/testutils/args.go +++ b/internal/testutils/args.go @@ -4,7 +4,6 @@ import ( "os" "runtime/debug" "strconv" - "strings" "sync" ) @@ -13,26 +12,10 @@ var ( isAsanOnce sync.Once isRace bool isRaceOnce sync.Once - isVerbose bool - isVerboseOnce sync.Once sleepMultiplier float64 sleepMultiplierOnce sync.Once ) -// IsVerbose returns whether the tests are running in verbose mode. -func IsVerbose() bool { - isVerboseOnce.Do(func() { - for _, arg := range os.Args { - value, ok := strings.CutPrefix(arg, "-test.v=") - if !ok { - continue - } - isVerbose = value == "true" - } - }) - return isVerbose -} - func haveBuildFlag(flag string) bool { b, ok := debug.ReadBuildInfo() if !ok { diff --git a/internal/testutils/bubblewrap.go b/internal/testutils/bubblewrap.go index 0ed2e65acb..da81c9fb87 100644 --- a/internal/testutils/bubblewrap.go +++ b/internal/testutils/bubblewrap.go @@ -134,7 +134,7 @@ func runInBubbleWrap(t *testing.T, withSudo bool, testDataPath string, env []str var b bytes.Buffer cmd.Stdout = &b cmd.Stderr = &b - if IsVerbose() { + if testing.Verbose() { cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout } @@ -143,7 +143,7 @@ func runInBubbleWrap(t *testing.T, withSudo bool, testDataPath string, env []str err = cmd.Run() output := strings.TrimSpace(b.String()) - if !IsVerbose() { + if !testing.Verbose() { t.Log(output) } return output, err diff --git a/internal/users/locking/locking_test.go b/internal/users/locking/locking_test.go index 208758f353..e21ea2aee6 100644 --- a/internal/users/locking/locking_test.go +++ b/internal/users/locking/locking_test.go @@ -59,7 +59,7 @@ func TestUsersLockingInBubbleWrap(t *testing.T) { "Setup: Too many tests defined for %s", testsListStr) testCommand := []string{testBinary, "-test.run", nameRegex} - if testutils.IsVerbose() { + if testing.Verbose() { testCommand = append(testCommand, "-test.v") } if c := testutils.CoverDirForTests(); c != "" { diff --git a/pam/integration-tests/exec_test.go b/pam/integration-tests/exec_test.go index 0f29710004..2df0bf1821 100644 --- a/pam/integration-tests/exec_test.go +++ b/pam/integration-tests/exec_test.go @@ -870,7 +870,7 @@ func getModuleArgs(t *testing.T, clientPath string, args []string) []string { } logFile := os.Stderr.Name() - if !testutils.IsVerbose() { + if !testing.Verbose() { logFile = prepareFileLogging(t, "exec-module.log") } moduleArgs = append(moduleArgs, "--exec-log", logFile) diff --git a/pam/integration-tests/gdm_test.go b/pam/integration-tests/gdm_test.go index c7c74d5503..911b343cee 100644 --- a/pam/integration-tests/gdm_test.go +++ b/pam/integration-tests/gdm_test.go @@ -1013,7 +1013,7 @@ func TestGdmModule(t *testing.T) { } var pamFlags pam.Flags - if !testutils.IsVerbose() { + if !testing.Verbose() { pamFlags = pam.Silent } @@ -1089,7 +1089,7 @@ func TestGdmModuleAuthenticateWithoutGdmExtension(t *testing.T) { t.Cleanup(enableGdmExtension) var pamFlags pam.Flags - if !testutils.IsVerbose() { + if !testing.Verbose() { pamFlags = pam.Silent } @@ -1130,7 +1130,7 @@ func TestGdmModuleAcctMgmtWithoutGdmExtension(t *testing.T) { } var pamFlags pam.Flags - if !testutils.IsVerbose() { + if !testing.Verbose() { pamFlags = pam.Silent } diff --git a/pam/integration-tests/helpers_test.go b/pam/integration-tests/helpers_test.go index eedc58da69..8db286e2da 100644 --- a/pam/integration-tests/helpers_test.go +++ b/pam/integration-tests/helpers_test.go @@ -120,7 +120,7 @@ func sharedAuthd(t *testing.T, args ...testutils.DaemonOption) (socketPath strin defer sharedAuthdInstance.mu.Unlock() sa.refCount-- - if testutils.IsVerbose() { + if testing.Verbose() { t.Logf("Authd shared instances decreased: %v", sa.refCount) } if sa.refCount != 0 { @@ -139,7 +139,7 @@ func sharedAuthd(t *testing.T, args ...testutils.DaemonOption) (socketPath strin defer sharedAuthdInstance.mu.Unlock() sa.refCount++ - if testutils.IsVerbose() { + if testing.Verbose() { t.Logf("Authd shared instances increased: %v", sa.refCount) } if sa.refCount != 1 { diff --git a/pam/integration-tests/ssh_test.go b/pam/integration-tests/ssh_test.go index d6f0f9a30a..480b361329 100644 --- a/pam/integration-tests/ssh_test.go +++ b/pam/integration-tests/ssh_test.go @@ -737,7 +737,7 @@ func startSSHd(t *testing.T, hostKey, forcedCommand string, env []string, daemon } t.Cleanup(func() { - if !t.Failed() && !testutils.IsVerbose() { + if !t.Failed() && !testing.Verbose() { return } contents, err := os.ReadFile(sshdLogFile) diff --git a/pam/integration-tests/vhs-helpers_test.go b/pam/integration-tests/vhs-helpers_test.go index c4289cf5c5..7425fa7701 100644 --- a/pam/integration-tests/vhs-helpers_test.go +++ b/pam/integration-tests/vhs-helpers_test.go @@ -477,7 +477,7 @@ func (td tapeData) PrepareTape(t *testing.T, testType vhsTestType, outputPath st err = os.WriteFile(tapePath, tape, 0600) require.NoError(t, err, "Setup: write tape file") - if testutils.IsVerbose() { + if testing.Verbose() { t.Logf("Tape %q is now:\n%s", td.Name, tape) } From 519ec31b82a6a99eb275c98a44963803571bbb42 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 17 Jun 2025 10:52:19 +0200 Subject: [PATCH 0457/1670] Show more useful error message When the username which the user tries to log in as doesn't match the username which the user authenticates to via Entra ID / Google IAM, we show the error message: authentication failure: could not fetch user info That's not very helpful. Let's instead show the error message which explains what went wrong: requested username X does not match the authenticated user Y And in case the username contains invalid characters: requested username X contains invalid characters --- internal/providers/genericprovider/genericprovider.go | 3 ++- internal/providers/msentraid/msentraid.go | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/providers/genericprovider/genericprovider.go b/internal/providers/genericprovider/genericprovider.go index 9543c5c041..4fbab6be2e 100644 --- a/internal/providers/genericprovider/genericprovider.go +++ b/internal/providers/genericprovider/genericprovider.go @@ -8,6 +8,7 @@ import ( "github.com/coreos/go-oidc/v3/oidc" "github.com/ubuntu/authd-oidc-brokers/internal/broker/authmodes" + providerErrors "github.com/ubuntu/authd-oidc-brokers/internal/providers/errors" "github.com/ubuntu/authd-oidc-brokers/internal/providers/info" "golang.org/x/oauth2" ) @@ -76,7 +77,7 @@ func (p GenericProvider) NormalizeUsername(username string) string { // VerifyUsername checks if the requested username matches the authenticated user. func (p GenericProvider) VerifyUsername(requestedUsername, username string) error { if p.NormalizeUsername(requestedUsername) != p.NormalizeUsername(username) { - return fmt.Errorf("requested username %q does not match the authenticated user %q", requestedUsername, username) + return providerErrors.NewForDisplayError("requested username %q does not match the authenticated user %q", requestedUsername, username) } return nil } diff --git a/internal/providers/msentraid/msentraid.go b/internal/providers/msentraid/msentraid.go index 62b6f3645f..3dd00a1ef3 100644 --- a/internal/providers/msentraid/msentraid.go +++ b/internal/providers/msentraid/msentraid.go @@ -350,7 +350,7 @@ func (p Provider) SupportedOIDCAuthModes() []string { // VerifyUsername checks if the authenticated username matches the requested username and that both are valid. func (p Provider) VerifyUsername(requestedUsername, authenticatedUsername string) error { if p.NormalizeUsername(requestedUsername) != p.NormalizeUsername(authenticatedUsername) { - return fmt.Errorf("requested username %q does not match the authenticated user %q", requestedUsername, authenticatedUsername) + return providerErrors.NewForDisplayError("requested username %q does not match the authenticated user %q", requestedUsername, authenticatedUsername) } // Check that the usernames only contain the characters allowed by the Microsoft Entra username policy @@ -362,7 +362,7 @@ func (p Provider) VerifyUsername(requestedUsername, authenticatedUsername string return providerErrors.NewForDisplayError("the authenticated username %q contains invalid characters. Please report this error on https://github.com/ubuntu/authd/issues", authenticatedUsername) } if !usernameRegexp.MatchString(requestedUsername) { - return fmt.Errorf("requested username %q contains invalid characters", requestedUsername) + return providerErrors.NewForDisplayError("requested username %q contains invalid characters", requestedUsername) } return nil From 639479de71b4c7d8e440eae3c4762d692e285407 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 30 May 2025 22:27:40 +0200 Subject: [PATCH 0458/1670] locking: Return ErrLockTimeout on EINTR We've seen the test fail in the CI with: Error Trace: /home/runner/work/authd/authd/internal/users/locking/locking_bwrap_test.go:266 Error: Target error should be in err chain: expected: "failed to lock the shadow password database: timeout" in chain: "failed to lock the shadow password database: Interrupted system call" Test: TestLockingLockedDatabaseFailsAfterTimeout The reason is probably that the alarm set by lckpwdf does sometimes work in the CI, so lckpwdf returns an error before our own 16 second timeout is reached, resulting in a different error to be returned. Return a ErrLockTimeout when that happens. --- internal/errno/errno_c.go | 2 ++ internal/users/locking/locking_c.go | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/internal/errno/errno_c.go b/internal/errno/errno_c.go index ccc0e2eea2..1c00409a6a 100644 --- a/internal/errno/errno_c.go +++ b/internal/errno/errno_c.go @@ -36,6 +36,8 @@ const ( ErrBadf Error = C.EBADF // ErrPerm is the errno EPERM. ErrPerm Error = C.EPERM + // ErrIntr is the errno EINTR. + ErrIntr Error = C.EINTR ) // All these functions are expected to be called while this mutex is locked. diff --git a/internal/users/locking/locking_c.go b/internal/users/locking/locking_c.go index d7011a495d..c122689d3d 100644 --- a/internal/users/locking/locking_c.go +++ b/internal/users/locking/locking_c.go @@ -6,6 +6,7 @@ package userslocking import "C" import ( + "errors" "fmt" "github.com/ubuntu/authd/internal/errno" @@ -20,7 +21,12 @@ func writeLock() error { return nil } - if err := errno.Get(); err != nil { + err := errno.Get() + if errors.Is(err, errno.ErrIntr) { + // lckpwdf sets errno to EINTR when a SIGALRM is received, which is expected when the lock times out. + return ErrLockTimeout + } + if err != nil { return fmt.Errorf("%w: %w", ErrLock, err) } From 6bb6736183a461e7bca643cf6cb1641f5e8afaf1 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 17 Jun 2025 00:28:04 +0200 Subject: [PATCH 0459/1670] Fix error message "GetUserByName: no result matching in" The functions of the tempentries package returned empty NoDataFoundError{} instances, which are converted to the string "no result matching in". This commit extracts exported functions to create instances of a NoDataFoundError which are now used both in the db package and the tempentries package. --- internal/users/db/db.go | 23 ++++++++++++++++++++++- internal/users/db/groups.go | 7 +++---- internal/users/db/update.go | 2 +- internal/users/db/usergroups.go | 2 +- internal/users/db/users.go | 7 +++---- internal/users/tempentries/groups.go | 5 +++-- internal/users/tempentries/preauth.go | 7 ++++--- internal/users/tempentries/users.go | 5 +++-- 8 files changed, 40 insertions(+), 18 deletions(-) diff --git a/internal/users/db/db.go b/internal/users/db/db.go index 198548b9fa..f17f4a9ace 100644 --- a/internal/users/db/db.go +++ b/internal/users/db/db.go @@ -9,6 +9,7 @@ import ( "fmt" "os" "path/filepath" + "strconv" "sync" "syscall" @@ -175,6 +176,26 @@ func RemoveDB(dbDir string) error { return os.Remove(filepath.Join(dbDir, consts.DefaultDatabaseFileName)) } +// NewUIDNotFoundError returns a NoDataFoundError for the given user ID. +func NewUIDNotFoundError(uid uint32) NoDataFoundError { + return NoDataFoundError{key: strconv.FormatUint(uint64(uid), 10), table: "users"} +} + +// NewGIDNotFoundError returns a NoDataFoundError for the given group ID. +func NewGIDNotFoundError(gid uint32) NoDataFoundError { + return NoDataFoundError{key: strconv.FormatUint(uint64(gid), 10), table: "groups"} +} + +// NewUserNotFoundError returns a NoDataFoundError for the given user name. +func NewUserNotFoundError(name string) NoDataFoundError { + return NoDataFoundError{key: name, table: "users"} +} + +// NewGroupNotFoundError returns a NoDataFoundError for the given group name. +func NewGroupNotFoundError(name string) NoDataFoundError { + return NoDataFoundError{key: name, table: "groups"} +} + // NoDataFoundError is returned when we didn’t find a matching entry. type NoDataFoundError struct { key string @@ -183,7 +204,7 @@ type NoDataFoundError struct { // Error implements the error interface. func (err NoDataFoundError) Error() string { - return fmt.Sprintf("no result matching %v in %v", err.key, err.table) + return fmt.Sprintf("no result matching %q in %q", err.key, err.table) } // Is makes this error insensitive to the key and table names. diff --git a/internal/users/db/groups.go b/internal/users/db/groups.go index ba0278e3ac..0382e6f1dd 100644 --- a/internal/users/db/groups.go +++ b/internal/users/db/groups.go @@ -4,7 +4,6 @@ import ( "database/sql" "errors" "fmt" - "strconv" ) // GroupRow represents a group in the database. @@ -46,7 +45,7 @@ func groupByID(db queryable, gid uint32) (GroupRow, error) { var g GroupRow err := row.Scan(&g.Name, &g.GID, &g.UGID) if errors.Is(err, sql.ErrNoRows) { - return GroupRow{}, NoDataFoundError{key: strconv.FormatUint(uint64(gid), 10), table: "groups"} + return GroupRow{}, NewGIDNotFoundError(gid) } if err != nil { return GroupRow{}, fmt.Errorf("query error: %w", err) @@ -93,7 +92,7 @@ func groupByName(db queryable, name string) (GroupRow, error) { var g GroupRow err := row.Scan(&g.Name, &g.GID, &g.UGID) if errors.Is(err, sql.ErrNoRows) { - return GroupRow{}, NoDataFoundError{key: name, table: "groups"} + return GroupRow{}, NewGroupNotFoundError(name) } if err != nil { return GroupRow{}, fmt.Errorf("query error: %w", err) @@ -140,7 +139,7 @@ func groupByUGID(db queryable, ugid string) (GroupRow, error) { var g GroupRow err := row.Scan(&g.Name, &g.GID, &g.UGID) if errors.Is(err, sql.ErrNoRows) { - return GroupRow{}, NoDataFoundError{key: ugid, table: "groups"} + return GroupRow{}, NewGroupNotFoundError(ugid) } if err != nil { return GroupRow{}, fmt.Errorf("query error: %w", err) diff --git a/internal/users/db/update.go b/internal/users/db/update.go index 52e3e0747c..4c412e22d3 100644 --- a/internal/users/db/update.go +++ b/internal/users/db/update.go @@ -174,7 +174,7 @@ func (m *Manager) UpdateBrokerForUser(username, brokerID string) error { return fmt.Errorf("failed to get rows affected: %w", err) } if rowsAffected == 0 { - return NoDataFoundError{table: "users", key: username} + return NewUserNotFoundError(username) } return nil diff --git a/internal/users/db/usergroups.go b/internal/users/db/usergroups.go index 837219e7d1..8dcf28b1df 100644 --- a/internal/users/db/usergroups.go +++ b/internal/users/db/usergroups.go @@ -43,7 +43,7 @@ func userGroups(db queryable, uid uint32) ([]GroupRow, error) { } if len(groups) == 0 { - return nil, NoDataFoundError{key: strconv.FormatUint(uint64(uid), 10), table: "users_to_groups"} + return nil, NewUIDNotFoundError(uid) } return groups, nil diff --git a/internal/users/db/users.go b/internal/users/db/users.go index f41d2c0f52..001ac80a42 100644 --- a/internal/users/db/users.go +++ b/internal/users/db/users.go @@ -5,7 +5,6 @@ import ( "database/sql" "errors" "fmt" - "strconv" "strings" "github.com/ubuntu/authd/log" @@ -52,7 +51,7 @@ func userByID(db queryable, uid uint32) (UserRow, error) { var u UserRow err := row.Scan(&u.Name, &u.UID, &u.GID, &u.Gecos, &u.Dir, &u.Shell, &u.BrokerID) if errors.Is(err, sql.ErrNoRows) { - return UserRow{}, NoDataFoundError{key: strconv.FormatUint(uint64(uid), 10), table: "users"} + return UserRow{}, NewUIDNotFoundError(uid) } if err != nil { return UserRow{}, fmt.Errorf("query error: %w", err) @@ -72,7 +71,7 @@ func (m *Manager) UserByName(name string) (UserRow, error) { var u UserRow err := row.Scan(&u.Name, &u.UID, &u.GID, &u.Gecos, &u.Dir, &u.Shell, &u.BrokerID) if errors.Is(err, sql.ErrNoRows) { - return UserRow{}, NoDataFoundError{key: name, table: "users"} + return UserRow{}, NewUserNotFoundError(name) } if err != nil { return UserRow{}, fmt.Errorf("query error: %w", err) @@ -181,7 +180,7 @@ func (m *Manager) DeleteUser(uid uint32) error { return fmt.Errorf("failed to get rows affected: %w", err) } if rowsAffected == 0 { - return NoDataFoundError{table: "users", key: strconv.FormatUint(uint64(uid), 10)} + return NewUIDNotFoundError(uid) } return nil diff --git a/internal/users/tempentries/groups.go b/internal/users/tempentries/groups.go index 920fe491f1..9837931ec7 100644 --- a/internal/users/tempentries/groups.go +++ b/internal/users/tempentries/groups.go @@ -7,6 +7,7 @@ import ( "fmt" "sync" + "github.com/ubuntu/authd/internal/users/db" "github.com/ubuntu/authd/internal/users/localentries" "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/authd/log" @@ -43,7 +44,7 @@ func (r *temporaryGroupRecords) GroupByID(gid uint32) (types.GroupEntry, error) group, ok := r.groups[gid] if !ok { - return types.GroupEntry{}, NoDataFoundError{} + return types.GroupEntry{}, db.NewGIDNotFoundError(gid) } return groupEntry(group), nil @@ -56,7 +57,7 @@ func (r *temporaryGroupRecords) GroupByName(name string) (types.GroupEntry, erro gid, ok := r.gidByName[name] if !ok { - return types.GroupEntry{}, NoDataFoundError{} + return types.GroupEntry{}, db.NewGroupNotFoundError(name) } return r.GroupByID(gid) diff --git a/internal/users/tempentries/preauth.go b/internal/users/tempentries/preauth.go index 8ce3e7f388..e420bce1a0 100644 --- a/internal/users/tempentries/preauth.go +++ b/internal/users/tempentries/preauth.go @@ -7,6 +7,7 @@ import ( "fmt" "sync" + "github.com/ubuntu/authd/internal/users/db" "github.com/ubuntu/authd/internal/users/localentries" "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/authd/log" @@ -58,7 +59,7 @@ func (r *preAuthUserRecords) userByID(uid uint32) (types.UserEntry, error) { user, ok := r.users[uid] if !ok { - return types.UserEntry{}, NoDataFoundError{} + return types.UserEntry{}, db.NewUIDNotFoundError(uid) } return preAuthUserEntry(user), nil @@ -71,7 +72,7 @@ func (r *preAuthUserRecords) userByName(name string) (types.UserEntry, error) { uid, ok := r.uidByName[name] if !ok { - return types.UserEntry{}, NoDataFoundError{} + return types.UserEntry{}, db.NewUserNotFoundError(name) } return r.userByID(uid) @@ -83,7 +84,7 @@ func (r *preAuthUserRecords) userByLogin(loginName string) (types.UserEntry, err uid, ok := r.uidByLogin[loginName] if !ok { - return types.UserEntry{}, NoDataFoundError{} + return types.UserEntry{}, db.NewUserNotFoundError(loginName) } return r.userByID(uid) diff --git a/internal/users/tempentries/users.go b/internal/users/tempentries/users.go index 4cb81959ce..cce8fba461 100644 --- a/internal/users/tempentries/users.go +++ b/internal/users/tempentries/users.go @@ -6,6 +6,7 @@ import ( "fmt" "sync" + "github.com/ubuntu/authd/internal/users/db" "github.com/ubuntu/authd/internal/users/localentries" "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/authd/log" @@ -42,7 +43,7 @@ func (r *temporaryUserRecords) userByID(uid uint32) (types.UserEntry, error) { user, ok := r.users[uid] if !ok { - return types.UserEntry{}, NoDataFoundError{} + return types.UserEntry{}, db.NewUIDNotFoundError(uid) } return userEntry(user), nil @@ -55,7 +56,7 @@ func (r *temporaryUserRecords) userByName(name string) (types.UserEntry, error) uid, ok := r.uidByName[name] if !ok { - return types.UserEntry{}, NoDataFoundError{} + return types.UserEntry{}, db.NewUserNotFoundError(name) } return r.userByID(uid) From 72b72419496b54cf0caf002f7069825780cf127a Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 17 Jun 2025 00:30:18 +0200 Subject: [PATCH 0460/1670] Remove redundant code There is no reason to return an empty NoDataFoundError here, we can just return the instance we already got from db.UserByName(). --- internal/users/manager.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/internal/users/manager.go b/internal/users/manager.go index d16935795b..c88eb6c666 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -324,10 +324,6 @@ func checkHomeDirOwnership(home string, uid, gid uint32) error { // BrokerForUser returns the broker ID for the given user. func (m *Manager) BrokerForUser(username string) (string, error) { u, err := m.db.UserByName(username) - if err != nil && errors.Is(err, db.NoDataFoundError{}) { - // User not in db. - return "", NoDataFoundError{} - } if err != nil { return "", err } From 6fdb0e85970eac65617fc04ce994c361e7a7e155 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 17 Jun 2025 16:21:13 +0200 Subject: [PATCH 0461/1670] Improve error messages authctl will show these errors messages, so let's improve them. For example, instead of: no result matching "foo" in "users" use: user "foo" not found --- internal/users/db/db.go | 14 ++++++-------- internal/users/db/usergroups.go | 3 +-- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/internal/users/db/db.go b/internal/users/db/db.go index f17f4a9ace..a95e2d9137 100644 --- a/internal/users/db/db.go +++ b/internal/users/db/db.go @@ -9,7 +9,6 @@ import ( "fmt" "os" "path/filepath" - "strconv" "sync" "syscall" @@ -178,33 +177,32 @@ func RemoveDB(dbDir string) error { // NewUIDNotFoundError returns a NoDataFoundError for the given user ID. func NewUIDNotFoundError(uid uint32) NoDataFoundError { - return NoDataFoundError{key: strconv.FormatUint(uint64(uid), 10), table: "users"} + return NoDataFoundError{fmt.Sprintf("user with UID %d not found", uid)} } // NewGIDNotFoundError returns a NoDataFoundError for the given group ID. func NewGIDNotFoundError(gid uint32) NoDataFoundError { - return NoDataFoundError{key: strconv.FormatUint(uint64(gid), 10), table: "groups"} + return NoDataFoundError{fmt.Sprintf("group with GID %d not found", gid)} } // NewUserNotFoundError returns a NoDataFoundError for the given user name. func NewUserNotFoundError(name string) NoDataFoundError { - return NoDataFoundError{key: name, table: "users"} + return NoDataFoundError{fmt.Sprintf("user %q not found", name)} } // NewGroupNotFoundError returns a NoDataFoundError for the given group name. func NewGroupNotFoundError(name string) NoDataFoundError { - return NoDataFoundError{key: name, table: "groups"} + return NoDataFoundError{fmt.Sprintf("group %q not found", name)} } // NoDataFoundError is returned when we didn’t find a matching entry. type NoDataFoundError struct { - key string - table string + msg string } // Error implements the error interface. func (err NoDataFoundError) Error() string { - return fmt.Sprintf("no result matching %q in %q", err.key, err.table) + return err.msg } // Is makes this error insensitive to the key and table names. diff --git a/internal/users/db/usergroups.go b/internal/users/db/usergroups.go index 8dcf28b1df..da35021ff1 100644 --- a/internal/users/db/usergroups.go +++ b/internal/users/db/usergroups.go @@ -3,7 +3,6 @@ package db import ( "context" "fmt" - "strconv" "github.com/ubuntu/authd/log" ) @@ -122,7 +121,7 @@ func removeUserFromAllGroups(db queryable, uid uint32) error { return fmt.Errorf("failed to get rows affected: %w", err) } if rowsAffected == 0 { - return NoDataFoundError{table: "users_to_groups", key: strconv.FormatUint(uint64(uid), 10)} + return NoDataFoundError{fmt.Sprintf("no groups found for user with UID %d", uid)} } return nil From c2883a838aec4dda3eb8e3f51b5461f0adccf20f Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 17 Jun 2025 19:47:43 +0200 Subject: [PATCH 0462/1670] docs: Do not document setting option to default value The force_provider_authentication option is set to false by default, and it's documented in the default broker.conf file, so users don't need to set it to false themselves. --- docs/howto/configure-authd.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/howto/configure-authd.md b/docs/howto/configure-authd.md index 1b95279ad1..24200c8530 100644 --- a/docs/howto/configure-authd.md +++ b/docs/howto/configure-authd.md @@ -144,7 +144,6 @@ To configure Google IAM, edit `/var/snap/authd-google/current/broker.conf`: issuer = https://accounts.google.com client_id = client_secret = -force_provider_authentication = false ``` :::: @@ -157,7 +156,6 @@ To configure Entra ID, edit `/var/snap/authd-msentraid/current/broker.conf`: [oidc] issuer = https://login.microsoftonline.com//v2.0 client_id = -force_provider_authentication = false ``` :::: ::::: From 1fa187d721227af9dadc2a72477d580521fe4628 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 18 Jun 2025 11:12:39 +0200 Subject: [PATCH 0463/1670] Make errors when checking home dir owner non-fatal We only do this check to display a warning in case that the home directory is not owned by the correct UID/GID. Let's not block login if we can't do this check. I encountered this case myself, when I bind-mounted another directory via bindfs, which caused a "transport endpoint is not connected" error when trying to access the home directory. It doesn't make sense to lock the user out in that case, lets log them in and let them deal with the fact that they can't access the home directory. --- internal/users/manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/users/manager.go b/internal/users/manager.go index c88eb6c666..b700e77d0d 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -231,7 +231,7 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { } if err = checkHomeDirOwnership(userRow.Dir, userRow.UID, userRow.GID); err != nil { - return fmt.Errorf("failed to check home directory owner and group: %w", err) + log.Warningf(context.Background(), "Failed to check home directory ownership: %v", err) } return nil From 78827bebbd8f6821b4a5faf250b02e8471450f5e Mon Sep 17 00:00:00 2001 From: shanecrowley Date: Tue, 17 Jun 2025 13:19:45 +0100 Subject: [PATCH 0464/1670] add sections and pages on configuring ID ranges for LXD * adds guide on additional UID/GID configurations needed for LXD * adds section in troubleshooting that points to new LXD guide * adds note in generic configuration guide that points to new LXD guide add fixes for spellchecker * updates wordlist * ignores captions in codeblocks --- docs/.custom_wordlist.txt | 1 + docs/.sphinx/spellingcheck.yaml | 1 + docs/howto/configure-authd.md | 21 +++++++++-- docs/howto/index.md | 1 + docs/howto/use-with-lxd.md | 60 +++++++++++++++++++++++++++++++ docs/reference/troubleshooting.md | 8 +++++ 6 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 docs/howto/use-with-lxd.md diff --git a/docs/.custom_wordlist.txt b/docs/.custom_wordlist.txt index 6798e78afb..39f5d9ead3 100644 --- a/docs/.custom_wordlist.txt +++ b/docs/.custom_wordlist.txt @@ -10,6 +10,7 @@ filesystem fstab ESC GDM +GID GIDs gRPC github diff --git a/docs/.sphinx/spellingcheck.yaml b/docs/.sphinx/spellingcheck.yaml index 417a541cfe..405b13aa59 100644 --- a/docs/.sphinx/spellingcheck.yaml +++ b/docs/.sphinx/spellingcheck.yaml @@ -28,3 +28,4 @@ matrix: - img - a.p-navigation__link - a.contributor + - div.code-block-caption diff --git a/docs/howto/configure-authd.md b/docs/howto/configure-authd.md index 24200c8530..089038169c 100644 --- a/docs/howto/configure-authd.md +++ b/docs/howto/configure-authd.md @@ -273,6 +273,23 @@ sudo snap restart authd-msentraid :::: ::::: -## System configuration +## Configure login timeout -By default on Ubuntu, the login timeout is 60s. This may be too brief for a device code flow authentication. It can be set to a different value by changing the value of `LOGIN_TIMEOUT` in `/etc/login.defs` +By default on Ubuntu, the login timeout is 60s. + +This may be too brief for a device code flow authentication. + +It can be modified by changing the value of `LOGIN_TIMEOUT` in `/etc/login.defs`. + +## Configure the authd service + +The authd service is configured in `/etc/authd/authd.yaml`. + +This provides configuration options for logging verbosity and UID/GID ranges. + +```{admonition} Additional configuration for LXD +:class: tip +If you are using authd inside LXD containers, read our short guide on [how to +use authd with LXD](howto::use-with-lxd), which outlines additional steps for +configuring appropriate UID/GID ranges. +``` diff --git a/docs/howto/index.md b/docs/howto/index.md index 9021e152de..bcd2487d90 100644 --- a/docs/howto/index.md +++ b/docs/howto/index.md @@ -12,6 +12,7 @@ provider. Install authd Configure authd +Use authd with LXD ``` ## Login and authentication diff --git a/docs/howto/use-with-lxd.md b/docs/howto/use-with-lxd.md new file mode 100644 index 0000000000..97ff2cb097 --- /dev/null +++ b/docs/howto/use-with-lxd.md @@ -0,0 +1,60 @@ +--- +myst: + html_meta: + "description lang=en": "To use authd with LXD, you need to configure the UID and GID ranges." +--- + +(howto::use-with-lxd)= +# Use authd in LXD containers + +Running authd inside a LXD container requires the configuration of UID and GID +ranges. + +```{important} +These steps are in addition to the general installation and configuration steps +outlined in the [configuring authd guide](configure-authd.md). +``` +## Default ID map ranges + +The ID map range for LXD is: `1000000:1000000000`. + +The default range for authd exceeds those values: `1000000000:1999999999`. + +This causes errors when authenticating from LXD containers. + +## Configuration options for using authd with LXD + +Two options for configuring the ID ranges are outlined below. + +### 1. Configure ID ranges for the authd service + +Change the default ranges so that they don't exceed those from the user namespace mappings, for example: + +```{code-block} diff +:caption: /etc/authd/authd.yaml + +-#UID_MIN: 1000000000 ++#UID_MIN: 100000 +-#UID_MAX: 1999999999 ++#UID_MAX: 1000000000 +-#GID_MIN: 1000000000 ++#GID_MIN: 100000 +-#GID_MAX: 1999999999 ++#GID_MAX: 1000000000 + +``` + +### 2. Configure subordinate ID ranges on the host + +The mappings that apply to LXD containers can be found in the following files on the host: + +* `/etc/subuid` +* `/etc/subgid` + +Configure the subordinate ID range in each file to include the default authd ID range (`1000000000:1999999999`), for example: + +```{code-block} diff +:caption: /etc/subuid +-:100000:65536 ++:1000000000:1999999999 +``` diff --git a/docs/reference/troubleshooting.md b/docs/reference/troubleshooting.md index 7455e2a64f..cac9ff3260 100644 --- a/docs/reference/troubleshooting.md +++ b/docs/reference/troubleshooting.md @@ -342,3 +342,11 @@ mode: 5. Select `drop to root shell prompt` The user then has access to the root filesystem and can proceed with debugging. + +## Using authd in LXD containers + +If you are using authd in a LXD container, you may encounter errors during authentication. + +To fix these errors, you need to configure UID and GID ranges. + +The configuration steps are outlined in this guide on [using authd with LXD](howto::use-with-lxd). From a5fa1b2b4256d36dff5380991c9c75c81970cd66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 20 Jun 2025 15:52:45 +0200 Subject: [PATCH 0465/1670] users/locking: Do not use sleep duration in tests Since the counterpart (libc) is not using any sleep multiplier, let's just use the actual values or we may end up in unexpected failures --- internal/users/locking/locking_bwrap_test.go | 22 +++++++------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/internal/users/locking/locking_bwrap_test.go b/internal/users/locking/locking_bwrap_test.go index 1687a6fa3c..050163b130 100644 --- a/internal/users/locking/locking_bwrap_test.go +++ b/internal/users/locking/locking_bwrap_test.go @@ -4,7 +4,6 @@ package userslocking_test import ( "context" - "math" "os" "os/exec" "path/filepath" @@ -14,7 +13,6 @@ import ( "time" "github.com/stretchr/testify/require" - "github.com/ubuntu/authd/internal/testutils" userslocking "github.com/ubuntu/authd/internal/users/locking" ) @@ -121,7 +119,7 @@ func TestLockAndLockAgainGroupFileOverridden(t *testing.T) { }() select { - case <-time.After(sleepDuration(3 * time.Second)): + case <-time.After(3 * time.Second): // If we're time-outing: it's fine, it means we were locked! case err := <-gPasswdExited: require.ErrorIs(t, err, userslocking.ErrLock, "GPasswd should fail") @@ -191,7 +189,7 @@ func TestLockingLockedDatabase(t *testing.T) { }() select { - case <-time.After(sleepDuration(1 * time.Second)): + case <-time.After(1 * time.Second): t.Cleanup(func() { cmd.Process.Kill() }) // If we're time-outing: it's fine, it means the test-locker process is running case err := <-lockerExited: @@ -205,7 +203,7 @@ func TestLockingLockedDatabase(t *testing.T) { }() select { - case <-time.After(sleepDuration(3 * time.Second)): + case <-time.After(3 * time.Second): // If we're time-outing: it's fine, it means we were locked! case err := <-gPasswdExited: require.ErrorIs(t, err, userslocking.ErrLock, "GPasswd should fail") @@ -216,7 +214,7 @@ func TestLockingLockedDatabase(t *testing.T) { }() select { - case <-time.After(sleepDuration(3 * time.Second)): + case <-time.After(3 * time.Second): // If we're time-outing: it's fine, it means the test-locker process is // still running and holding the lock. case err := <-writeLockExited: @@ -247,7 +245,7 @@ func TestLockingLockedDatabaseFailsAfterTimeout(t *testing.T) { }() select { - case <-time.After(sleepDuration(1 * time.Second)): + case <-time.After(2 * time.Second): t.Cleanup(func() { cmd.Process.Kill() }) // If we're time-outing: it's fine, it means the test-locker process is running case err := <-lockerExited: @@ -283,7 +281,7 @@ func TestLockingLockedDatabaseWorksAfterUnlock(t *testing.T) { }() select { - case <-time.After(sleepDuration(1 * time.Second)): + case <-time.After(1 * time.Second): // If we're time-outing: it's fine, it means the test-locker process is // still running and holding the lock. t.Cleanup(func() { lockerCmd.Process.Kill() }) @@ -297,7 +295,7 @@ func TestLockingLockedDatabaseWorksAfterUnlock(t *testing.T) { }() select { - case <-time.After(sleepDuration(3 * time.Second)): + case <-time.After(3 * time.Second): // If we're time-outing: it's fine, it means the test-locker process is // still running and holding the lock. case err := <-writeLockExited: @@ -310,7 +308,7 @@ func TestLockingLockedDatabaseWorksAfterUnlock(t *testing.T) { }() select { - case <-time.After(sleepDuration(1 * time.Second)): + case <-time.After(1 * time.Second): // If we're time-outing: it's fine, it means the test-locker process is // still running and holding the lock. case err := <-writeUnLockExited: @@ -353,7 +351,3 @@ func runGPasswd(t *testing.T, args ...string) (string, error) { return runCmd(t, "gpasswd", args...) } - -func sleepDuration(in time.Duration) time.Duration { - return time.Duration(math.Round(float64(in) * testutils.SleepMultiplier())) -} From 398bc8c1436aa53b8f1f61d458eec75928c2a7a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 09:14:03 +0000 Subject: [PATCH 0466/1670] deps(go): bump github.com/ubuntu/authd from 0.5.3 to 0.5.6 Bumps [github.com/ubuntu/authd](https://github.com/ubuntu/authd) from 0.5.3 to 0.5.6. - [Commits](https://github.com/ubuntu/authd/compare/v0.5.3...v0.5.6) --- updated-dependencies: - dependency-name: github.com/ubuntu/authd dependency-version: 0.5.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f42288f8d7..671fea4f08 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/spf13/cobra v1.9.1 github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.10.0 - github.com/ubuntu/authd v0.5.3 + github.com/ubuntu/authd v0.5.6 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 golang.org/x/crypto v0.39.0 diff --git a/go.sum b/go.sum index 829c585f0b..ba28a72299 100644 --- a/go.sum +++ b/go.sum @@ -105,8 +105,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/ubuntu/authd v0.5.3 h1:/R+K/KJvF0vMrpJPipRxYPgtn5IgZKxlXMnl23MfOqk= -github.com/ubuntu/authd v0.5.3/go.mod h1:ZKAUqzql1ekDc1T3aw/hDhrPEKhkbq2MPFe3LFqulV8= +github.com/ubuntu/authd v0.5.6 h1:2QZuW2+bQEyz31s05fsZw8I8GbE22balR8zVcYssDKc= +github.com/ubuntu/authd v0.5.6/go.mod h1:fAJiEdBA4iusBgaysolNVkOKVvv0qv45HQHxMfxVcnk= github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 h1:qO8m+4mLbo1HRpD5lfhEfr7R1PuqZvbAmjaRzYEy+tM= github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5/go.mod h1:PUpwIgUuCQyuCz/gwiq6WYbo7IvtXXd8JqL01ez+jZE= github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 h1:CA2dVorxvzdsGtszqhSjyvkrXxZi4bS52ZKvP0Ko634= From e58bb738f32e9cf0b483abf1431614f908a0b453 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 09:21:36 +0000 Subject: [PATCH 0467/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.74.0 to 1.75.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.74.0...v1.75.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-version: 1.75.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f42288f8d7..08dbe1d58b 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.2 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.74.0 + github.com/microsoftgraph/msgraph-sdk-go v1.75.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 diff --git a/go.sum b/go.sum index 829c585f0b..4253b18991 100644 --- a/go.sum +++ b/go.sum @@ -65,8 +65,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.1.2 h1:1pUyA1QgIeKslQwb github.com/microsoft/kiota-serialization-multipart-go v1.1.2/go.mod h1:j2K7ZyYErloDu7Kuuk993DsvfoP7LPWvAo7rfDpdPio= github.com/microsoft/kiota-serialization-text-go v1.1.2 h1:7OfKFlzdjpPygca/+OtqafkEqCWR7+94efUFGC28cLw= github.com/microsoft/kiota-serialization-text-go v1.1.2/go.mod h1:QNTcswkBPFY3QVBFmzfk00UMNViKQtV0AQKCrRw5ibM= -github.com/microsoftgraph/msgraph-sdk-go v1.74.0 h1:Qp8IQcMbTMwjA8J8MrGab4wgB2qHDUgp04DlKjNJFyk= -github.com/microsoftgraph/msgraph-sdk-go v1.74.0/go.mod h1:5ncg4aauxM5XKHo/xvAq7Cjl6+Dqu6lOtoihSGKtDt4= +github.com/microsoftgraph/msgraph-sdk-go v1.75.0 h1:CFlM+aMhoErKBy99X91tf417EImikVWpphfi5agmRwk= +github.com/microsoftgraph/msgraph-sdk-go v1.75.0/go.mod h1:5ncg4aauxM5XKHo/xvAq7Cjl6+Dqu6lOtoihSGKtDt4= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 h1:5jCUSosTKaINzPPQXsz7wsHWwknyBmJSu8+ZWxx3kdQ= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2/go.mod h1:iD75MK3LX8EuwjDYCmh0hkojKXK6VKME33u4daCo3cE= github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= From a4a240fa0ed6496828a0732430cfd4fcb692e1ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Jun 2025 09:13:52 +0000 Subject: [PATCH 0468/1670] deps(go): bump the minor-updates group across 1 directory with 4 updates Bumps the minor-updates group with 4 updates in the / directory: [go.etcd.io/bbolt](https://github.com/etcd-io/bbolt), [golang.org/x/sys](https://github.com/golang/sys), [golang.org/x/term](https://github.com/golang/term) and [google.golang.org/grpc](https://github.com/grpc/grpc-go). Updates `go.etcd.io/bbolt` from 1.4.0 to 1.4.1 - [Release notes](https://github.com/etcd-io/bbolt/releases) - [Commits](https://github.com/etcd-io/bbolt/compare/v1.4.0...v1.4.1) Updates `golang.org/x/sys` from 0.32.0 to 0.33.0 - [Commits](https://github.com/golang/sys/compare/v0.32.0...v0.33.0) Updates `golang.org/x/term` from 0.31.0 to 0.32.0 - [Commits](https://github.com/golang/term/compare/v0.31.0...v0.32.0) Updates `google.golang.org/grpc` from 1.71.1 to 1.73.0 - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.71.1...v1.73.0) --- updated-dependencies: - dependency-name: go.etcd.io/bbolt dependency-version: 1.4.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-updates - dependency-name: golang.org/x/sys dependency-version: 0.33.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-updates - dependency-name: golang.org/x/term dependency-version: 0.32.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-updates - dependency-name: google.golang.org/grpc dependency-version: 1.73.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-updates ... Signed-off-by: dependabot[bot] --- go.mod | 10 +++++----- go.sum | 44 ++++++++++++++++++++++---------------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index 0044b55c4b..a9901ab597 100644 --- a/go.mod +++ b/go.mod @@ -22,11 +22,11 @@ require ( github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.10.0 github.com/ubuntu/decorate v0.0.0-20230606064312-bc4ac83958d6 - go.etcd.io/bbolt v1.4.0 + go.etcd.io/bbolt v1.4.1 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 - golang.org/x/sys v0.32.0 - golang.org/x/term v0.31.0 - google.golang.org/grpc v1.71.1 + golang.org/x/sys v0.33.0 + golang.org/x/term v0.32.0 + google.golang.org/grpc v1.73.0 google.golang.org/protobuf v1.36.6 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 @@ -67,7 +67,7 @@ require ( golang.org/x/net v0.38.0 // indirect golang.org/x/sync v0.12.0 // indirect golang.org/x/text v0.23.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect ) // FIXME: Use released version once we have one! diff --git a/go.sum b/go.sum index 5df17e1ce3..4b6e3e6042 100644 --- a/go.sum +++ b/go.sum @@ -42,8 +42,8 @@ github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -116,20 +116,20 @@ github.com/ubuntu/decorate v0.0.0-20230606064312-bc4ac83958d6 h1:J0625LLHcZxxnnK github.com/ubuntu/decorate v0.0.0-20230606064312-bc4ac83958d6/go.mod h1:edGgz97NOqS2oqzbKrZqO9YU9neosRrkEZbVJVQynAA= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= -go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= +go.etcd.io/bbolt v1.4.1 h1:5mOV+HWjIPLEAlUGMsveaUvK2+byZMFOzojoi7bh7uI= +go.etcd.io/bbolt v1.4.1/go.mod h1:c8zu2BnXWTu2XM4XcICtbGSl9cFwsXtcf9zLt2OncM8= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= @@ -143,16 +143,16 @@ golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= -golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= -google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= -google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= +google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From c309adc55dd1f8a70e3a51f254707fcec3459544 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 12 Jun 2025 15:14:49 +0200 Subject: [PATCH 0469/1670] Update golden file for google.golang.org/grpc 1.73.0 Updating google.golang.org/grpc from 1.71.1 to 1.73.0 changed the output of the TestRegisterGRPCServices test. --- internal/services/testdata/golden/TestRegisterGRPCServices | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/services/testdata/golden/TestRegisterGRPCServices b/internal/services/testdata/golden/TestRegisterGRPCServices index 566a759a4c..eb115926b9 100644 --- a/internal/services/testdata/golden/TestRegisterGRPCServices +++ b/internal/services/testdata/golden/TestRegisterGRPCServices @@ -51,6 +51,9 @@ grpc.health.v1.Health: - name: Check isclientstream: false isserverstream: false + - name: List + isclientstream: false + isserverstream: false - name: Watch isclientstream: false isserverstream: true From 58943ad48370ca4d3c710d99ce7c216c6c403ceb Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 24 Jun 2025 23:24:36 +0200 Subject: [PATCH 0470/1670] testutils: Get current effective UID via os.Geteuid It's more efficient to use os.Getuid (or os.Geteuid) than user.Current(), because the latter uses getpwuid which sends an NSS request while the former just retrieves the UID of the current process from the kernel via the getuid (or geteuid) syscall. We want geteuid instead of getuid because this is used for permission checking (in tests, but still). --- internal/services/permissions/testutils.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/internal/services/permissions/testutils.go b/internal/services/permissions/testutils.go index 58c5133c56..d6d95a5e02 100644 --- a/internal/services/permissions/testutils.go +++ b/internal/services/permissions/testutils.go @@ -6,8 +6,7 @@ package permissions import ( "fmt" "math" - "os/user" - "strconv" + "os" "strings" "github.com/ubuntu/authd/internal/testsdetection" @@ -29,15 +28,13 @@ func Z_ForTests_WithCurrentUserAsRoot() Option { func currentUserUID() uint32 { testsdetection.MustBeTesting() - u, err := user.Current() - if err != nil { - panic(fmt.Sprintf("could not get current user: %v", err)) - } - uid, err := strconv.ParseUint(u.Uid, 10, 0) - if err != nil || uid > math.MaxUint32 { - panic(fmt.Sprintf("current uid is not an uint32 (%v): %v", u.Uid, err)) + uid := os.Geteuid() + + if uid < 0 || uint64(uid) > math.MaxUint32 { + panic(fmt.Sprintf("current uid is not a valid uint32: %v", uid)) } + //nolint:gosec // G115 we checked for an integer overflow above. return uint32(uid) } From dcfa694115998427bf30bcd8610e6b94c9d2ba95 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 23 Jun 2025 16:46:20 +0200 Subject: [PATCH 0471/1670] log: More readable messages when using SetOutput SetOutput used the slog.TextHandler instead of the default slog.defaultHandler. That produced output like this: time=2025-06-23T16:48:23.615+02:00 level=DEBUG msg="Sending to GDM: {\"type\":\"request\",\"request\":{\"type\":\"uiLayoutCapabilities\",\"uiLayoutCapabilities\":{}}}" This commit uses a custom handler which produces output similar to the slog.defaultHandler (which we can't use because it's not exported), except that it omits the date and only prints the time instead (because we're generally not interested in the date when looking at test logs). It produces output like this: 16:48:09 DEBUG Sending to GDM: {"type":"request","request":{"type":"uiLayoutCapabilities","uiLayoutCapabilities":{}}} --- log/handler.go | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++ log/log.go | 4 +--- 2 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 log/handler.go diff --git a/log/handler.go b/log/handler.go new file mode 100644 index 0000000000..aa91524cd9 --- /dev/null +++ b/log/handler.go @@ -0,0 +1,63 @@ +package log + +import ( + "context" + "fmt" + "io" + "log/slog" +) + +// SimpleHandler writes logs in the format: . +type SimpleHandler struct { + slog.TextHandler + w io.Writer +} + +// NewSimpleHandler creates a new SimpleHandler that writes to the provided io.Writer. +func NewSimpleHandler(w io.Writer, level slog.Level) slog.Handler { + opts := &slog.HandlerOptions{ + Level: level, + } + return &SimpleHandler{ + TextHandler: *slog.NewTextHandler(w, opts), + w: w, + } +} + +// Handle implements the slog.Handler interface. +func (h *SimpleHandler) Handle(ctx context.Context, r slog.Record) error { + t := r.Time.Format("15:04:05") + _, err := fmt.Fprintf(h.w, "%s %s %s\n", t, r.Level.String(), r.Message) + return err +} + +// Enabled checks if the handler is enabled for the given log level. +func (h *SimpleHandler) Enabled(ctx context.Context, level slog.Level) bool { + return h.TextHandler.Enabled(ctx, level) +} + +// WithAttrs returns a new SimpleHandler with the specified attributes. +func (h *SimpleHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + textHandler, ok := h.TextHandler.WithAttrs(attrs).(*slog.TextHandler) + if !ok { + panic("WithAttrs did not return a *slog.TextHandler") + } + + return &SimpleHandler{ + TextHandler: *textHandler, + w: h.w, + } +} + +// WithGroup returns a new SimpleHandler with the specified group name. +func (h *SimpleHandler) WithGroup(name string) slog.Handler { + textHandler, ok := h.TextHandler.WithGroup(name).(*slog.TextHandler) + if !ok { + panic("WithGroup did not return a *slog.TextHandler") + } + + return &SimpleHandler{ + TextHandler: *textHandler, + w: h.w, + } +} diff --git a/log/log.go b/log/log.go index 15f720a3c1..a142243ad4 100644 --- a/log/log.go +++ b/log/log.go @@ -96,9 +96,7 @@ func SetLevel(level Level) (oldLevel Level) { // SetOutput sets the log output. func SetOutput(out io.Writer) { hasCustomOutput.Store(&out) - slog.SetDefault(slog.New(slog.NewTextHandler(out, &slog.HandlerOptions{ - Level: GetLevel(), - }))) + slog.SetDefault(slog.New(NewSimpleHandler(out, GetLevel()))) } // SetLevelHandler allows to define the default handler function for a given level. From d8a496558a42687e9eac7464b6d4980754b82db2 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 25 Jun 2025 13:11:08 +0200 Subject: [PATCH 0472/1670] Fix debug message Print the whole bubblewrap command, not just the arguments which were passed to runInBubbleWrap(). --- internal/testutils/bubblewrap.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/testutils/bubblewrap.go b/internal/testutils/bubblewrap.go index da81c9fb87..fdb4e59006 100644 --- a/internal/testutils/bubblewrap.go +++ b/internal/testutils/bubblewrap.go @@ -139,7 +139,7 @@ func runInBubbleWrap(t *testing.T, withSudo bool, testDataPath string, env []str cmd.Stdout = os.Stdout } - t.Log("Running command", strings.Join(args, " ")) + t.Log("Running command:", cmd.String()) err = cmd.Run() output := strings.TrimSpace(b.String()) From 6f4c5e94761030dee4f66ff2216905ff75ea5b8f Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 25 Jun 2025 13:12:01 +0200 Subject: [PATCH 0473/1670] Improve debug message --- internal/testutils/bubblewrap.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/testutils/bubblewrap.go b/internal/testutils/bubblewrap.go index fdb4e59006..42ba2a15e4 100644 --- a/internal/testutils/bubblewrap.go +++ b/internal/testutils/bubblewrap.go @@ -144,7 +144,7 @@ func runInBubbleWrap(t *testing.T, withSudo bool, testDataPath string, env []str output := strings.TrimSpace(b.String()) if !testing.Verbose() { - t.Log(output) + t.Logf("Command output\n%s", output) } return output, err } From f7215a8f3fd2ee149dd9ed71dbdb0d98f209721b Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 25 Jun 2025 13:18:28 +0200 Subject: [PATCH 0474/1670] Fix TestUsersLockingInBubbleWrap/TestReadWhileLocked The test case failed on my system which has authd installed, because the `getent group` command also returned the authd groups. Fix that by specifying which group entries `getent` should return. --- internal/users/locking/locking_bwrap_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/users/locking/locking_bwrap_test.go b/internal/users/locking/locking_bwrap_test.go index 050163b130..2179345d34 100644 --- a/internal/users/locking/locking_bwrap_test.go +++ b/internal/users/locking/locking_bwrap_test.go @@ -78,7 +78,7 @@ testgroup:x:1001:testuser` require.NoError(t, err, "Locking once it is allowed") t.Cleanup(func() { userslocking.WriteUnlock() }) - output, err := runCmd(t, "getent", "group") + output, err := runCmd(t, "getent", "group", "root", "testgroup") require.NoError(t, err, "Reading should be allowed") require.Equal(t, groupContents, output) } From feb4561b4424c8f0490e783149c339701a7a0452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 25 Jun 2025 17:54:36 +0200 Subject: [PATCH 0475/1670] users/localentries/getgrent: Use safer *_r implementations We are getting some races for the way we handle the errno, which is by design unsafe, especially in a multi-threading context. glibc provides some safer implementations that instead of setting errno, they actually return the error we got. Use them instead to avoid having races. Also, while `getgrent_r` is not thread safe, `getgrnam_r` it is, thus we don't need any more an errno implicit lock. Test both working in parallel too --- internal/users/localentries/getgrent_c.go | 132 +++++++++++-------- internal/users/localentries/getgrent_test.go | 45 +++++-- 2 files changed, 111 insertions(+), 66 deletions(-) diff --git a/internal/users/localentries/getgrent_c.go b/internal/users/localentries/getgrent_c.go index 888b8d8131..acdee706f7 100644 --- a/internal/users/localentries/getgrent_c.go +++ b/internal/users/localentries/getgrent_c.go @@ -4,19 +4,22 @@ package localentries /* +#define _GNU_SOURCE + #include #include #include -#include */ import "C" import ( "errors" - "fmt" + "runtime" "sync" + "syscall" + "unsafe" - "github.com/ubuntu/authd/internal/errno" + "github.com/ubuntu/decorate" ) // Group represents a group entry. @@ -28,81 +31,98 @@ type Group struct { var getgrentMu sync.Mutex -func getGroupEntry() (*C.struct_group, error) { - errno.Lock() - defer errno.Unlock() - - cGroup := C.getgrent() - if cGroup != nil { - return cGroup, nil - } - - err := errno.Get() - // It's not documented in the man page, but apparently getgrent sets errno to ENOENT when there are no more - // entries in the group database. - if errors.Is(err, errno.ErrNoEnt) { - return nil, nil - } - if err != nil { - return nil, fmt.Errorf("getgrent: %v", err) - } - return cGroup, nil -} - // GetGroupEntries returns all group entries. -func GetGroupEntries() ([]Group, error) { - // This function repeatedly calls getgrent, which iterates over the records in the group database. +func GetGroupEntries() (entries []Group, err error) { + decorate.OnError(&err, "getgrent_r") + + // This function repeatedly calls getgrent_r, which iterates over the records in the group database. // Use a mutex to avoid that parallel calls to this function interfere with each other. + // It would be nice to use fgetgrent_r, that is thread safe, but it can only + // iterate over a stream, while we want to iterate over all the NSS sources too. getgrentMu.Lock() defer getgrentMu.Unlock() C.setgrent() defer C.endgrent() - var entries []Group + var group C.struct_group + var groupPtr *C.struct_group + buf := make([]C.char, 1024) + + pinner := runtime.Pinner{} + defer pinner.Unpin() + + pinner.Pin(&group) + pinner.Pin(&buf[0]) + for { - cGroup, err := getGroupEntry() - if err != nil { - return nil, err + ret := C.getgrent_r(&group, &buf[0], C.size_t(len(buf)), &groupPtr) + errno := syscall.Errno(ret) + + if errors.Is(errno, syscall.ERANGE) { + buf = make([]C.char, len(buf)*2) + pinner.Pin(&buf[0]) + continue } - if cGroup == nil { - // No more entries in the group database. - break + if errors.Is(errno, syscall.ENOENT) { + return entries, nil + } + if !errors.Is(errno, syscall.Errno(0)) { + return nil, errno } entries = append(entries, Group{ - Name: C.GoString(cGroup.gr_name), - GID: uint32(cGroup.gr_gid), - Passwd: C.GoString(cGroup.gr_passwd), + Name: C.GoString(groupPtr.gr_name), + Passwd: C.GoString(groupPtr.gr_passwd), + GID: uint32(groupPtr.gr_gid), }) } - - return entries, nil } // ErrGroupNotFound is returned when a group is not found. var ErrGroupNotFound = errors.New("group not found") // GetGroupByName returns the group with the given name. -func GetGroupByName(name string) (Group, error) { - errno.Lock() - defer errno.Unlock() - - cGroup := C.getgrnam(C.CString(name)) - if cGroup == nil { - err := errno.Get() - if err == nil || - errors.Is(err, errno.ErrNoEnt) || - errors.Is(err, errno.ErrSrch) || - errors.Is(err, errno.ErrBadf) || - errors.Is(err, errno.ErrPerm) { +func GetGroupByName(name string) (g Group, err error) { + decorate.OnError(&err, "getgrnam_r") + + var group C.struct_group + var groupPtr *C.struct_group + buf := make([]C.char, 256) + + pinner := runtime.Pinner{} + defer pinner.Unpin() + + pinner.Pin(&group) + pinner.Pin(&buf[0]) + + cName := C.CString(name) + defer C.free(unsafe.Pointer(cName)) + + for { + ret := C.getgrnam_r(cName, &group, &buf[0], C.size_t(len(buf)), &groupPtr) + errno := syscall.Errno(ret) + + if errors.Is(errno, syscall.ERANGE) { + buf = make([]C.char, len(buf)*2) + pinner.Pin(&buf[0]) + continue + } + if (errors.Is(errno, syscall.Errno(0)) && groupPtr == nil) || + errors.Is(errno, syscall.ENOENT) || + errors.Is(errno, syscall.ESRCH) || + errors.Is(errno, syscall.EBADF) || + errors.Is(errno, syscall.EPERM) { return Group{}, ErrGroupNotFound } - return Group{}, fmt.Errorf("getgrnam: %v", err) - } + if !errors.Is(errno, syscall.Errno(0)) { + return Group{}, errno + } - return Group{ - Name: C.GoString(cGroup.gr_name), - GID: uint32(cGroup.gr_gid), - }, nil + return Group{ + Name: C.GoString(groupPtr.gr_name), + GID: uint32(groupPtr.gr_gid), + Passwd: C.GoString(groupPtr.gr_passwd), + }, nil + } } diff --git a/internal/users/localentries/getgrent_test.go b/internal/users/localentries/getgrent_test.go index 3c4bec031e..c8a6e9558c 100644 --- a/internal/users/localentries/getgrent_test.go +++ b/internal/users/localentries/getgrent_test.go @@ -1,6 +1,8 @@ package localentries import ( + "fmt" + "slices" "testing" "github.com/stretchr/testify/require" @@ -9,24 +11,47 @@ import ( func TestGetGroupEntries(t *testing.T) { t.Parallel() - got, err := GetGroupEntries() - require.NoError(t, err, "GetGroupEntries should never return an error") - require.NotEmpty(t, got, "GetGroupEntries should never return an empty list") + // Ensure the requests can be parallelized + for idx := range 10 { + t.Run(fmt.Sprintf("iteration_%d", idx), func(t *testing.T) { + t.Parallel() + + got, err := GetGroupEntries() + require.NoError(t, err, "GetGroupEntries should never return an error") + require.NotEmpty(t, got, "GetGroupEntries should never return an empty list") + require.True(t, slices.ContainsFunc(got, func(g Group) bool { + return g.Name == "root" && g.GID == 0 + }), "GetGroupEntries should return root") + }) + } } func TestGetGroupByName(t *testing.T) { t.Parallel() - got, err := GetGroupByName("root") - require.NoError(t, err, "GetGroupByName should not return an error") - require.Equal(t, got.Name, "root") - require.Equal(t, got.GID, uint32(0)) + for idx := range 10 { + t.Run(fmt.Sprintf("iteration_%d", idx), func(t *testing.T) { + t.Parallel() + + got, err := GetGroupByName("root") + require.NoError(t, err, "GetGroupByName should not return an error") + require.Equal(t, got.Name, "root") + require.Equal(t, got.GID, uint32(0)) + require.Equal(t, got.Passwd, "x") + }) + } } func TestGetGroupByName_NotFound(t *testing.T) { t.Parallel() - got, err := GetGroupByName("nonexistent") - require.ErrorIs(t, err, ErrGroupNotFound) - require.Equal(t, got.Name, "") + for idx := range 10 { + t.Run(fmt.Sprintf("iteration_%d", idx), func(t *testing.T) { + t.Parallel() + + got, err := GetGroupByName(fmt.Sprintf("nonexistent-really-%d", idx)) + require.ErrorIs(t, err, ErrGroupNotFound) + require.Equal(t, got.Name, "") + }) + } } From 877955b15521b8fae7e60f357e068dbd1169cbdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 25 Jun 2025 18:16:08 +0200 Subject: [PATCH 0476/1670] users/localentries/getpwent: Use safer *_r implementations As in previous commit... We are getting some races for the way we handle the errno, which is by design unsafe, especially in a multi-threading context. glibc provides some safer implementations that instead of setting errno, they actually return the error we got. Use them instead to avoid having races. Also, while `getpwent_r` is not thread safe, `getpwnam_r` it is, thus we don't need any more an errno implicit lock. Test both working in parallel too --- internal/users/localentries/getpwent_c.go | 130 +++++++++++-------- internal/users/localentries/getpwent_test.go | 52 +++++--- 2 files changed, 110 insertions(+), 72 deletions(-) diff --git a/internal/users/localentries/getpwent_c.go b/internal/users/localentries/getpwent_c.go index 74b0e431d5..cf4d96768a 100644 --- a/internal/users/localentries/getpwent_c.go +++ b/internal/users/localentries/getpwent_c.go @@ -7,16 +7,17 @@ package localentries #include #include #include -#include */ import "C" import ( "errors" - "fmt" + "runtime" "sync" + "syscall" + "unsafe" - "github.com/ubuntu/authd/internal/errno" + "github.com/ubuntu/decorate" ) // Passwd represents a passwd entry. @@ -28,81 +29,98 @@ type Passwd struct { var getpwentMu sync.Mutex -func getPasswdEntry() (*C.struct_passwd, error) { - errno.Lock() - defer errno.Unlock() - - cPasswd := C.getpwent() - if cPasswd != nil { - return cPasswd, nil - } - - err := errno.Get() - // It's not documented in the man page, but apparently getpwent sets errno to ENOENT when there are no more - // entries in the passwd database. - if errors.Is(err, errno.ErrNoEnt) { - return nil, nil - } - if err != nil { - return nil, fmt.Errorf("getpwent: %v", err) - } - return cPasswd, nil -} - // GetPasswdEntries returns all passwd entries. -func GetPasswdEntries() ([]Passwd, error) { - // This function repeatedly calls getpwent, which iterates over the records in the passwd database. +func GetPasswdEntries() (entries []Passwd, err error) { + decorate.OnError(&err, "getpwent_r") + + // This function repeatedly calls getpwent_r, which iterates over the records in the passwd database. // Use a mutex to avoid that parallel calls to this function interfere with each other. + // It would be nice to use fgetpwent_r, that is thread safe, but it can only + // iterate over a stream, while we want to iterate over all the NSS sources too. getpwentMu.Lock() defer getpwentMu.Unlock() C.setpwent() defer C.endpwent() - var entries []Passwd + var passwd C.struct_passwd + var passwdPtr *C.struct_passwd + buf := make([]C.char, 1024) + + pinner := runtime.Pinner{} + defer pinner.Unpin() + + pinner.Pin(&passwd) + pinner.Pin(&buf[0]) + for { - cPasswd, err := getPasswdEntry() - if err != nil { - return nil, err + ret := C.getpwent_r(&passwd, &buf[0], C.size_t(len(buf)), &passwdPtr) + errno := syscall.Errno(ret) + + if errors.Is(errno, syscall.ERANGE) { + buf = make([]C.char, len(buf)*2) + pinner.Pin(&buf[0]) + continue + } + if errors.Is(errno, syscall.ENOENT) { + return entries, nil } - if cPasswd == nil { - // No more entries in the passwd database. - break + if !errors.Is(errno, syscall.Errno(0)) { + return nil, errno } entries = append(entries, Passwd{ - Name: C.GoString(cPasswd.pw_name), - UID: uint32(cPasswd.pw_uid), - Gecos: C.GoString(cPasswd.pw_gecos), + Name: C.GoString(passwdPtr.pw_name), + UID: uint32(passwdPtr.pw_uid), + Gecos: C.GoString(passwdPtr.pw_gecos), }) } - - return entries, nil } // ErrUserNotFound is returned when a user is not found. var ErrUserNotFound = errors.New("user not found") // GetPasswdByName returns the user with the given name. -func GetPasswdByName(name string) (Passwd, error) { - errno.Lock() - defer errno.Unlock() - - cPasswd := C.getpwnam(C.CString(name)) - if cPasswd == nil { - err := errno.Get() - if err == nil || - errors.Is(err, errno.ErrNoEnt) || - errors.Is(err, errno.ErrSrch) || - errors.Is(err, errno.ErrBadf) || - errors.Is(err, errno.ErrPerm) { +func GetPasswdByName(name string) (p Passwd, err error) { + decorate.OnError(&err, "getgrnam_r") + + var passwd C.struct_passwd + var passwdPtr *C.struct_passwd + buf := make([]C.char, 256) + + pinner := runtime.Pinner{} + defer pinner.Unpin() + + pinner.Pin(&passwd) + pinner.Pin(&buf[0]) + + cName := C.CString(name) + defer C.free(unsafe.Pointer(cName)) + + for { + ret := C.getpwnam_r(cName, &passwd, &buf[0], C.size_t(len(buf)), &passwdPtr) + errno := syscall.Errno(ret) + + if errors.Is(errno, syscall.ERANGE) { + buf = make([]C.char, len(buf)*2) + pinner.Pin(&buf[0]) + continue + } + if (errors.Is(errno, syscall.Errno(0)) && passwdPtr == nil) || + errors.Is(errno, syscall.ENOENT) || + errors.Is(errno, syscall.ESRCH) || + errors.Is(errno, syscall.EBADF) || + errors.Is(errno, syscall.EPERM) { return Passwd{}, ErrUserNotFound } - return Passwd{}, fmt.Errorf("getpwnam: %v", err) - } + if !errors.Is(errno, syscall.Errno(0)) { + return Passwd{}, errno + } - return Passwd{ - Name: C.GoString(cPasswd.pw_name), - UID: uint32(cPasswd.pw_uid), - }, nil + return Passwd{ + Name: C.GoString(passwdPtr.pw_name), + UID: uint32(passwdPtr.pw_uid), + Gecos: C.GoString(passwdPtr.pw_gecos), + }, nil + } } diff --git a/internal/users/localentries/getpwent_test.go b/internal/users/localentries/getpwent_test.go index b70bd893f8..163c22d452 100644 --- a/internal/users/localentries/getpwent_test.go +++ b/internal/users/localentries/getpwent_test.go @@ -1,6 +1,7 @@ package localentries import ( + "fmt" "slices" "testing" @@ -10,30 +11,49 @@ import ( func TestGetPasswdEntries(t *testing.T) { t.Parallel() - got, err := GetPasswdEntries() - require.NoError(t, err, "GetPasswdEntries should never return an error") - require.NotEmpty(t, got, "GetPasswdEntries should never return an empty list") - - // Check if the root user is present in the list - rootFound := slices.ContainsFunc(got, func(entry Passwd) bool { - return entry.Name == "root" - }) - require.True(t, rootFound, "GetPasswdEntries should always return root") + for idx := range 10 { + t.Run(fmt.Sprintf("iteration_%d", idx), func(t *testing.T) { + t.Parallel() + + got, err := GetPasswdEntries() + require.NoError(t, err, "GetPasswdEntries should never return an error") + require.NotEmpty(t, got, "GetPasswdEntries should never return an empty list") + + // Check if the root user is present in the list + rootFound := slices.ContainsFunc(got, func(entry Passwd) bool { + return entry.Name == "root" && entry.UID == 0 + }) + require.True(t, rootFound, "GetPasswdEntries should always return root") + }) + } } func TestGetPasswdByName(t *testing.T) { t.Parallel() - got, err := GetPasswdByName("root") - require.NoError(t, err, "GetPasswdByName should not return an error") - require.Equal(t, got.Name, "root") - require.Equal(t, got.UID, uint32(0)) + for idx := range 10 { + t.Run(fmt.Sprintf("iteration_%d", idx), func(t *testing.T) { + t.Parallel() + + got, err := GetPasswdByName("root") + require.NoError(t, err, "GetPasswdByName should not return an error") + require.Equal(t, "root", got.Name, "Name does not match") + require.Equal(t, uint32(0), got.UID, "UID does not match") + require.Equal(t, "root", got.Gecos, "Gecos does not match") + }) + } } func TestGetPasswdByName_NotFound(t *testing.T) { t.Parallel() - got, err := GetPasswdByName("nonexistent") - require.ErrorIs(t, err, ErrUserNotFound) - require.Equal(t, got.Name, "") + for idx := range 10 { + t.Run(fmt.Sprintf("iteration_%d", idx), func(t *testing.T) { + t.Parallel() + + got, err := GetPasswdByName(fmt.Sprintf("nonexistent-really-%d", idx)) + require.ErrorIs(t, err, ErrUserNotFound) + require.Empty(t, got, "Entry should be empty, but is not") + }) + } } From 8b6155f276fcf10c963f5e574ad35c815999413b Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 26 Jun 2025 11:49:49 +0200 Subject: [PATCH 0477/1670] Remove unused golden files These are leftovers from removed or renamed test cases. --- .../testdata/TestRegisterGRPCServices/golden | 54 ------------------- .../Get_users_only_rely_on_valid_userByID | 38 ------------- .../db/testdata/golden/TestBrokerForUser | 1 - ...database_to_lowercase_user_and_group_names | 18 ------- ...w_migrates_database_to_lowercase_usernames | 18 ------- .../Update_last_login_time_for_user | 17 ------ 6 files changed, 146 deletions(-) delete mode 100644 internal/services/testdata/TestRegisterGRPCServices/golden delete mode 100644 internal/users/db/testdata/golden/TestAllUsers/Get_users_only_rely_on_valid_userByID delete mode 100644 internal/users/db/testdata/golden/TestBrokerForUser delete mode 100644 internal/users/db/testdata/golden/TestNew/New_migrates_database_to_lowercase_user_and_group_names delete mode 100644 internal/users/db/testdata/golden/TestNew/New_migrates_database_to_lowercase_usernames delete mode 100644 internal/users/db/testdata/golden/TestUpdateUserEntry/Update_last_login_time_for_user diff --git a/internal/services/testdata/TestRegisterGRPCServices/golden b/internal/services/testdata/TestRegisterGRPCServices/golden deleted file mode 100644 index 22990ce9f3..0000000000 --- a/internal/services/testdata/TestRegisterGRPCServices/golden +++ /dev/null @@ -1,54 +0,0 @@ -authd.NSS: - methods: - - name: GetGroupByID - isclientstream: false - isserverstream: false - - name: GetGroupByName - isclientstream: false - isserverstream: false - - name: GetGroupEntries - isclientstream: false - isserverstream: false - - name: GetPasswdByName - isclientstream: false - isserverstream: false - - name: GetPasswdByUID - isclientstream: false - isserverstream: false - - name: GetPasswdEntries - isclientstream: false - isserverstream: false - - name: GetShadowByName - isclientstream: false - isserverstream: false - - name: GetShadowEntries - isclientstream: false - isserverstream: false - metadata: authd.proto -authd.PAM: - methods: - - name: AvailableBrokers - isclientstream: false - isserverstream: false - - name: EndSession - isclientstream: false - isserverstream: false - - name: GetAuthenticationModes - isclientstream: false - isserverstream: false - - name: GetPreviousBroker - isclientstream: false - isserverstream: false - - name: IsAuthenticated - isclientstream: false - isserverstream: false - - name: SelectAuthenticationMode - isclientstream: false - isserverstream: false - - name: SelectBroker - isclientstream: false - isserverstream: false - - name: SetDefaultBrokerForUser - isclientstream: false - isserverstream: false - metadata: authd.proto diff --git a/internal/users/db/testdata/golden/TestAllUsers/Get_users_only_rely_on_valid_userByID b/internal/users/db/testdata/golden/TestAllUsers/Get_users_only_rely_on_valid_userByID deleted file mode 100644 index 9a105be784..0000000000 --- a/internal/users/db/testdata/golden/TestAllUsers/Get_users_only_rely_on_valid_userByID +++ /dev/null @@ -1,38 +0,0 @@ -- name: user1 - uid: 1111 - gid: 11111 - gecos: |- - User1 gecos - On multiple lines - dir: /home/user1 - shell: /bin/bash - lastpwdchange: -1 - maxpwdage: -1 - pwdwarnperiod: -1 - pwdinactivity: -1 - minpwdage: -1 - expirationdate: -1 -- name: user2 - uid: 2222 - gid: 22222 - gecos: User2 - dir: /home/user2 - shell: /bin/dash - lastpwdchange: -1 - maxpwdage: -1 - pwdwarnperiod: -1 - pwdinactivity: -1 - minpwdage: -1 - expirationdate: -1 -- name: user3 - uid: 3333 - gid: 33333 - gecos: User3 - dir: /home/user3 - shell: /bin/zsh - lastpwdchange: -1 - maxpwdage: -1 - pwdwarnperiod: -1 - pwdinactivity: -1 - minpwdage: -1 - expirationdate: -1 diff --git a/internal/users/db/testdata/golden/TestBrokerForUser b/internal/users/db/testdata/golden/TestBrokerForUser deleted file mode 100644 index ffe84c0bce..0000000000 --- a/internal/users/db/testdata/golden/TestBrokerForUser +++ /dev/null @@ -1 +0,0 @@ -broker-id \ No newline at end of file diff --git a/internal/users/db/testdata/golden/TestNew/New_migrates_database_to_lowercase_user_and_group_names b/internal/users/db/testdata/golden/TestNew/New_migrates_database_to_lowercase_user_and_group_names deleted file mode 100644 index 0f479a482b..0000000000 --- a/internal/users/db/testdata/golden/TestNew/New_migrates_database_to_lowercase_user_and_group_names +++ /dev/null @@ -1,18 +0,0 @@ -users: - - name: user1 - uid: 1111 - gid: 11111 - gecos: |- - User1 gecos - On multiple lines - dir: /home/user1 - shell: /bin/bash - broker_id: broker-id -groups: - - name: group1 - gid: 11111 - ugid: "12345678" -users_to_groups: - - uid: 1111 - gid: 11111 -schema_version: 1 diff --git a/internal/users/db/testdata/golden/TestNew/New_migrates_database_to_lowercase_usernames b/internal/users/db/testdata/golden/TestNew/New_migrates_database_to_lowercase_usernames deleted file mode 100644 index 56bcad61a0..0000000000 --- a/internal/users/db/testdata/golden/TestNew/New_migrates_database_to_lowercase_usernames +++ /dev/null @@ -1,18 +0,0 @@ -users: - - name: user1 - uid: 1111 - gid: 11111 - gecos: |- - User1 gecos - On multiple lines - dir: /home/user1 - shell: /bin/bash - broker_id: broker-id -groups: - - name: Group1 - gid: 11111 - ugid: "12345678" -users_to_groups: - - uid: 1111 - gid: 11111 -schema_version: 1 diff --git a/internal/users/db/testdata/golden/TestUpdateUserEntry/Update_last_login_time_for_user b/internal/users/db/testdata/golden/TestUpdateUserEntry/Update_last_login_time_for_user deleted file mode 100644 index 5d9b092038..0000000000 --- a/internal/users/db/testdata/golden/TestUpdateUserEntry/Update_last_login_time_for_user +++ /dev/null @@ -1,17 +0,0 @@ -users: - - name: user1 - uid: 1111 - gid: 11111 - gecos: |- - User1 gecos - On multiple lines - dir: /home/user1 - shell: /bin/bash - last_login: 2020-01-01T00:00:00Z -groups: - - name: group1 - gid: 11111 - ugid: "12345678" -users_to_groups: - - uid: 1111 - gid: 11111 From f75acea1486ecc6aa31628275fe440468ea23b68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 23 Jun 2025 17:25:53 +0200 Subject: [PATCH 0478/1670] pam/integration-tests: Drop unused golden files --- ...icate_user_successfully_with_socket_stdout | 47 -- .../Authenticate_user_with_qr_code_in_polkit | 696 ------------------ 2 files changed, 743 deletions(-) delete mode 100644 pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_successfully_with_socket_stdout delete mode 100644 pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_qr_code_in_polkit diff --git a/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_successfully_with_socket_stdout b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_successfully_with_socket_stdout deleted file mode 100644 index 5e8d6585d7..0000000000 --- a/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_successfully_with_socket_stdout +++ /dev/null @@ -1,47 +0,0 @@ -> python3 /DEV/authd/pam/integration-tests/sshuttle-sudo.py ./pam_authd login socket=${AUTHD_TES -T_TAPE_SOCKET} -== Provider selection == - 1. local - 2. ExampleBroker -Choose your provider: -> -──────────────────────────────────────────────────────────────────────────────── -> python3 /DEV/authd/pam/integration-tests/sshuttle-sudo.py ./pam_authd login socket=${AUTHD_TES -T_TAPE_SOCKET} -== Provider selection == - 1. local - 2. ExampleBroker -Choose your provider: -> 2 -──────────────────────────────────────────────────────────────────────────────── -> python3 /DEV/authd/pam/integration-tests/sshuttle-sudo.py ./pam_authd login socket=${AUTHD_TES -T_TAPE_SOCKET} -== Provider selection == - 1. local - 2. ExampleBroker -Choose your provider: -> 2 -== Password authentication == -Enter 'r' to cancel the request and go back to select the authentication method -Gimme your password: -> -──────────────────────────────────────────────────────────────────────────────── -> python3 /DEV/authd/pam/integration-tests/sshuttle-sudo.py ./pam_authd login socket=${AUTHD_TES -T_TAPE_SOCKET} -== Provider selection == - 1. local - 2. ExampleBroker -Choose your provider: -> 2 -== Password authentication == -Enter 'r' to cancel the request and go back to select the authentication method -Gimme your password: -> -PAM Authenticate() - User: "user-integration-native-authenticate-user-successfully-with-socket-stdout" - Result: success -PAM AcctMgmt() - User: "user-integration-native-authenticate-user-successfully-with-socket-stdout" - Result: success -> -──────────────────────────────────────────────────────────────────────────────── diff --git a/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_qr_code_in_polkit b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_qr_code_in_polkit deleted file mode 100644 index 4d83d2e3cb..0000000000 --- a/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_qr_code_in_polkit +++ /dev/null @@ -1,696 +0,0 @@ -> ./pam_authd login socket=${AUTHD_TEST_LOGIN_SOCK} force_native_client=true -== Provider selection == - 1. local - 2. ExampleBroker -Choose your provider: -> -──────────────────────────────────────────────────────────────────────────────── -> if [ -v AUTHD_PAM_CLI_TERM ]; then export TERM=${AUTHD_PAM_CLI_TERM}; fi -> ./pam_authd login socket=${AUTHD_TEST_LOGIN_SOCK} force_native_client=true -== Provider selection == - 1. local - 2. ExampleBroker -Choose your provider: -> 2 -──────────────────────────────────────────────────────────────────────────────── -> if [ -v AUTHD_PAM_CLI_TERM ]; then export TERM=${AUTHD_PAM_CLI_TERM}; fi -> ./pam_authd login socket=${AUTHD_TEST_LOGIN_SOCK} force_native_client=true -== Provider selection == - 1. local - 2. ExampleBroker -Choose your provider: -> 2 -== Password authentication == -Enter 'r' to cancel the request and go back to select the authentication method -Gimme your password: -> -──────────────────────────────────────────────────────────────────────────────── -> if [ -v AUTHD_PAM_CLI_TERM ]; then export TERM=${AUTHD_PAM_CLI_TERM}; fi -> ./pam_authd login socket=${AUTHD_TEST_LOGIN_SOCK} force_native_client=true -== Provider selection == - 1. local - 2. ExampleBroker -Choose your provider: -> 2 -== Password authentication == -Enter 'r' to cancel the request and go back to select the authentication method -Gimme your password: -> -== Authentication method selection == - 1. Password authentication - 2. Use a Login code - 3. Send URL to user-integration-native-authenticate-user-with-qr-code-in-polkit@gmail.com - 4. Use your fido device foo - 5. Use your phone +33... - 6. Use your phone +1... - 7. Pin code - 8. Authentication code -Or enter 'r' to go back to choose the provider -Choose your authentication method: -> -──────────────────────────────────────────────────────────────────────────────── -> if [ -v AUTHD_PAM_CLI_TERM ]; then export TERM=${AUTHD_PAM_CLI_TERM}; fi -> ./pam_authd login socket=${AUTHD_TEST_LOGIN_SOCK} force_native_client=true -== Provider selection == - 1. local - 2. ExampleBroker -Choose your provider: -> 2 -== Password authentication == -Enter 'r' to cancel the request and go back to select the authentication method -Gimme your password: -> -== Authentication method selection == - 1. Password authentication - 2. Use a Login code - 3. Send URL to user-integration-native-authenticate-user-with-qr-code-in-polkit@gmail.com - 4. Use your fido device foo - 5. Use your phone +33... - 6. Use your phone +1... - 7. Pin code - 8. Authentication code -Or enter 'r' to go back to choose the provider -Choose your authentication method: -> 2 -──────────────────────────────────────────────────────────────────────────────── -> if [ -v AUTHD_PAM_CLI_TERM ]; then export TERM=${AUTHD_PAM_CLI_TERM}; fi -> ./pam_authd login socket=${AUTHD_TEST_LOGIN_SOCK} force_native_client=true -== Provider selection == - 1. local - 2. ExampleBroker -Choose your provider: -> 2 -== Password authentication == -Enter 'r' to cancel the request and go back to select the authentication method -Gimme your password: -> -== Authentication method selection == - 1. Password authentication - 2. Use a Login code - 3. Send URL to user-integration-native-authenticate-user-with-qr-code-in-polkit@gmail.com - 4. Use your fido device foo - 5. Use your phone +33... - 6. Use your phone +1... - 7. Pin code - 8. Authentication code -Or enter 'r' to go back to choose the provider -Choose your authentication method: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntu.com - 1337 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> -──────────────────────────────────────────────────────────────────────────────── -> if [ -v AUTHD_PAM_CLI_TERM ]; then export TERM=${AUTHD_PAM_CLI_TERM}; fi -> ./pam_authd login socket=${AUTHD_TEST_LOGIN_SOCK} force_native_client=true -== Provider selection == - 1. local - 2. ExampleBroker -Choose your provider: -> 2 -== Password authentication == -Enter 'r' to cancel the request and go back to select the authentication method -Gimme your password: -> -== Authentication method selection == - 1. Password authentication - 2. Use a Login code - 3. Send URL to user-integration-native-authenticate-user-with-qr-code-in-polkit@gmail.com - 4. Use your fido device foo - 5. Use your phone +33... - 6. Use your phone +1... - 7. Pin code - 8. Authentication code -Or enter 'r' to go back to choose the provider -Choose your authentication method: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntu.com - 1337 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -──────────────────────────────────────────────────────────────────────────────── -> if [ -v AUTHD_PAM_CLI_TERM ]; then export TERM=${AUTHD_PAM_CLI_TERM}; fi -> ./pam_authd login socket=${AUTHD_TEST_LOGIN_SOCK} force_native_client=true -== Provider selection == - 1. local - 2. ExampleBroker -Choose your provider: -> 2 -== Password authentication == -Enter 'r' to cancel the request and go back to select the authentication method -Gimme your password: -> -== Authentication method selection == - 1. Password authentication - 2. Use a Login code - 3. Send URL to user-integration-native-authenticate-user-with-qr-code-in-polkit@gmail.com - 4. Use your fido device foo - 5. Use your phone +33... - 6. Use your phone +1... - 7. Pin code - 8. Authentication code -Or enter 'r' to go back to choose the provider -Choose your authentication method: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntu.com - 1337 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntu.fr/ - 1338 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> -──────────────────────────────────────────────────────────────────────────────── -> if [ -v AUTHD_PAM_CLI_TERM ]; then export TERM=${AUTHD_PAM_CLI_TERM}; fi -> ./pam_authd login socket=${AUTHD_TEST_LOGIN_SOCK} force_native_client=true -== Provider selection == - 1. local - 2. ExampleBroker -Choose your provider: -> 2 -== Password authentication == -Enter 'r' to cancel the request and go back to select the authentication method -Gimme your password: -> -== Authentication method selection == - 1. Password authentication - 2. Use a Login code - 3. Send URL to user-integration-native-authenticate-user-with-qr-code-in-polkit@gmail.com - 4. Use your fido device foo - 5. Use your phone +33... - 6. Use your phone +1... - 7. Pin code - 8. Authentication code -Or enter 'r' to go back to choose the provider -Choose your authentication method: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntu.com - 1337 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntu.fr/ - 1338 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -──────────────────────────────────────────────────────────────────────────────── -> if [ -v AUTHD_PAM_CLI_TERM ]; then export TERM=${AUTHD_PAM_CLI_TERM}; fi -> ./pam_authd login socket=${AUTHD_TEST_LOGIN_SOCK} force_native_client=true -== Provider selection == - 1. local - 2. ExampleBroker -Choose your provider: -> 2 -== Password authentication == -Enter 'r' to cancel the request and go back to select the authentication method -Gimme your password: -> -== Authentication method selection == - 1. Password authentication - 2. Use a Login code - 3. Send URL to user-integration-native-authenticate-user-with-qr-code-in-polkit@gmail.com - 4. Use your fido device foo - 5. Use your phone +33... - 6. Use your phone +1... - 7. Pin code - 8. Authentication code -Or enter 'r' to go back to choose the provider -Choose your authentication method: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntu.com - 1337 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntu.fr/ - 1338 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntuforum-br.org/ - 1339 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> -──────────────────────────────────────────────────────────────────────────────── -> if [ -v AUTHD_PAM_CLI_TERM ]; then export TERM=${AUTHD_PAM_CLI_TERM}; fi -> ./pam_authd login socket=${AUTHD_TEST_LOGIN_SOCK} force_native_client=true -== Provider selection == - 1. local - 2. ExampleBroker -Choose your provider: -> 2 -== Password authentication == -Enter 'r' to cancel the request and go back to select the authentication method -Gimme your password: -> -== Authentication method selection == - 1. Password authentication - 2. Use a Login code - 3. Send URL to user-integration-native-authenticate-user-with-qr-code-in-polkit@gmail.com - 4. Use your fido device foo - 5. Use your phone +33... - 6. Use your phone +1... - 7. Pin code - 8. Authentication code -Or enter 'r' to go back to choose the provider -Choose your authentication method: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntu.com - 1337 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntu.fr/ - 1338 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntuforum-br.org/ - 1339 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -──────────────────────────────────────────────────────────────────────────────── -> if [ -v AUTHD_PAM_CLI_TERM ]; then export TERM=${AUTHD_PAM_CLI_TERM}; fi -> ./pam_authd login socket=${AUTHD_TEST_LOGIN_SOCK} force_native_client=true -== Provider selection == - 1. local - 2. ExampleBroker -Choose your provider: -> 2 -== Password authentication == -Enter 'r' to cancel the request and go back to select the authentication method -Gimme your password: -> -== Authentication method selection == - 1. Password authentication - 2. Use a Login code - 3. Send URL to user-integration-native-authenticate-user-with-qr-code-in-polkit@gmail.com - 4. Use your fido device foo - 5. Use your phone +33... - 6. Use your phone +1... - 7. Pin code - 8. Authentication code -Or enter 'r' to go back to choose the provider -Choose your authentication method: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntu.com - 1337 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntu.fr/ - 1338 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntuforum-br.org/ - 1339 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -== Use a Login code == -Enter the code in the login page -https://www.ubuntu-it.org/ - 1340 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> -──────────────────────────────────────────────────────────────────────────────── -> if [ -v AUTHD_PAM_CLI_TERM ]; then export TERM=${AUTHD_PAM_CLI_TERM}; fi -> ./pam_authd login socket=${AUTHD_TEST_LOGIN_SOCK} force_native_client=true -== Provider selection == - 1. local - 2. ExampleBroker -Choose your provider: -> 2 -== Password authentication == -Enter 'r' to cancel the request and go back to select the authentication method -Gimme your password: -> -== Authentication method selection == - 1. Password authentication - 2. Use a Login code - 3. Send URL to user-integration-native-authenticate-user-with-qr-code-in-polkit@gmail.com - 4. Use your fido device foo - 5. Use your phone +33... - 6. Use your phone +1... - 7. Pin code - 8. Authentication code -Or enter 'r' to go back to choose the provider -Choose your authentication method: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntu.com - 1337 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntu.fr/ - 1338 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntuforum-br.org/ - 1339 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -== Use a Login code == -Enter the code in the login page -https://www.ubuntu-it.org/ - 1340 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -──────────────────────────────────────────────────────────────────────────────── -> if [ -v AUTHD_PAM_CLI_TERM ]; then export TERM=${AUTHD_PAM_CLI_TERM}; fi -> ./pam_authd login socket=${AUTHD_TEST_LOGIN_SOCK} force_native_client=true -== Provider selection == - 1. local - 2. ExampleBroker -Choose your provider: -> 2 -== Password authentication == -Enter 'r' to cancel the request and go back to select the authentication method -Gimme your password: -> -== Authentication method selection == - 1. Password authentication - 2. Use a Login code - 3. Send URL to user-integration-native-authenticate-user-with-qr-code-in-polkit@gmail.com - 4. Use your fido device foo - 5. Use your phone +33... - 6. Use your phone +1... - 7. Pin code - 8. Authentication code -Or enter 'r' to go back to choose the provider -Choose your authentication method: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntu.com - 1337 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntu.fr/ - 1338 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntuforum-br.org/ - 1339 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -== Use a Login code == -Enter the code in the login page -https://www.ubuntu-it.org/ - 1340 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntu.com - 1341 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> -──────────────────────────────────────────────────────────────────────────────── -> if [ -v AUTHD_PAM_CLI_TERM ]; then export TERM=${AUTHD_PAM_CLI_TERM}; fi -> ./pam_authd login socket=${AUTHD_TEST_LOGIN_SOCK} force_native_client=true -== Provider selection == - 1. local - 2. ExampleBroker -Choose your provider: -> 2 -== Password authentication == -Enter 'r' to cancel the request and go back to select the authentication method -Gimme your password: -> -== Authentication method selection == - 1. Password authentication - 2. Use a Login code - 3. Send URL to user-integration-native-authenticate-user-with-qr-code-in-polkit@gmail.com - 4. Use your fido device foo - 5. Use your phone +33... - 6. Use your phone +1... - 7. Pin code - 8. Authentication code -Or enter 'r' to go back to choose the provider -Choose your authentication method: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntu.com - 1337 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntu.fr/ - 1338 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntuforum-br.org/ - 1339 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -== Use a Login code == -Enter the code in the login page -https://www.ubuntu-it.org/ - 1340 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntu.com - 1341 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 1 -──────────────────────────────────────────────────────────────────────────────── -> if [ -v AUTHD_PAM_CLI_TERM ]; then export TERM=${AUTHD_PAM_CLI_TERM}; fi -> ./pam_authd login socket=${AUTHD_TEST_LOGIN_SOCK} force_native_client=true -== Provider selection == - 1. local - 2. ExampleBroker -Choose your provider: -> 2 -== Password authentication == -Enter 'r' to cancel the request and go back to select the authentication method -Gimme your password: -> -== Authentication method selection == - 1. Password authentication - 2. Use a Login code - 3. Send URL to user-integration-native-authenticate-user-with-qr-code-in-polkit@gmail.com - 4. Use your fido device foo - 5. Use your phone +33... - 6. Use your phone +1... - 7. Pin code - 8. Authentication code -Or enter 'r' to go back to choose the provider -Choose your authentication method: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntu.com - 1337 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntu.fr/ - 1338 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntuforum-br.org/ - 1339 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -== Use a Login code == -Enter the code in the login page -https://www.ubuntu-it.org/ - 1340 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 2 -== Use a Login code == -Enter the code in the login page -https://ubuntu.com - 1341 - - 1. Wait for authentication result - 2. Regenerate code -Or enter 'r' to go back to select the authentication method -Choose action: -> 1 -PAM Authenticate() - User: "user-integration-native-authenticate-user-with-qr-code-in-polkit" - Result: success -PAM AcctMgmt() - User: "user-integration-native-authenticate-user-with-qr-code-in-polkit" - Result: success -> -──────────────────────────────────────────────────────────────────────────────── From 9315a39c943ee0bbda9a09692f087e65bcb79e64 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 16 Jun 2025 11:12:50 +0200 Subject: [PATCH 0479/1670] GitHub issue template: Redact values assigned to the client_secret option To avoid users accidentally leaking secrets in public GitHub issues. --- .github/ISSUE_TEMPLATE/bug_report.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 0b981122b6..635bce6561 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -97,7 +97,8 @@ body: _SYSTEMD_UNIT=snap.authd-google.authd-google.service \+ UNIT=snap.authd-google.authd-google.service \+ SYSLOG_IDENTIFIER=authd-google \+ \ '_CMDLINE="gdm-session-worker [pam/gdm-authd]"' | sed -E -e 's/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}//g' \ -e 's/GOCSPX-[0-9a-zA-Z_-]+//g' \ - -e 's/[0-9a-zA-Z_-]+\.apps\.googleusercontent\.com//g') + -e 's/[0-9a-zA-Z_-]+\.apps\.googleusercontent\.com//g' \ + -e 's/(client_secret = )\S+.*/\1/g') \`\`\` #### authd apt history From 0e1a8f2b4804ffc027c8b19987643e6c60115fcc Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 17 Jun 2025 12:46:49 +0200 Subject: [PATCH 0480/1670] Inline error format message No need to extract a global variable here --- internal/services/permissions/permissions.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/services/permissions/permissions.go b/internal/services/permissions/permissions.go index 8d786a7b04..ef38acd63b 100644 --- a/internal/services/permissions/permissions.go +++ b/internal/services/permissions/permissions.go @@ -10,8 +10,6 @@ import ( "google.golang.org/grpc/peer" ) -var permErrorFmt = "this action is only allowed for root users. Current user is %d" - // Manager is an abstraction of permission process. type Manager struct { rootUID uint32 @@ -56,7 +54,7 @@ func (m Manager) IsRequestFromRoot(ctx context.Context) (err error) { } if pci.uid != m.rootUID { - return fmt.Errorf(permErrorFmt, pci.uid) + return fmt.Errorf("this action is only allowed for root users. Current user is %d", pci.uid) } return nil From 924271353e5cdc447c96ed20a31452a27af2d12f Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 17 Jun 2025 12:47:56 +0200 Subject: [PATCH 0481/1670] Rename IsRequestFromRoot -> CheckRequestIsFromRoot We're returning an error, not a boolean. --- internal/services/pam/permissions.go | 2 +- internal/services/permissions/permissions.go | 4 ++-- internal/services/permissions/permissions_test.go | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/services/pam/permissions.go b/internal/services/pam/permissions.go index c06e6d508b..47d4347b7a 100644 --- a/internal/services/pam/permissions.go +++ b/internal/services/pam/permissions.go @@ -4,5 +4,5 @@ import "context" // CheckGlobalAccess denies all requests not coming from the root user. func (s Service) CheckGlobalAccess(ctx context.Context, method string) error { - return s.permissionManager.IsRequestFromRoot(ctx) + return s.permissionManager.CheckRequestIsFromRoot(ctx) } diff --git a/internal/services/permissions/permissions.go b/internal/services/permissions/permissions.go index ef38acd63b..e46abded1c 100644 --- a/internal/services/permissions/permissions.go +++ b/internal/services/permissions/permissions.go @@ -39,9 +39,9 @@ func New(args ...Option) Manager { } } -// IsRequestFromRoot returns nil if the request was performed by a root user. +// CheckRequestIsFromRoot returns nil if the request was performed by a root user. // The pid and uid are extracted from peerCredsInfo in the gRPC context. -func (m Manager) IsRequestFromRoot(ctx context.Context) (err error) { +func (m Manager) CheckRequestIsFromRoot(ctx context.Context) (err error) { defer decorate.OnError(&err, "permission denied") p, ok := peer.FromContext(ctx) diff --git a/internal/services/permissions/permissions_test.go b/internal/services/permissions/permissions_test.go index 64dc933167..ec9b20d468 100644 --- a/internal/services/permissions/permissions_test.go +++ b/internal/services/permissions/permissions_test.go @@ -66,13 +66,13 @@ func TestIsRequestFromRoot(t *testing.T) { } pm := permissions.New(opts...) - err := pm.IsRequestFromRoot(ctx) + err := pm.CheckRequestIsFromRoot(ctx) if tc.wantErr { - require.Error(t, err, "IsRequestFromRoot should deny access but didn't") + require.Error(t, err, "CheckRequestIsFromRoot should deny access but didn't") return } - require.NoError(t, err, "IsRequestFromRoot should allow access but didn't") + require.NoError(t, err, "CheckRequestIsFromRoot should allow access but didn't") }) } } From e0fa9dd8b429390484ad65431cc4d324febf8b04 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 17 Jun 2025 12:49:48 +0200 Subject: [PATCH 0482/1670] Improve comment --- internal/services/permissions/permissions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/services/permissions/permissions.go b/internal/services/permissions/permissions.go index e46abded1c..a25c737a3e 100644 --- a/internal/services/permissions/permissions.go +++ b/internal/services/permissions/permissions.go @@ -39,7 +39,7 @@ func New(args ...Option) Manager { } } -// CheckRequestIsFromRoot returns nil if the request was performed by a root user. +// CheckRequestIsFromRoot checks if the current gRPC request is from a root user and returns an error if not. // The pid and uid are extracted from peerCredsInfo in the gRPC context. func (m Manager) CheckRequestIsFromRoot(ctx context.Context) (err error) { defer decorate.OnError(&err, "permission denied") From 361e5e3b8eb42d3035983f95f41975b76b8c8939 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 17 Jun 2025 12:50:56 +0200 Subject: [PATCH 0483/1670] Omit current UID from error message We return this error message to the gRPC client. We don't have to tell the client it's UID, and in authctl, we print this error message, which looks bad. If anything, we might want to make the server log the client's UID. However, I don't see the need for that right know. --- .../TestIsAuthenticated/Error_when_not_root/IsAuthenticated | 2 +- internal/services/permissions/permissions.go | 3 +-- ...y_authentication_if_current_user_is_not_considered_as_root | 4 ++-- ...ssword_if_current_user_is_not_root_as_can_not_authenticate | 4 ++-- ...y_authentication_if_current_user_is_not_considered_as_root | 4 ++-- ...ssword_if_current_user_is_not_root_as_can_not_authenticate | 4 ++-- 6 files changed, 10 insertions(+), 11 deletions(-) diff --git a/internal/services/pam/testdata/golden/TestIsAuthenticated/Error_when_not_root/IsAuthenticated b/internal/services/pam/testdata/golden/TestIsAuthenticated/Error_when_not_root/IsAuthenticated index 960d5b8c2c..a36c0d5b44 100644 --- a/internal/services/pam/testdata/golden/TestIsAuthenticated/Error_when_not_root/IsAuthenticated +++ b/internal/services/pam/testdata/golden/TestIsAuthenticated/Error_when_not_root/IsAuthenticated @@ -1,4 +1,4 @@ FIRST CALL: access: msg: - err: permission denied: this action is only allowed for root users. Current user is XXXX + err: permission denied: this action is only allowed for root users diff --git a/internal/services/permissions/permissions.go b/internal/services/permissions/permissions.go index a25c737a3e..2b4ca3fffa 100644 --- a/internal/services/permissions/permissions.go +++ b/internal/services/permissions/permissions.go @@ -4,7 +4,6 @@ package permissions import ( "context" "errors" - "fmt" "github.com/ubuntu/decorate" "google.golang.org/grpc/peer" @@ -54,7 +53,7 @@ func (m Manager) CheckRequestIsFromRoot(ctx context.Context) (err error) { } if pci.uid != m.rootUID { - return fmt.Errorf("this action is only allowed for root users. Current user is %d", pci.uid) + return errors.New("this action is only allowed for root users") } return nil diff --git a/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Deny_authentication_if_current_user_is_not_considered_as_root b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Deny_authentication_if_current_user_is_not_considered_as_root index 4158028750..ed3fd8a586 100644 --- a/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Deny_authentication_if_current_user_is_not_considered_as_root +++ b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Deny_authentication_if_current_user_is_not_considered_as_root @@ -1,6 +1,6 @@ > ./pam_authd login socket=${AUTHD_TEST_TAPE_SOCKET} PAM Error Message: could not get current available brokers: permission denied: this action is on -ly allowed for root users. Current user is XXXX +ly allowed for root users PAM Authenticate() User: "" Result: error: PAM exit code: 4 @@ -14,7 +14,7 @@ PAM AcctMgmt() ──────────────────────────────────────────────────────────────────────────────── > ./pam_authd login socket=${AUTHD_TEST_TAPE_SOCKET} PAM Error Message: could not get current available brokers: permission denied: this action is on -ly allowed for root users. Current user is XXXX +ly allowed for root users PAM Authenticate() User: "" Result: error: PAM exit code: 4 diff --git a/pam/integration-tests/testdata/golden/TestCLIChangeAuthTok/Prevent_change_password_if_current_user_is_not_root_as_can_not_authenticate b/pam/integration-tests/testdata/golden/TestCLIChangeAuthTok/Prevent_change_password_if_current_user_is_not_root_as_can_not_authenticate index d512faa995..010af6479b 100644 --- a/pam/integration-tests/testdata/golden/TestCLIChangeAuthTok/Prevent_change_password_if_current_user_is_not_root_as_can_not_authenticate +++ b/pam/integration-tests/testdata/golden/TestCLIChangeAuthTok/Prevent_change_password_if_current_user_is_not_root_as_can_not_authenticate @@ -1,6 +1,6 @@ > ./pam_authd passwd socket=${AUTHD_TEST_TAPE_SOCKET} PAM Error Message: could not get current available brokers: permission denied: this action is on -ly allowed for root users. Current user is XXXX +ly allowed for root users PAM ChangeAuthTok() User: "" Result: error: PAM exit code: 4 @@ -14,7 +14,7 @@ PAM AcctMgmt() ──────────────────────────────────────────────────────────────────────────────── > ./pam_authd passwd socket=${AUTHD_TEST_TAPE_SOCKET} PAM Error Message: could not get current available brokers: permission denied: this action is on -ly allowed for root users. Current user is XXXX +ly allowed for root users PAM ChangeAuthTok() User: "" Result: error: PAM exit code: 4 diff --git a/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Deny_authentication_if_current_user_is_not_considered_as_root b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Deny_authentication_if_current_user_is_not_considered_as_root index 6592255ba3..1611789cf6 100644 --- a/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Deny_authentication_if_current_user_is_not_considered_as_root +++ b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Deny_authentication_if_current_user_is_not_considered_as_root @@ -1,6 +1,6 @@ > ./pam_authd login socket=${AUTHD_TEST_TAPE_SOCKET} force_native_client=true PAM Error Message: could not get current available brokers: permission denied: this action is on -ly allowed for root users. Current user is XXXX +ly allowed for root users PAM Authenticate() User: "user-integration-native-deny-authentication-if-current-user-is-not-considered-as-root" Result: error: PAM exit code: 4 @@ -14,7 +14,7 @@ PAM AcctMgmt() ──────────────────────────────────────────────────────────────────────────────── > ./pam_authd login socket=${AUTHD_TEST_TAPE_SOCKET} force_native_client=true PAM Error Message: could not get current available brokers: permission denied: this action is on -ly allowed for root users. Current user is XXXX +ly allowed for root users PAM Authenticate() User: "user-integration-native-deny-authentication-if-current-user-is-not-considered-as-root" Result: error: PAM exit code: 4 diff --git a/pam/integration-tests/testdata/golden/TestNativeChangeAuthTok/Prevent_change_password_if_current_user_is_not_root_as_can_not_authenticate b/pam/integration-tests/testdata/golden/TestNativeChangeAuthTok/Prevent_change_password_if_current_user_is_not_root_as_can_not_authenticate index b28545ad34..9b0b991cea 100644 --- a/pam/integration-tests/testdata/golden/TestNativeChangeAuthTok/Prevent_change_password_if_current_user_is_not_root_as_can_not_authenticate +++ b/pam/integration-tests/testdata/golden/TestNativeChangeAuthTok/Prevent_change_password_if_current_user_is_not_root_as_can_not_authenticate @@ -1,6 +1,6 @@ > ./pam_authd passwd socket=${AUTHD_TEST_TAPE_SOCKET} force_native_client=true PAM Error Message: could not get current available brokers: permission denied: this action is on -ly allowed for root users. Current user is XXXX +ly allowed for root users PAM ChangeAuthTok() User: "" Result: error: PAM exit code: 4 @@ -14,7 +14,7 @@ PAM AcctMgmt() ──────────────────────────────────────────────────────────────────────────────── > ./pam_authd passwd socket=${AUTHD_TEST_TAPE_SOCKET} force_native_client=true PAM Error Message: could not get current available brokers: permission denied: this action is on -ly allowed for root users. Current user is XXXX +ly allowed for root users PAM ChangeAuthTok() User: "" Result: error: PAM exit code: 4 From 5fe73202b4c1e64190e8e9448ccf5ab0438dd186 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 17 Jun 2025 13:33:50 +0200 Subject: [PATCH 0484/1670] Improve error message --- .../Error_when_not_root/IsAuthenticated | 2 +- internal/services/permissions/permissions.go | 2 +- ...thentication_if_current_user_is_not_considered_as_root | 8 ++++---- ...rd_if_current_user_is_not_root_as_can_not_authenticate | 8 ++++---- ...thentication_if_current_user_is_not_considered_as_root | 8 ++++---- ...rd_if_current_user_is_not_root_as_can_not_authenticate | 8 ++++---- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/internal/services/pam/testdata/golden/TestIsAuthenticated/Error_when_not_root/IsAuthenticated b/internal/services/pam/testdata/golden/TestIsAuthenticated/Error_when_not_root/IsAuthenticated index a36c0d5b44..6faba367bb 100644 --- a/internal/services/pam/testdata/golden/TestIsAuthenticated/Error_when_not_root/IsAuthenticated +++ b/internal/services/pam/testdata/golden/TestIsAuthenticated/Error_when_not_root/IsAuthenticated @@ -1,4 +1,4 @@ FIRST CALL: access: msg: - err: permission denied: this action is only allowed for root users + err: permission denied: only root can perform this operation diff --git a/internal/services/permissions/permissions.go b/internal/services/permissions/permissions.go index 2b4ca3fffa..7fa64603c0 100644 --- a/internal/services/permissions/permissions.go +++ b/internal/services/permissions/permissions.go @@ -53,7 +53,7 @@ func (m Manager) CheckRequestIsFromRoot(ctx context.Context) (err error) { } if pci.uid != m.rootUID { - return errors.New("this action is only allowed for root users") + return errors.New("only root can perform this operation") } return nil diff --git a/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Deny_authentication_if_current_user_is_not_considered_as_root b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Deny_authentication_if_current_user_is_not_considered_as_root index ed3fd8a586..4398e79dc9 100644 --- a/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Deny_authentication_if_current_user_is_not_considered_as_root +++ b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Deny_authentication_if_current_user_is_not_considered_as_root @@ -1,6 +1,6 @@ > ./pam_authd login socket=${AUTHD_TEST_TAPE_SOCKET} -PAM Error Message: could not get current available brokers: permission denied: this action is on -ly allowed for root users +PAM Error Message: could not get current available brokers: permission denied: only root can per +form this operation PAM Authenticate() User: "" Result: error: PAM exit code: 4 @@ -13,8 +13,8 @@ PAM AcctMgmt() > ──────────────────────────────────────────────────────────────────────────────── > ./pam_authd login socket=${AUTHD_TEST_TAPE_SOCKET} -PAM Error Message: could not get current available brokers: permission denied: this action is on -ly allowed for root users +PAM Error Message: could not get current available brokers: permission denied: only root can per +form this operation PAM Authenticate() User: "" Result: error: PAM exit code: 4 diff --git a/pam/integration-tests/testdata/golden/TestCLIChangeAuthTok/Prevent_change_password_if_current_user_is_not_root_as_can_not_authenticate b/pam/integration-tests/testdata/golden/TestCLIChangeAuthTok/Prevent_change_password_if_current_user_is_not_root_as_can_not_authenticate index 010af6479b..d3ee332f57 100644 --- a/pam/integration-tests/testdata/golden/TestCLIChangeAuthTok/Prevent_change_password_if_current_user_is_not_root_as_can_not_authenticate +++ b/pam/integration-tests/testdata/golden/TestCLIChangeAuthTok/Prevent_change_password_if_current_user_is_not_root_as_can_not_authenticate @@ -1,6 +1,6 @@ > ./pam_authd passwd socket=${AUTHD_TEST_TAPE_SOCKET} -PAM Error Message: could not get current available brokers: permission denied: this action is on -ly allowed for root users +PAM Error Message: could not get current available brokers: permission denied: only root can per +form this operation PAM ChangeAuthTok() User: "" Result: error: PAM exit code: 4 @@ -13,8 +13,8 @@ PAM AcctMgmt() > ──────────────────────────────────────────────────────────────────────────────── > ./pam_authd passwd socket=${AUTHD_TEST_TAPE_SOCKET} -PAM Error Message: could not get current available brokers: permission denied: this action is on -ly allowed for root users +PAM Error Message: could not get current available brokers: permission denied: only root can per +form this operation PAM ChangeAuthTok() User: "" Result: error: PAM exit code: 4 diff --git a/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Deny_authentication_if_current_user_is_not_considered_as_root b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Deny_authentication_if_current_user_is_not_considered_as_root index 1611789cf6..8071a827f9 100644 --- a/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Deny_authentication_if_current_user_is_not_considered_as_root +++ b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Deny_authentication_if_current_user_is_not_considered_as_root @@ -1,6 +1,6 @@ > ./pam_authd login socket=${AUTHD_TEST_TAPE_SOCKET} force_native_client=true -PAM Error Message: could not get current available brokers: permission denied: this action is on -ly allowed for root users +PAM Error Message: could not get current available brokers: permission denied: only root can per +form this operation PAM Authenticate() User: "user-integration-native-deny-authentication-if-current-user-is-not-considered-as-root" Result: error: PAM exit code: 4 @@ -13,8 +13,8 @@ PAM AcctMgmt() > ──────────────────────────────────────────────────────────────────────────────── > ./pam_authd login socket=${AUTHD_TEST_TAPE_SOCKET} force_native_client=true -PAM Error Message: could not get current available brokers: permission denied: this action is on -ly allowed for root users +PAM Error Message: could not get current available brokers: permission denied: only root can per +form this operation PAM Authenticate() User: "user-integration-native-deny-authentication-if-current-user-is-not-considered-as-root" Result: error: PAM exit code: 4 diff --git a/pam/integration-tests/testdata/golden/TestNativeChangeAuthTok/Prevent_change_password_if_current_user_is_not_root_as_can_not_authenticate b/pam/integration-tests/testdata/golden/TestNativeChangeAuthTok/Prevent_change_password_if_current_user_is_not_root_as_can_not_authenticate index 9b0b991cea..f62db56c5b 100644 --- a/pam/integration-tests/testdata/golden/TestNativeChangeAuthTok/Prevent_change_password_if_current_user_is_not_root_as_can_not_authenticate +++ b/pam/integration-tests/testdata/golden/TestNativeChangeAuthTok/Prevent_change_password_if_current_user_is_not_root_as_can_not_authenticate @@ -1,6 +1,6 @@ > ./pam_authd passwd socket=${AUTHD_TEST_TAPE_SOCKET} force_native_client=true -PAM Error Message: could not get current available brokers: permission denied: this action is on -ly allowed for root users +PAM Error Message: could not get current available brokers: permission denied: only root can per +form this operation PAM ChangeAuthTok() User: "" Result: error: PAM exit code: 4 @@ -13,8 +13,8 @@ PAM AcctMgmt() > ──────────────────────────────────────────────────────────────────────────────── > ./pam_authd passwd socket=${AUTHD_TEST_TAPE_SOCKET} force_native_client=true -PAM Error Message: could not get current available brokers: permission denied: this action is on -ly allowed for root users +PAM Error Message: could not get current available brokers: permission denied: only root can per +form this operation PAM ChangeAuthTok() User: "" Result: error: PAM exit code: 4 From 49057b51313d416511aa5713e04de8a35152243d Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 9 May 2025 13:00:16 +0200 Subject: [PATCH 0485/1670] Add options extra_groups and owner_extra_groups Multiple users asked for a feature to automatically add users to a list of local groups via local configuration (instead of configuration in the identity provider, which is currently only supported for Microsoft Entra ID). This commit adds two new options, extra_groups and owner_extra_groups. extra_groups can be set to a comma-separated list of local groups which each authd user will be added to upon login. owner_extra_groups is the same, except that only the owner is added to these groups upon login. Closes https://github.com/ubuntu/authd/issues/910 --- conf/broker.conf | 12 ++++++- internal/broker/broker.go | 31 +++++++++++++++--- internal/broker/broker_test.go | 32 ++++++++++++++++++- internal/broker/config.go | 13 +++++--- internal/broker/export_test.go | 8 +++++ internal/broker/helper_test.go | 8 +++++ .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json | 1 + .../Extra_groups_configured/first_call | 3 ++ .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json | 1 + .../Owner_extra_groups_configured/first_call | 3 ++ .../provider_url/test-user@email.com/password | 1 + .../test-user@email.com/token.json | 1 + .../first_call | 3 ++ .../config.txt | 4 ++- .../Successfully_parse_config_file/config.txt | 4 ++- .../config.txt | 4 ++- .../config.txt | 4 ++- 19 files changed, 120 insertions(+), 15 deletions(-) create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Extra_groups_configured/data/provider_url/test-user@email.com/password create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Extra_groups_configured/data/provider_url/test-user@email.com/token.json create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Extra_groups_configured/first_call create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured/data/provider_url/test-user@email.com/password create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured/data/provider_url/test-user@email.com/token.json create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured/first_call create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured_but_user_does_not_become_owner/data/provider_url/test-user@email.com/password create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured_but_user_does_not_become_owner/data/provider_url/test-user@email.com/token.json create mode 100644 internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured_but_user_does_not_become_owner/first_call diff --git a/conf/broker.conf b/conf/broker.conf index a94571cf81..747d61f498 100644 --- a/conf/broker.conf +++ b/conf/broker.conf @@ -1,5 +1,5 @@ [oidc] -issuer = https:// +issuer = client_id = ## Depending on the identity provider, you may need to provide a @@ -56,3 +56,13 @@ client_id = ## ## Example: owner = user2@example.com #owner = + +## A comma-separated list of local groups which authd users will be +## added to upon login. +## Example: extra_groups = users +#extra_groups = + +## Like 'extra_groups', but only the user assigned the owner role +## (see 'owner' option) will be added to these groups. +## Example: owner_extra_groups = sudo,lpadmin +#owner_extra_groups = diff --git a/internal/broker/broker.go b/internal/broker/broker.go index acb30134dc..3f9ae9cdc0 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -679,11 +679,13 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au } } - if err := b.cfg.registerOwner(b.cfg.ConfigFile, authInfo.UserInfo.Name); err != nil { - // The user is not allowed if we fail to create the owner-autoregistration file. - // Otherwise the owner might change if the broker is restarted. - log.Errorf(context.Background(), "Failed to assign the owner role: %v", err) - return AuthDenied, errorMessage{Message: "could not register the owner"} + if b.cfg.shouldRegisterOwner() { + if err := b.cfg.registerOwner(b.cfg.ConfigFile, authInfo.UserInfo.Name); err != nil { + // The user is not allowed if we fail to create the owner-autoregistration file. + // Otherwise the owner might change if the broker is restarted. + log.Errorf(context.Background(), "Failed to assign the owner role: %v", err) + return AuthDenied, errorMessage{Message: "could not register the owner"} + } } if !b.userNameIsAllowed(authInfo.UserInfo.Name) { @@ -691,6 +693,20 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au return AuthDenied, errorMessage{Message: "user not allowed in broker configuration"} } + // Add extra groups to the user info. + for _, name := range b.cfg.extraGroups { + log.Debugf(context.Background(), "Adding extra group %q", name) + authInfo.UserInfo.Groups = append(authInfo.UserInfo.Groups, info.Group{Name: name}) + } + + if b.isOwner(authInfo.UserInfo.Name) { + // Add the owner extra groups to the user info. + for _, name := range b.cfg.ownerExtraGroups { + log.Debugf(context.Background(), "Adding owner extra group %q", name) + authInfo.UserInfo.Groups = append(authInfo.UserInfo.Groups, info.Group{Name: name}) + } + } + if session.isOffline { return AuthGranted, userInfoMessage{UserInfo: authInfo.UserInfo} } @@ -712,6 +728,11 @@ func (b *Broker) userNameIsAllowed(userName string) bool { return b.cfg.userNameIsAllowed(b.provider.NormalizeUsername(userName)) } +// isOwner returns true if the user is the owner of the machine. +func (b *Broker) isOwner(userName string) bool { + return b.cfg.owner == b.provider.NormalizeUsername(userName) +} + func (b *Broker) userNotAllowedLogMsg(userName string) string { logMsg := fmt.Sprintf("User %q is not in the list of allowed users.", userName) logMsg += fmt.Sprintf("\nYou can add the user to allowed_users in %s", b.cfg.ConfigFile) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index eebfa99eb4..525edef85b 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -391,6 +391,10 @@ func TestIsAuthenticated(t *testing.T) { sessionOffline bool username string forceProviderAuthentication bool + userDoesNotBecomeOwner bool + allUsersAllowed bool + extraGroups []string + ownerExtraGroups []string firstMode string firstSecret string @@ -482,6 +486,29 @@ func TestIsAuthenticated(t *testing.T) { token: &tokenOptions{}, forceProviderAuthentication: true, }, + "Extra_groups_configured": { + firstMode: authmodes.Password, + token: &tokenOptions{}, + groupsReturnedByProvider: []info.Group{{Name: "remote-group"}}, + extraGroups: []string{"extra-group"}, + wantGroups: []info.Group{{Name: "remote-group"}, {Name: "extra-group"}}, + }, + "Owner_extra_groups_configured": { + firstMode: authmodes.Password, + token: &tokenOptions{}, + groupsReturnedByProvider: []info.Group{{Name: "remote-group"}}, + ownerExtraGroups: []string{"owner-group"}, + wantGroups: []info.Group{{Name: "remote-group"}, {Name: "owner-group"}}, + }, + "Owner_extra_groups_configured_but_user_does_not_become_owner": { + firstMode: authmodes.Password, + token: &tokenOptions{}, + groupsReturnedByProvider: []info.Group{{Name: "remote-group"}}, + userDoesNotBecomeOwner: true, + allUsersAllowed: true, + ownerExtraGroups: []string{"owner-group"}, + wantGroups: []info.Group{{Name: "remote-group"}}, + }, "Error_when_authentication_data_is_invalid": {invalidAuthData: true}, "Error_when_secret_can_not_be_decrypted": {firstMode: authmodes.Password, badFirstKey: true}, @@ -582,8 +609,11 @@ func TestIsAuthenticated(t *testing.T) { Config: broker.Config{DataDir: dataDir}, getUserInfoFails: tc.getUserInfoFails, ownerAllowed: true, - firstUserBecomesOwner: true, + firstUserBecomesOwner: !tc.userDoesNotBecomeOwner, + allUsersAllowed: tc.allUsersAllowed, forceProviderAuthentication: tc.forceProviderAuthentication, + extraGroups: tc.extraGroups, + ownerExtraGroups: tc.ownerExtraGroups, } if tc.customHandlers == nil { // Use the default provider URL if no custom handlers are provided. diff --git a/internal/broker/config.go b/internal/broker/config.go index 5462436f85..5f93d21dc2 100644 --- a/internal/broker/config.go +++ b/internal/broker/config.go @@ -38,6 +38,10 @@ const ( homeDirKey = "home_base_dir" // SSHSuffixKey is the key in the config file for the SSH allowed suffixes. sshSuffixesKey = "ssh_allowed_suffixes" + // extraGroupsKey is the key in the config file for the extra groups to add to each authd user. + extraGroupsKey = "extra_groups" + // ownerExtraGroupsKey is the key in the config file for the extra groups to add to the owner. + ownerExtraGroupsKey = "owner_extra_groups" // allUsersKeyword is the keyword for the `allowed_users` key that allows access to all users. allUsersKeyword = "ALL" @@ -77,6 +81,8 @@ type userConfig struct { ownerMutex *sync.RWMutex homeBaseDir string allowedSSHSuffixes []string + extraGroups []string + ownerExtraGroups []string provider provider } @@ -152,6 +158,9 @@ func (uc *userConfig) populateUsersConfig(users *ini.Section) { // We need to read the owner key after we call HasKey, because the key is created // when we call the "Key" function and we can't distinguish between empty and unset. uc.owner = uc.provider.NormalizeUsername(users.Key(ownerKey).String()) + + uc.extraGroups = users.Key(extraGroupsKey).Strings(",") + uc.ownerExtraGroups = users.Key(ownerExtraGroupsKey).Strings(",") } // parseConfigFile parses the config file and returns a map with the configuration keys and values. @@ -232,10 +241,6 @@ func (uc *userConfig) registerOwner(cfgPath, userName string) error { uc.ownerMutex.Lock() defer uc.ownerMutex.Unlock() - if !uc.shouldRegisterOwner() { - return nil - } - if cfgPath == "" { uc.owner = uc.provider.NormalizeUsername(userName) uc.firstUserBecomesOwner = false diff --git a/internal/broker/export_test.go b/internal/broker/export_test.go index 7e0ce59396..eb06b72baf 100644 --- a/internal/broker/export_test.go +++ b/internal/broker/export_test.go @@ -57,6 +57,14 @@ func (cfg *Config) SetOwnerAllowed(ownerAllowed bool) { cfg.ownerAllowed = ownerAllowed } +func (cfg *Config) SetExtraGroups(extraGroups []string) { + cfg.extraGroups = extraGroups +} + +func (cfg *Config) SetOwnerExtraGroups(ownerExtraGroups []string) { + cfg.ownerExtraGroups = ownerExtraGroups +} + func (cfg *Config) SetAllowedSSHSuffixes(allowedSSHSuffixes []string) { cfg.allowedSSHSuffixes = allowedSSHSuffixes } diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index 99b9316222..770c6adc50 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -31,6 +31,8 @@ type brokerForTestConfig struct { ownerAllowed bool firstUserBecomesOwner bool owner string + extraGroups []string + ownerExtraGroups []string homeBaseDir string allowedSSHSuffixes []string provider providers.Provider @@ -77,6 +79,12 @@ func newBrokerForTests(t *testing.T, cfg *brokerForTestConfig) (b *broker.Broker if cfg.ownerAllowed != false { cfg.SetOwnerAllowed(cfg.ownerAllowed) } + if cfg.extraGroups != nil { + cfg.SetExtraGroups(cfg.extraGroups) + } + if cfg.ownerExtraGroups != nil { + cfg.SetOwnerExtraGroups(cfg.ownerExtraGroups) + } provider := &testutils.MockProvider{ GetUserInfoFails: cfg.getUserInfoFails, diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Extra_groups_configured/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Extra_groups_configured/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Extra_groups_configured/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Extra_groups_configured/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Extra_groups_configured/data/provider_url/test-user@email.com/token.json new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Extra_groups_configured/data/provider_url/test-user@email.com/token.json @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Extra_groups_configured/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Extra_groups_configured/first_call new file mode 100644 index 0000000000..c767dfccbf --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Extra_groups_configured/first_call @@ -0,0 +1,3 @@ +access: granted +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-group","ugid":""},{"name":"extra-group","ugid":""}]}}' +err: diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured/data/provider_url/test-user@email.com/token.json new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured/data/provider_url/test-user@email.com/token.json @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured/first_call new file mode 100644 index 0000000000..26add22993 --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured/first_call @@ -0,0 +1,3 @@ +access: granted +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-group","ugid":""},{"name":"owner-group","ugid":""}]}}' +err: diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured_but_user_does_not_become_owner/data/provider_url/test-user@email.com/password b/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured_but_user_does_not_become_owner/data/provider_url/test-user@email.com/password new file mode 100644 index 0000000000..119947240f --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured_but_user_does_not_become_owner/data/provider_url/test-user@email.com/password @@ -0,0 +1 @@ +Definitely a hashed password \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured_but_user_does_not_become_owner/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured_but_user_does_not_become_owner/data/provider_url/test-user@email.com/token.json new file mode 100644 index 0000000000..80ab783820 --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured_but_user_does_not_become_owner/data/provider_url/test-user@email.com/token.json @@ -0,0 +1 @@ +Definitely an encrypted token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured_but_user_does_not_become_owner/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured_but_user_does_not_become_owner/first_call new file mode 100644 index 0000000000..e97c996716 --- /dev/null +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured_but_user_does_not_become_owner/first_call @@ -0,0 +1,3 @@ +access: granted +data: '{"userinfo":{"name":"test-user@email.com","uuid":"test-user-id","dir":"/home/test-user@email.com","shell":"/usr/bin/bash","gecos":"test-user@email.com","groups":[{"name":"remote-group","ugid":""}]}}' +err: diff --git a/internal/broker/testdata/golden/TestParseConfig/Do_not_fail_if_values_contain_a_single_template_delimiter/config.txt b/internal/broker/testdata/golden/TestParseConfig/Do_not_fail_if_values_contain_a_single_template_delimiter/config.txt index 24d5771bc3..1305b4d7ea 100644 --- a/internal/broker/testdata/golden/TestParseConfig/Do_not_fail_if_values_contain_a_single_template_delimiter/config.txt +++ b/internal/broker/testdata/golden/TestParseConfig/Do_not_fail_if_values_contain_a_single_template_delimiter/config.txt @@ -8,4 +8,6 @@ ownerAllowed=true firstUserBecomesOwner=true owner= homeBaseDir= -allowedSSHSuffixes=[] \ No newline at end of file +allowedSSHSuffixes=[] +extraGroups=[] +ownerExtraGroups=[] \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file/config.txt b/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file/config.txt index bfb3f20a41..ac0f7425ab 100644 --- a/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file/config.txt +++ b/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file/config.txt @@ -8,4 +8,6 @@ ownerAllowed=true firstUserBecomesOwner=true owner= homeBaseDir= -allowedSSHSuffixes=[] \ No newline at end of file +allowedSSHSuffixes=[] +extraGroups=[] +ownerExtraGroups=[] \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file_with_optional_values/config.txt b/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file_with_optional_values/config.txt index f318318119..af408fdc91 100644 --- a/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file_with_optional_values/config.txt +++ b/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_file_with_optional_values/config.txt @@ -8,4 +8,6 @@ ownerAllowed=true firstUserBecomesOwner=true owner= homeBaseDir=/home -allowedSSHSuffixes=[] \ No newline at end of file +allowedSSHSuffixes=[] +extraGroups=[] +ownerExtraGroups=[] \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_with_drop_in_files/config.txt b/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_with_drop_in_files/config.txt index 19009a056d..7ea331cb9b 100644 --- a/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_with_drop_in_files/config.txt +++ b/internal/broker/testdata/golden/TestParseConfig/Successfully_parse_config_with_drop_in_files/config.txt @@ -8,4 +8,6 @@ ownerAllowed=true firstUserBecomesOwner=true owner= homeBaseDir=/home -allowedSSHSuffixes=[] \ No newline at end of file +allowedSSHSuffixes=[] +extraGroups=[] +ownerExtraGroups=[] \ No newline at end of file From 2b75afb708afbd0feb39e61ec7e461bff2e2d88d Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 9 May 2025 14:01:33 +0200 Subject: [PATCH 0486/1670] broker.conf: Fix indentation --- conf/broker.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/conf/broker.conf b/conf/broker.conf index 747d61f498..471639b2f9 100644 --- a/conf/broker.conf +++ b/conf/broker.conf @@ -33,11 +33,11 @@ client_id = ## successfully authenticating with the Identity Provider. ## Values are separated by commas. Supported values: ## - 'OWNER': Grants access to the user specified in the 'owner' option -## (see below). This is the default. +## (see below). This is the default. ## - 'ALL': Grants access to all users who successfully authenticate -## with the Identity Provider. +## with the Identity Provider. ## - : Grants access to specific additional users -## (e.g. user1@example.com). +## (e.g. user1@example.com). ## Example: allowed_users = OWNER,user1@example.com,admin@example.com #allowed_users = OWNER From 3e28735a206f8f1ed5dd4af709f98bbdc570fcc8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Jun 2025 16:41:05 +0000 Subject: [PATCH 0487/1670] deps(go): bump github.com/go-viper/mapstructure/v2 from 2.2.1 to 2.3.0 Bumps [github.com/go-viper/mapstructure/v2](https://github.com/go-viper/mapstructure) from 2.2.1 to 2.3.0. - [Release notes](https://github.com/go-viper/mapstructure/releases) - [Changelog](https://github.com/go-viper/mapstructure/blob/main/CHANGELOG.md) - [Commits](https://github.com/go-viper/mapstructure/compare/v2.2.1...v2.3.0) --- updated-dependencies: - dependency-name: github.com/go-viper/mapstructure/v2 dependency-version: 2.3.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a9901ab597..1f517e48c6 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect - github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/go-viper/mapstructure/v2 v2.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect diff --git a/go.sum b/go.sum index 4b6e3e6042..a4e5d11da2 100644 --- a/go.sum +++ b/go.sum @@ -35,8 +35,8 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= +github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= From 03774c573ba3a53c1413ff7a3fadb2147947f2ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Jun 2025 16:49:55 +0000 Subject: [PATCH 0488/1670] deps(go-tools): bump github.com/go-viper/mapstructure/v2 in /tools Bumps [github.com/go-viper/mapstructure/v2](https://github.com/go-viper/mapstructure) from 2.2.1 to 2.3.0. - [Release notes](https://github.com/go-viper/mapstructure/releases) - [Changelog](https://github.com/go-viper/mapstructure/blob/main/CHANGELOG.md) - [Commits](https://github.com/go-viper/mapstructure/compare/v2.2.1...v2.3.0) --- updated-dependencies: - dependency-name: github.com/go-viper/mapstructure/v2 dependency-version: 2.3.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tools/go.mod | 2 +- tools/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index eaf0dcd944..54acb806eb 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -65,7 +65,7 @@ require ( github.com/go-toolsmith/astp v1.1.0 // indirect github.com/go-toolsmith/strparse v1.1.0 // indirect github.com/go-toolsmith/typep v1.1.0 // indirect - github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/go-viper/mapstructure/v2 v2.3.0 // indirect github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gofrs/flock v0.12.1 // indirect diff --git a/tools/go.sum b/tools/go.sum index 7375f9e9b7..b829c47b43 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -193,8 +193,8 @@ github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQi github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= -github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= +github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY= github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= From 39428ee2c56b83b61e67b5684fb37c8a028bc9dc Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 23 Jun 2025 13:16:47 +0200 Subject: [PATCH 0489/1670] Include error description in gRPC not-found error authctl shows these error messages, so they should contain a helpful description of the error, not just the "not found" error code. --- internal/services/user/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/services/user/user.go b/internal/services/user/user.go index 0544028559..b421b6cd7f 100644 --- a/internal/services/user/user.go +++ b/internal/services/user/user.go @@ -204,7 +204,7 @@ func (s Service) userPreCheck(ctx context.Context, username string) (types.UserE // The NSS module uses this status code to determine the NSS status it should return. func grpcError(err error) error { if errors.Is(err, users.NoDataFoundError{}) { - return status.Error(codes.NotFound, "") + return status.Error(codes.NotFound, err.Error()) } return err From 0bb3cdcfc63239ec913c42e3acbe1eb27db48f92 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Sun, 29 Jun 2025 13:36:41 +0200 Subject: [PATCH 0490/1670] Fix UserPreCheck D-Bus interface The D-Bus interface definition of the UserPreCheck method was missing the out parameter for the user info it returns. --- internal/dbusservice/dbusservice.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/dbusservice/dbusservice.go b/internal/dbusservice/dbusservice.go index 2159cbd595..f1ffe7aef8 100644 --- a/internal/dbusservice/dbusservice.go +++ b/internal/dbusservice/dbusservice.go @@ -45,6 +45,7 @@ const intro = ` + ` + introspect.IntrospectDataString + ` ` From ea8024a7f51ae05d361920d3450bbcca6b78bd03 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 30 Jun 2025 11:41:05 +0200 Subject: [PATCH 0491/1670] Use consistent spelling in broker.conf Consistently use the spelling "identity provider" instead of "Identity Provider" in the broker.conf file. --- conf/broker.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conf/broker.conf b/conf/broker.conf index 471639b2f9..e2e952bea3 100644 --- a/conf/broker.conf +++ b/conf/broker.conf @@ -30,12 +30,12 @@ client_id = #ssh_allowed_suffixes = @example.com,@anotherexample.com ## 'allowed_users' specifies the users who are permitted to log in after -## successfully authenticating with the Identity Provider. +## successfully authenticating with the identity provider. ## Values are separated by commas. Supported values: ## - 'OWNER': Grants access to the user specified in the 'owner' option ## (see below). This is the default. ## - 'ALL': Grants access to all users who successfully authenticate -## with the Identity Provider. +## with the identity provider. ## - : Grants access to specific additional users ## (e.g. user1@example.com). ## Example: allowed_users = OWNER,user1@example.com,admin@example.com From a10232c5b5febaa09720f7ed3d76bfe620f39554 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 11:00:06 +0000 Subject: [PATCH 0492/1670] deps(go): bump github.com/go-jose/go-jose/v4 from 4.1.0 to 4.1.1 Bumps [github.com/go-jose/go-jose/v4](https://github.com/go-jose/go-jose) from 4.1.0 to 4.1.1. - [Release notes](https://github.com/go-jose/go-jose/releases) - [Changelog](https://github.com/go-jose/go-jose/blob/main/CHANGELOG.md) - [Commits](https://github.com/go-jose/go-jose/compare/v4.1.0...v4.1.1) --- updated-dependencies: - dependency-name: github.com/go-jose/go-jose/v4 dependency-version: 4.1.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a7a63e8318..375e6fd3ff 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 github.com/coreos/go-oidc/v3 v3.14.1 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf - github.com/go-jose/go-jose/v4 v4.1.0 + github.com/go-jose/go-jose/v4 v4.1.1 github.com/godbus/dbus/v5 v5.1.0 github.com/golang-jwt/jwt/v5 v5.2.2 github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum index 3f7136cfe9..36fce53ac2 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY= -github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw= +github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= +github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= From 7897ea7887bda75b1d12afdef54e57de6d29afde Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 11:05:11 +0000 Subject: [PATCH 0493/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.75.0 to 1.76.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.75.0...v1.76.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-version: 1.76.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a7a63e8318..253f6144dd 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.2 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.75.0 + github.com/microsoftgraph/msgraph-sdk-go v1.76.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 diff --git a/go.sum b/go.sum index 3f7136cfe9..e370859645 100644 --- a/go.sum +++ b/go.sum @@ -65,8 +65,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.1.2 h1:1pUyA1QgIeKslQwb github.com/microsoft/kiota-serialization-multipart-go v1.1.2/go.mod h1:j2K7ZyYErloDu7Kuuk993DsvfoP7LPWvAo7rfDpdPio= github.com/microsoft/kiota-serialization-text-go v1.1.2 h1:7OfKFlzdjpPygca/+OtqafkEqCWR7+94efUFGC28cLw= github.com/microsoft/kiota-serialization-text-go v1.1.2/go.mod h1:QNTcswkBPFY3QVBFmzfk00UMNViKQtV0AQKCrRw5ibM= -github.com/microsoftgraph/msgraph-sdk-go v1.75.0 h1:CFlM+aMhoErKBy99X91tf417EImikVWpphfi5agmRwk= -github.com/microsoftgraph/msgraph-sdk-go v1.75.0/go.mod h1:5ncg4aauxM5XKHo/xvAq7Cjl6+Dqu6lOtoihSGKtDt4= +github.com/microsoftgraph/msgraph-sdk-go v1.76.0 h1:rv3zQMUxX0xJ1R/Ot0NC9qN/9BwPEypzWvqlmkICty8= +github.com/microsoftgraph/msgraph-sdk-go v1.76.0/go.mod h1:5ncg4aauxM5XKHo/xvAq7Cjl6+Dqu6lOtoihSGKtDt4= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 h1:5jCUSosTKaINzPPQXsz7wsHWwknyBmJSu8+ZWxx3kdQ= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2/go.mod h1:iD75MK3LX8EuwjDYCmh0hkojKXK6VKME33u4daCo3cE= github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= From 4f7ff180eaf62b1bba952cf51c89aa103907eaaa Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 27 Jun 2025 17:22:39 +0200 Subject: [PATCH 0494/1670] Remove stray closing directive block There is no opening directive block matching this closing directive block. --- docs/howto/configure-authd.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/howto/configure-authd.md b/docs/howto/configure-authd.md index 089038169c..0f560e4b22 100644 --- a/docs/howto/configure-authd.md +++ b/docs/howto/configure-authd.md @@ -238,9 +238,6 @@ in `allowed_users`: owner = "" ``` -:::: -::::: - ## Restart the broker When a configuration file is added you have to restart authd: From 010d73b589a9c9ed5198f953988a441b8d2d18a5 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 27 Jun 2025 17:48:02 +0200 Subject: [PATCH 0495/1670] Document the extra_groups and owner_extra_groups options Co-authored-by: Shane Crowley <66971213+edibotopic@users.noreply.github.com> --- docs/howto/configure-authd.md | 30 ++++++++++++++++++++++++++++++ docs/reference/group-management.md | 4 ++++ 2 files changed, 34 insertions(+) diff --git a/docs/howto/configure-authd.md b/docs/howto/configure-authd.md index 0f560e4b22..ee88ddd094 100644 --- a/docs/howto/configure-authd.md +++ b/docs/howto/configure-authd.md @@ -238,6 +238,36 @@ in `allowed_users`: owner = "" ``` +(ref::config-user-groups)= +## Configure user groups + +Some brokers support adding users to groups that are configured in the identity provider. + +> See the [group management reference](reference::group-management) for more details. + +In addition, you can configure extra groups for authd users. +On login, the users are added to these groups automatically. +Specify any extra groups in the `users` section of the broker +configuration file: + +```ini +[users] +## A comma-separated list of local groups which authd users will be +## added to upon login. +## Example: extra_groups = users +#extra_groups = +``` + +There is also an `owner_extra_groups` option for specifying additional local groups, +to which only the user with the [owner role](#configure-allowed-users) is added: + +```ini +## Like 'extra_groups', but only the user assigned the owner role +## will be added to these groups. +## Example: owner_extra_groups = sudo,lpadmin +#owner_extra_groups = +``` + ## Restart the broker When a configuration file is added you have to restart authd: diff --git a/docs/reference/group-management.md b/docs/reference/group-management.md index 12a7fdb3d9..a4ab7c6afe 100644 --- a/docs/reference/group-management.md +++ b/docs/reference/group-management.md @@ -1,8 +1,12 @@ +(reference::group-management)= # Group management Groups are used to manage users that all need the same access and permissions to resources. Groups from the remote provider can be mapped into local Linux groups for the user. +In addition, you can configure extra groups +[in the broker configuration file](ref::config-user-groups). + ```{note} Groups are currently supported for the `msentraid` broker. ``` From 76b2c5a1476ca44a7272a8f4de9e879ee7897fa1 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 30 Jun 2025 14:50:05 +0200 Subject: [PATCH 0496/1670] Fix UserPreCheck D-Bus interface in example broker See https://github.com/ubuntu/authd-oidc-brokers/pull/567 --- examplebroker/com.ubuntu.auth.ExampleBroker.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/examplebroker/com.ubuntu.auth.ExampleBroker.xml b/examplebroker/com.ubuntu.auth.ExampleBroker.xml index f4932b3fc2..86b1d78ed7 100644 --- a/examplebroker/com.ubuntu.auth.ExampleBroker.xml +++ b/examplebroker/com.ubuntu.auth.ExampleBroker.xml @@ -36,6 +36,7 @@ + From 72279600d3c793c30b02c8904cdcd794c827775a Mon Sep 17 00:00:00 2001 From: shanecrowley Date: Tue, 1 Jul 2025 13:16:40 +0100 Subject: [PATCH 0497/1670] add guidance for configuring password policy * Guides user in configuring local password policy * Updates wordlist for the automated spellcheck --- docs/.custom_wordlist.txt | 3 +++ docs/howto/configure-authd.md | 45 +++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/docs/.custom_wordlist.txt b/docs/.custom_wordlist.txt index 39f5d9ead3..d28d1832de 100644 --- a/docs/.custom_wordlist.txt +++ b/docs/.custom_wordlist.txt @@ -2,6 +2,7 @@ alice amd auth authd +authd's biometric Center DBus @@ -24,7 +25,9 @@ Kerberos keytab Keytab Keytabs +libpwquality linux +MDM mountpoint msentraid NFS diff --git a/docs/howto/configure-authd.md b/docs/howto/configure-authd.md index ee88ddd094..835ad81407 100644 --- a/docs/howto/configure-authd.md +++ b/docs/howto/configure-authd.md @@ -320,3 +320,48 @@ If you are using authd inside LXD containers, read our short guide on [how to use authd with LXD](howto::use-with-lxd), which outlines additional steps for configuring appropriate UID/GID ranges. ``` + +## Configure password quality + +You can change authd's local password policy to ensure that users always set +strong passwords. + +If your mobile device management (MDM) solution includes a compliance check for +the passwords of authd users, you may also need to configure authd's password +policy so that it matches that of the MDM. + +authd depends on the libpwquality library, which supports configuring password +quality. + +To configure the local password policy for authd, create a drop file in +`/etc/security/pwquality.conf.d/`. +This will override the default values in `/etc/security/pwquality.conf`. + +First, create a directory for the custom configuration file: + +```shell +sudo mkdir -p /etc/security/pwquality.conf.d/ +``` + +Then edit the file to add your settings. + +```shell +sudoedit /etc/security/pwquality.conf.d/99_custom-options.conf +``` + +For example, if your policy requires that passwords have a 14 character minimum, +edit the drop file to set the value for `minlen`: + +```{code-block} diff +:caption: /etc/security/pwquality.conf.d/99_custom-options.conf +# This file overrides the defaults set in /etc/security/pwquality.conf +# Refer to the pwquality file for additional configuration options. +# Minimum acceptable size for the new password (plus one if +# credits are not disabled which is the default). (See pam_cracklib manual.) +# Cannot be set to lower value than 6 (default is 8). +minlen = 14 +``` + +> The [man pages](https://manpages.ubuntu.com/manpages/noble/man5/pwquality.conf.5.html) +provide a full list of configuration options for the libpwquality library. + From d29c99f7e9d1f66bf8a59912beaf3a1f066dd530 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Jul 2025 09:05:02 +0000 Subject: [PATCH 0498/1670] deps(go): bump go.etcd.io/bbolt in the minor-updates group Bumps the minor-updates group with 1 update: [go.etcd.io/bbolt](https://github.com/etcd-io/bbolt). Updates `go.etcd.io/bbolt` from 1.4.1 to 1.4.2 - [Release notes](https://github.com/etcd-io/bbolt/releases) - [Commits](https://github.com/etcd-io/bbolt/compare/v1.4.1...v1.4.2) --- updated-dependencies: - dependency-name: go.etcd.io/bbolt dependency-version: 1.4.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-updates ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 1f517e48c6..84b1f2b088 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.10.0 github.com/ubuntu/decorate v0.0.0-20230606064312-bc4ac83958d6 - go.etcd.io/bbolt v1.4.1 + go.etcd.io/bbolt v1.4.2 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 golang.org/x/sys v0.33.0 golang.org/x/term v0.32.0 diff --git a/go.sum b/go.sum index a4e5d11da2..8d3a44d577 100644 --- a/go.sum +++ b/go.sum @@ -116,8 +116,8 @@ github.com/ubuntu/decorate v0.0.0-20230606064312-bc4ac83958d6 h1:J0625LLHcZxxnnK github.com/ubuntu/decorate v0.0.0-20230606064312-bc4ac83958d6/go.mod h1:edGgz97NOqS2oqzbKrZqO9YU9neosRrkEZbVJVQynAA= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -go.etcd.io/bbolt v1.4.1 h1:5mOV+HWjIPLEAlUGMsveaUvK2+byZMFOzojoi7bh7uI= -go.etcd.io/bbolt v1.4.1/go.mod h1:c8zu2BnXWTu2XM4XcICtbGSl9cFwsXtcf9zLt2OncM8= +go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= +go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= From 15304654d8d77e7c64b061e8fe4f77332f40dc57 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Fri, 27 Jun 2025 07:53:48 -0400 Subject: [PATCH 0499/1670] Update behavior for ssh_allowed_suffixes We used to allow every user to authenticate for the first time through SSH if this setting was not configured. This behavior could be misleading and result in unexpected situations for the users. To improve this, we now consider "" to mean 'disallow all' and have a new '*' value to represent the 'allow all' option, in case users still want to keep this. --- internal/broker/broker.go | 6 ++++++ internal/broker/broker_test.go | 19 ++++++++++++++++++- ...ully_allow_username_if_suffix_has_asterisk | 1 + ...ully_allow_username_if_suffix_is_allow_all | 1 + ...w_username_ignoring_empty_string_in_config | 1 + 5 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 internal/broker/testdata/golden/TestUserPreCheck/Successfully_allow_username_if_suffix_has_asterisk create mode 100644 internal/broker/testdata/golden/TestUserPreCheck/Successfully_allow_username_if_suffix_is_allow_all create mode 100644 internal/broker/testdata/golden/TestUserPreCheck/Successfully_allow_username_ignoring_empty_string_in_config diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 3f9ae9cdc0..a5a57b905a 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -802,6 +802,12 @@ func (b *Broker) CancelIsAuthenticated(sessionID string) { func (b *Broker) UserPreCheck(username string) (string, error) { found := false for _, suffix := range b.cfg.allowedSSHSuffixes { + if suffix == "" { + continue + } + + // If suffx is only "*", TrimPrefix will return the empty string and that works for the 'match all' case also. + suffix = strings.TrimPrefix(suffix, "*") if strings.HasSuffix(username, suffix) { found = true break diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index 525edef85b..f8e63d09ce 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -1202,6 +1202,18 @@ func TestUserPreCheck(t *testing.T) { username: "user@allowed", allowedSuffixes: []string{"@other", "@something", "@allowed"}, }, + "Successfully_allow_username_if_suffix_is_allow_all": { + username: "user@doesnotmatter", + allowedSuffixes: []string{"*"}, + }, + "Successfully_allow_username_if_suffix_has_asterisk": { + username: "user@allowed", + allowedSuffixes: []string{"*@allowed"}, + }, + "Successfully_allow_username_ignoring_empty_string_in_config": { + username: "user@allowed", + allowedSuffixes: []string{"@anothersuffix", "", "@allowed"}, + }, "Return_userinfo_with_correct_homedir_after_precheck": { username: "user@allowed", allowedSuffixes: []string{"@allowed"}, @@ -1215,13 +1227,18 @@ func TestUserPreCheck(t *testing.T) { }, "Error_when_username_does_not_match_any_of_the_allowed_suffixes": { username: "user@notallowed", - allowedSuffixes: []string{"@other", "@something", "@allowed"}, + allowedSuffixes: []string{"@other", "@something", "@allowed", ""}, wantErr: true, }, "Error_when_no_allowed_suffixes_are_provided": { username: "user@allowed", wantErr: true, }, + "Error_when_allowed_suffixes_has_only_empty_string": { + username: "user@allowed", + allowedSuffixes: []string{""}, + wantErr: true, + }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { diff --git a/internal/broker/testdata/golden/TestUserPreCheck/Successfully_allow_username_if_suffix_has_asterisk b/internal/broker/testdata/golden/TestUserPreCheck/Successfully_allow_username_if_suffix_has_asterisk new file mode 100644 index 0000000000..1c1ddfb545 --- /dev/null +++ b/internal/broker/testdata/golden/TestUserPreCheck/Successfully_allow_username_if_suffix_has_asterisk @@ -0,0 +1 @@ +{"name":"user@allowed","uuid":"","dir":"/home/user@allowed","shell":"/usr/bin/bash","gecos":"user@allowed","groups":null} \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestUserPreCheck/Successfully_allow_username_if_suffix_is_allow_all b/internal/broker/testdata/golden/TestUserPreCheck/Successfully_allow_username_if_suffix_is_allow_all new file mode 100644 index 0000000000..6a420fc5e0 --- /dev/null +++ b/internal/broker/testdata/golden/TestUserPreCheck/Successfully_allow_username_if_suffix_is_allow_all @@ -0,0 +1 @@ +{"name":"user@doesnotmatter","uuid":"","dir":"/home/user@doesnotmatter","shell":"/usr/bin/bash","gecos":"user@doesnotmatter","groups":null} \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestUserPreCheck/Successfully_allow_username_ignoring_empty_string_in_config b/internal/broker/testdata/golden/TestUserPreCheck/Successfully_allow_username_ignoring_empty_string_in_config new file mode 100644 index 0000000000..1c1ddfb545 --- /dev/null +++ b/internal/broker/testdata/golden/TestUserPreCheck/Successfully_allow_username_ignoring_empty_string_in_config @@ -0,0 +1 @@ +{"name":"user@allowed","uuid":"","dir":"/home/user@allowed","shell":"/usr/bin/bash","gecos":"user@allowed","groups":null} \ No newline at end of file From ac9227ac4e8e285344d217a6862fc4a46cd6e965 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Fri, 27 Jun 2025 07:59:32 -0400 Subject: [PATCH 0500/1670] Rename ssh_allowed_suffixes key and improve comment We only provided a (way too) simple explanation for the ssh_allowed_suffixes setting and this could create some misunderstanding on how this value is supposed to work. Improving the wording on the configuration value to better inform the user of what that value is supposed to affect should minimize the confusion. Renaming the key should also help the users understand what's going on and its purporse. --- conf/broker.conf | 21 +++++++++++++-- internal/broker/config.go | 15 ++++++++--- internal/broker/config_test.go | 26 ++++++++++++++----- .../All_are_allowed/broker.conf | 2 +- .../broker.conf | 2 +- .../Only_owner_is_allowed/broker.conf | 2 +- .../broker.conf | 2 +- .../broker.conf | 2 +- .../Set_owner_and_u1_is_allowed/broker.conf | 2 +- .../Support_old_suffixes_key/broker.conf | 10 +++++++ .../broker.conf.d/.empty | 0 .../Unset_owner_and_u1_is_allowed/broker.conf | 2 +- .../Users_u1_and_u2_are_allowed/broker.conf | 2 +- 13 files changed, 67 insertions(+), 21 deletions(-) create mode 100644 internal/broker/testdata/golden/TestParseUserConfig/Support_old_suffixes_key/broker.conf create mode 100644 internal/broker/testdata/golden/TestParseUserConfig/Support_old_suffixes_key/broker.conf.d/.empty diff --git a/conf/broker.conf b/conf/broker.conf index e2e952bea3..b2a23a8145 100644 --- a/conf/broker.conf +++ b/conf/broker.conf @@ -25,9 +25,26 @@ client_id = ## The home directories are created in the format / #home_base_dir = /home +## By default, SSH only allows logins from users that already exist on the +## system. +## New authd users (who have never logged in before) are *not* allowed to log +## in for the first time via SSH unless this option is configured. +## ## If configured, only users with a suffix in this list are allowed to -## log in via SSH. The suffixes must be separated by comma. -#ssh_allowed_suffixes = @example.com,@anotherexample.com +## authenticate for the first time directly through SSH. +## Note that this does not affect users that already authenticated for +## the first time and already exist on the system. +## +## Suffixes must be comma-separated (e.g., '@example.com,@example.org'). +## To allow all suffixes, use a single asterisk ('*'). +## +## Example: +## ssh_allowed_suffixes_first_auth = @example.com,@anotherexample.org +## +## Example (allow all): +## ssh_allowed_suffixes_first_auth = * +## +#ssh_allowed_suffixes_first_auth = ## 'allowed_users' specifies the users who are permitted to log in after ## successfully authenticating with the identity provider. diff --git a/internal/broker/config.go b/internal/broker/config.go index 5f93d21dc2..870f944c50 100644 --- a/internal/broker/config.go +++ b/internal/broker/config.go @@ -36,13 +36,14 @@ const ( ownerKey = "owner" // homeDirKey is the key in the config file for the home directory prefix. homeDirKey = "home_base_dir" - // SSHSuffixKey is the key in the config file for the SSH allowed suffixes. - sshSuffixesKey = "ssh_allowed_suffixes" + // sshSuffixesKey is the key in the config file for the SSH allowed suffixes. + sshSuffixesKey = "ssh_allowed_suffixes_first_auth" + // sshSuffixesKeyOld is the old key in the config file for the SSH allowed suffixes. It should be removed later. + sshSuffixesKeyOld = "ssh_allowed_suffixes" // extraGroupsKey is the key in the config file for the extra groups to add to each authd user. extraGroupsKey = "extra_groups" // ownerExtraGroupsKey is the key in the config file for the extra groups to add to the owner. ownerExtraGroupsKey = "owner_extra_groups" - // allUsersKeyword is the keyword for the `allowed_users` key that allows access to all users. allUsersKeyword = "ALL" // ownerUserKeyword is the keyword for the `allowed_users` key that allows access to the owner. @@ -127,7 +128,13 @@ func (uc *userConfig) populateUsersConfig(users *ini.Section) { } uc.homeBaseDir = users.Key(homeDirKey).String() - uc.allowedSSHSuffixes = strings.Split(users.Key(sshSuffixesKey).String(), ",") + + suffixesKey := sshSuffixesKey + // If we don't have the new key, we should try reading the old one instead. + if !users.HasKey(sshSuffixesKey) { + suffixesKey = sshSuffixesKeyOld + } + uc.allowedSSHSuffixes = strings.Split(users.Key(suffixesKey).String(), ",") if uc.allowedUsers == nil { uc.allowedUsers = make(map[string]struct{}) diff --git a/internal/broker/config_test.go b/internal/broker/config_test.go index 9787e20652..5b3ce660e4 100644 --- a/internal/broker/config_test.go +++ b/internal/broker/config_test.go @@ -178,7 +178,7 @@ client_id = client_id allowed_users = ALL owner = machine_owner home_base_dir = /home -allowed_ssh_suffixes = @issuer.url.com +allowed_ssh_suffixes_first_auth = @issuer.url.com `, "Only_owner_is_allowed": ` [oidc] @@ -189,7 +189,7 @@ client_id = client_id allowed_users = OWNER owner = machine_owner home_base_dir = /home -allowed_ssh_suffixes = @issuer.url.com +allowed_ssh_suffixes_first_auth = @issuer.url.com `, "By_default_only_owner_is_allowed": ` [oidc] @@ -199,7 +199,7 @@ client_id = client_id [users] owner = machine_owner home_base_dir = /home -allowed_ssh_suffixes = @issuer.url.com +allowed_ssh_suffixes_first_auth = @issuer.url.com `, "Only_owner_is_allowed_but_is_unset": ` [oidc] @@ -208,7 +208,7 @@ client_id = client_id [users] home_base_dir = /home -allowed_ssh_suffixes = @issuer.url.com +allowed_ssh_suffixes_first_auth = @issuer.url.com `, "Only_owner_is_allowed_but_is_empty": ` [oidc] @@ -218,7 +218,7 @@ client_id = client_id [users] owner = home_base_dir = /home -allowed_ssh_suffixes = @issuer.url.com +allowed_ssh_suffixes_first_auth = @issuer.url.com `, "Users_u1_and_u2_are_allowed": ` [oidc] @@ -228,7 +228,7 @@ client_id = client_id [users] allowed_users = u1,u2 home_base_dir = /home -allowed_ssh_suffixes = @issuer.url.com +allowed_ssh_suffixes_first_auth = @issuer.url.com `, "Unset_owner_and_u1_is_allowed": ` [oidc] @@ -238,7 +238,7 @@ client_id = client_id [users] allowed_users = OWNER,u1 home_base_dir = /home -allowed_ssh_suffixes = @issuer.url.com +allowed_ssh_suffixes_first_auth = @issuer.url.com `, "Set_owner_and_u1_is_allowed": ` [oidc] @@ -249,6 +249,17 @@ client_id = client_id allowed_users = OWNER,u1 owner = machine_owner home_base_dir = /home +allowed_ssh_suffixes_first_auth = @issuer.url.com +`, + "Support_old_suffixes_key": ` +[oidc] +issuer = https://issuer.url.com +client_id = client_id + +[users] +allowed_users = ALL +owner = machine_owner +home_base_dir = /home allowed_ssh_suffixes = @issuer.url.com `, } @@ -280,6 +291,7 @@ func TestParseUserConfig(t *testing.T) { wantOwner: "machine_owner", wantAllowedUsers: []string{"u1"}, }, + "Support_old_suffixes_key": {wantAllUsersAllowed: true, wantOwner: "machine_owner"}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { diff --git a/internal/broker/testdata/golden/TestParseUserConfig/All_are_allowed/broker.conf b/internal/broker/testdata/golden/TestParseUserConfig/All_are_allowed/broker.conf index b777f7fc85..18badfb1e6 100644 --- a/internal/broker/testdata/golden/TestParseUserConfig/All_are_allowed/broker.conf +++ b/internal/broker/testdata/golden/TestParseUserConfig/All_are_allowed/broker.conf @@ -7,4 +7,4 @@ client_id = client_id allowed_users = ALL owner = machine_owner home_base_dir = /home -allowed_ssh_suffixes = @issuer.url.com +allowed_ssh_suffixes_first_auth = @issuer.url.com diff --git a/internal/broker/testdata/golden/TestParseUserConfig/By_default_only_owner_is_allowed/broker.conf b/internal/broker/testdata/golden/TestParseUserConfig/By_default_only_owner_is_allowed/broker.conf index 46abf5a379..1c8f8105bf 100644 --- a/internal/broker/testdata/golden/TestParseUserConfig/By_default_only_owner_is_allowed/broker.conf +++ b/internal/broker/testdata/golden/TestParseUserConfig/By_default_only_owner_is_allowed/broker.conf @@ -6,4 +6,4 @@ client_id = client_id [users] owner = machine_owner home_base_dir = /home -allowed_ssh_suffixes = @issuer.url.com +allowed_ssh_suffixes_first_auth = @issuer.url.com diff --git a/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed/broker.conf b/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed/broker.conf index bcd7c04b05..4146b36d1c 100644 --- a/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed/broker.conf +++ b/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed/broker.conf @@ -7,4 +7,4 @@ client_id = client_id allowed_users = OWNER owner = machine_owner home_base_dir = /home -allowed_ssh_suffixes = @issuer.url.com +allowed_ssh_suffixes_first_auth = @issuer.url.com diff --git a/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed_but_is_empty/broker.conf b/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed_but_is_empty/broker.conf index 8ad17837e3..6fedf4fead 100644 --- a/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed_but_is_empty/broker.conf +++ b/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed_but_is_empty/broker.conf @@ -6,4 +6,4 @@ client_id = client_id [users] owner = home_base_dir = /home -allowed_ssh_suffixes = @issuer.url.com +allowed_ssh_suffixes_first_auth = @issuer.url.com diff --git a/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed_but_is_unset/broker.conf b/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed_but_is_unset/broker.conf index 2f17914dda..7657c12ad9 100644 --- a/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed_but_is_unset/broker.conf +++ b/internal/broker/testdata/golden/TestParseUserConfig/Only_owner_is_allowed_but_is_unset/broker.conf @@ -5,4 +5,4 @@ client_id = client_id [users] home_base_dir = /home -allowed_ssh_suffixes = @issuer.url.com +allowed_ssh_suffixes_first_auth = @issuer.url.com diff --git a/internal/broker/testdata/golden/TestParseUserConfig/Set_owner_and_u1_is_allowed/broker.conf b/internal/broker/testdata/golden/TestParseUserConfig/Set_owner_and_u1_is_allowed/broker.conf index 050e023017..4d5ee91a36 100644 --- a/internal/broker/testdata/golden/TestParseUserConfig/Set_owner_and_u1_is_allowed/broker.conf +++ b/internal/broker/testdata/golden/TestParseUserConfig/Set_owner_and_u1_is_allowed/broker.conf @@ -7,4 +7,4 @@ client_id = client_id allowed_users = OWNER,u1 owner = machine_owner home_base_dir = /home -allowed_ssh_suffixes = @issuer.url.com +allowed_ssh_suffixes_first_auth = @issuer.url.com diff --git a/internal/broker/testdata/golden/TestParseUserConfig/Support_old_suffixes_key/broker.conf b/internal/broker/testdata/golden/TestParseUserConfig/Support_old_suffixes_key/broker.conf new file mode 100644 index 0000000000..b777f7fc85 --- /dev/null +++ b/internal/broker/testdata/golden/TestParseUserConfig/Support_old_suffixes_key/broker.conf @@ -0,0 +1,10 @@ + +[oidc] +issuer = https://issuer.url.com +client_id = client_id + +[users] +allowed_users = ALL +owner = machine_owner +home_base_dir = /home +allowed_ssh_suffixes = @issuer.url.com diff --git a/internal/broker/testdata/golden/TestParseUserConfig/Support_old_suffixes_key/broker.conf.d/.empty b/internal/broker/testdata/golden/TestParseUserConfig/Support_old_suffixes_key/broker.conf.d/.empty new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/golden/TestParseUserConfig/Unset_owner_and_u1_is_allowed/broker.conf b/internal/broker/testdata/golden/TestParseUserConfig/Unset_owner_and_u1_is_allowed/broker.conf index 890dcb9873..39fc9aa19b 100644 --- a/internal/broker/testdata/golden/TestParseUserConfig/Unset_owner_and_u1_is_allowed/broker.conf +++ b/internal/broker/testdata/golden/TestParseUserConfig/Unset_owner_and_u1_is_allowed/broker.conf @@ -6,4 +6,4 @@ client_id = client_id [users] allowed_users = OWNER,u1 home_base_dir = /home -allowed_ssh_suffixes = @issuer.url.com +allowed_ssh_suffixes_first_auth = @issuer.url.com diff --git a/internal/broker/testdata/golden/TestParseUserConfig/Users_u1_and_u2_are_allowed/broker.conf b/internal/broker/testdata/golden/TestParseUserConfig/Users_u1_and_u2_are_allowed/broker.conf index 6a2c1bc113..fd4ce484d7 100644 --- a/internal/broker/testdata/golden/TestParseUserConfig/Users_u1_and_u2_are_allowed/broker.conf +++ b/internal/broker/testdata/golden/TestParseUserConfig/Users_u1_and_u2_are_allowed/broker.conf @@ -6,4 +6,4 @@ client_id = client_id [users] allowed_users = u1,u2 home_base_dir = /home -allowed_ssh_suffixes = @issuer.url.com +allowed_ssh_suffixes_first_auth = @issuer.url.com From 28402ec9d8c3de7578b4ac3996d47f37c1ece337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Sat, 5 Jul 2025 02:02:08 +0200 Subject: [PATCH 0501/1670] users/db/bbolt: Drop unused code We have lots of code in bolt DB that is still unused, neither for migration purposes, so clean it up (since our coverage numbers are affected by it too :), but well... More seriously it's just dead code) --- internal/users/db/bbolt/delete.go | 66 ----------- internal/users/db/bbolt/getgroups.go | 57 --------- internal/users/db/bbolt/getusers.go | 23 ---- internal/users/db/bbolt/update.go | 166 --------------------------- 4 files changed, 312 deletions(-) diff --git a/internal/users/db/bbolt/delete.go b/internal/users/db/bbolt/delete.go index a71e0ae822..73f82600b7 100644 --- a/internal/users/db/bbolt/delete.go +++ b/internal/users/db/bbolt/delete.go @@ -9,28 +9,9 @@ import ( "strconv" "github.com/ubuntu/authd/log" - "github.com/ubuntu/decorate" "go.etcd.io/bbolt" ) -// DeleteUser removes the user from the database. -func (c *Database) DeleteUser(uid uint32) error { - c.mu.RLock() - defer c.mu.RUnlock() - - return c.db.Update(func(tx *bbolt.Tx) error { - buckets, err := getAllBuckets(tx) - if err != nil { - return err - } - - if err := deleteUser(buckets, uid); err != nil { - return err - } - return nil - }) -} - // deleteUserFromGroup removes the uid from the group. // If the group is empty after the uid gets removed, the group is deleted from the database. func deleteUserFromGroup(buckets map[string]bucketWithName, uid, gid uint32) error { @@ -48,44 +29,6 @@ func deleteUserFromGroup(buckets map[string]bucketWithName, uid, gid uint32) err return nil } -// deleteUser removes the user from the database. -func deleteUser(buckets map[string]bucketWithName, uid uint32) (err error) { - defer decorate.OnError(&err, "could not remove user %d from db", uid) - - u, err := getFromBucket[UserDB](buckets[userByIDBucketName], uid) - if err != nil { - return err - } - - userToGroups, err := getFromBucket[userToGroupsDB](buckets[userToGroupsBucketName], uid) - if err != nil { - return err - } - for _, gid := range userToGroups.GIDs { - if err := deleteUserFromGroup(buckets, uid, gid); err != nil { - return err - } - } - - uidKey := []byte(strconv.FormatUint(uint64(u.UID), 10)) - - // Delete user - // Delete calls fail if the transaction is read only, so we should panic if this function is called in that context. - if err = buckets[userToGroupsBucketName].Delete(uidKey); err != nil { - panic(fmt.Sprintf("programming error: delete is not allowed in a RO transaction: %v", err)) - } - if err = buckets[userByIDBucketName].Delete(uidKey); err != nil { - panic(fmt.Sprintf("programming error: delete is not allowed in a RO transaction: %v", err)) - } - if err = buckets[userByNameBucketName].Delete([]byte(u.Name)); err != nil { - panic(fmt.Sprintf("programming error: delete is not allowed in a RO transaction: %v", err)) - } - if err = buckets[userToBrokerBucketName].Delete(uidKey); err != nil { - panic(fmt.Sprintf("programming error: delete is not allowed in a RO transaction: %v", err)) - } - return nil -} - // deleteOrphanedUsers removes users from the UserByID bucket that are not in the UserByName bucket. func deleteOrphanedUsers(db *bbolt.DB) error { log.Debug(context.TODO(), "Cleaning up orphaned user records") @@ -151,12 +94,3 @@ func deleteOrphanedUser(buckets map[string]bucketWithName, uid uint32) (err erro return nil } - -// deleteRenamedGroup removes a group record with the given Name from groupByName bucket. -func deleteRenamedGroup(buckets map[string]bucketWithName, groupName string) (err error) { - // Delete calls fail if the transaction is read only, so we should panic if this function is called in that context. - if err = buckets[groupByNameBucketName].Delete([]byte(groupName)); err != nil { - panic(fmt.Sprintf("programming error: delete is not allowed in a RO transaction: %v", err)) - } - return nil -} diff --git a/internal/users/db/bbolt/getgroups.go b/internal/users/db/bbolt/getgroups.go index 056be06e94..7b8f69c893 100644 --- a/internal/users/db/bbolt/getgroups.go +++ b/internal/users/db/bbolt/getgroups.go @@ -23,21 +23,6 @@ func NewGroupDB(name string, gid uint32, ugid string, members []string) GroupDB } } -// GroupByID returns a group matching this gid or an error if the database is corrupted or no entry was found. -func (c *Database) GroupByID(gid uint32) (GroupDB, error) { - return getGroup(c, groupByIDBucketName, gid) -} - -// GroupByName returns a group matching a given name or an error if the database is corrupted or no entry was found. -func (c *Database) GroupByName(name string) (GroupDB, error) { - return getGroup(c, groupByNameBucketName, name) -} - -// GroupByUGID returns a group matching this ugid or an error if the database is corrupted or no entry was found. -func (c *Database) GroupByUGID(ugid string) (GroupDB, error) { - return getGroup(c, groupByUGIDBucketName, ugid) -} - // UserGroups returns all groups for a given user or an error if the database is corrupted or no entry was found. func (c *Database) UserGroups(uid uint32) ([]GroupDB, error) { c.mu.RLock() @@ -140,48 +125,6 @@ func (c *Database) AllGroups() (all []GroupDB, err error) { return all, nil } -// getGroup returns a group matching the key and its members or an error if the database is corrupted -// or no entry was found. -func getGroup[K uint32 | string](c *Database, bucketName string, key K) (GroupDB, error) { - var groupName string - var gid uint32 - var ugid string - var users []string - - c.mu.RLock() - defer c.mu.RUnlock() - err := c.db.View(func(tx *bbolt.Tx) error { - buckets, err := getAllBuckets(tx) - if err != nil { - return err - } - - // Get id, name and ugid of the group. - g, err := getFromBucket[groupDB](buckets[bucketName], key) - if err != nil { - return err - } - - groupName = g.Name - gid = g.GID - ugid = g.UGID - - // Get user names in the group. - users, err = getUsersInGroup(buckets, gid) - if err != nil { - return err - } - - return nil - }) - - if err != nil { - return GroupDB{}, err - } - - return NewGroupDB(groupName, gid, ugid, users), nil -} - // usersInGroup returns all user names in a given group. It returns an error if the database is corrupted. func getUsersInGroup(buckets map[string]bucketWithName, gid uint32) (users []string, err error) { usersInGroup, err := getFromBucket[groupToUsersDB](buckets[groupToUsersBucketName], gid) diff --git a/internal/users/db/bbolt/getusers.go b/internal/users/db/bbolt/getusers.go index 56c8ed6e94..eb3582a435 100644 --- a/internal/users/db/bbolt/getusers.go +++ b/internal/users/db/bbolt/getusers.go @@ -7,29 +7,6 @@ import ( "go.etcd.io/bbolt" ) -// NewUserDB creates a new UserDB. -func NewUserDB(name string, uid, gid uint32, gecos, dir, shell string) UserDB { - return UserDB{ - Name: name, - UID: uid, - GID: gid, - Gecos: gecos, - Dir: dir, - Shell: shell, - LastPwdChange: -1, - MaxPwdAge: -1, - PwdWarnPeriod: -1, - PwdInactivity: -1, - MinPwdAge: -1, - ExpirationDate: -1, - } -} - -// UserByID returns a user matching this uid or an error if the database is corrupted or no entry was found. -func (c *Database) UserByID(uid uint32) (UserDB, error) { - return getUser(c, userByIDBucketName, uid) -} - // UserByName returns a user matching this name or an error if the database is corrupted or no entry was found. func (c *Database) UserByName(name string) (UserDB, error) { return getUser(c, userByNameBucketName, name) diff --git a/internal/users/db/bbolt/update.go b/internal/users/db/bbolt/update.go index 9cafea4132..87053f3549 100644 --- a/internal/users/db/bbolt/update.go +++ b/internal/users/db/bbolt/update.go @@ -1,155 +1,11 @@ package bbolt import ( - "context" "encoding/json" - "errors" "fmt" - "slices" "strconv" - - "github.com/ubuntu/authd/log" - "go.etcd.io/bbolt" ) -// UpdateUserEntry inserts or updates user and group buckets from the user information. -func (c *Database) UpdateUserEntry(user UserDB, authdGroups []GroupDB, localGroups []string) error { - c.mu.RLock() - defer c.mu.RUnlock() - - err := c.db.Update(func(tx *bbolt.Tx) error { - buckets, err := getAllBuckets(tx) - if err != nil { - return err - } - - previousGroupsForCurrentUser, err := getFromBucket[userToGroupsDB](buckets[userToGroupsBucketName], user.UID) - // No data is valid and means this is the first insertion. - if err != nil && !errors.Is(err, NoDataFoundError{}) { - return err - } - - /* 1. Handle user update */ - if err := updateUser(buckets, user); err != nil { - return err - } - - /* 2. Handle groups update */ - if err := updateGroups(buckets, authdGroups); err != nil { - return err - } - - /* 3. Users and groups mapping buckets */ - if err := updateUsersAndGroups(buckets, user.UID, authdGroups, previousGroupsForCurrentUser.GIDs); err != nil { - return err - } - - /* 4. Update user to local groups bucket */ - updateBucket(buckets[userToLocalGroupsBucketName], user.UID, localGroups) - - return nil - }) - - return err -} - -// updateUser updates both user buckets with userContent. -func updateUser(buckets map[string]bucketWithName, userContent UserDB) error { - existingUser, err := getFromBucket[UserDB](buckets[userByIDBucketName], userContent.UID) - if err != nil && !errors.Is(err, NoDataFoundError{}) { - return err - } - - // If a user with the same UID exists, we need to ensure that it's the same user or fail the update otherwise. - if existingUser.Name != "" && existingUser.Name != userContent.Name { - log.Errorf(context.TODO(), "UID for user %q already in use by user %q", userContent.Name, existingUser.Name) - return errors.New("UID already in use by a different user") - } - - // Ensure that we use the same homedir as the one we have in the database. - if existingUser.Dir != "" && existingUser.Dir != userContent.Dir { - log.Warningf(context.TODO(), "User %q already has a homedir. The existing %q one will be kept instead of %q", userContent.Name, existingUser.Dir, userContent.Dir) - userContent.Dir = existingUser.Dir - } - - // Update user buckets - log.Debugf(context.Background(), "Updating entry of user %q (UID: %d)", userContent.Name, userContent.UID) - updateBucket(buckets[userByIDBucketName], userContent.UID, userContent) - updateBucket(buckets[userByNameBucketName], userContent.Name, userContent) - - return nil -} - -// updateUser updates all group buckets with groupContent. -func updateGroups(buckets map[string]bucketWithName, groupContents []GroupDB) error { - for _, groupContent := range groupContents { - existingGroup, err := getFromBucket[groupDB](buckets[groupByIDBucketName], groupContent.GID) - if err != nil && !errors.Is(err, NoDataFoundError{}) { - return err - } - groupExists := !errors.Is(err, NoDataFoundError{}) - - // If a group with the same GID exists, we need to ensure that it's the same group or fail the update otherwise. - // Ignore the case that the UGID of the existing group is empty, which means that the group was stored without a - // UGID, which was the case before https://github.com/ubuntu/authd/pull/647. - if groupExists && existingGroup.UGID != "" && existingGroup.UGID != groupContent.UGID { - log.Errorf(context.TODO(), "GID %d for group with UGID %q already in use by a group with UGID %q", groupContent.GID, groupContent.UGID, existingGroup.UGID) - return fmt.Errorf("GID for group %q already in use by a different group", groupContent.Name) - } - - // Same GID and UGID but a different Name can happen due to group renaming at provider's end. - if groupExists && existingGroup.Name != groupContent.Name { - // The record being pointed by the existing group name in the groupByName bucket should be deleted. - if err := deleteRenamedGroup(buckets, existingGroup.Name); err != nil { - return err - } - } - - // Update group buckets - updateBucket(buckets[groupByIDBucketName], groupContent.GID, groupDB{Name: groupContent.Name, GID: groupContent.GID, UGID: groupContent.UGID}) - updateBucket(buckets[groupByNameBucketName], groupContent.Name, groupDB{Name: groupContent.Name, GID: groupContent.GID, UGID: groupContent.UGID}) - - if groupContent.UGID != "" { - updateBucket(buckets[groupByUGIDBucketName], groupContent.UGID, groupDB{Name: groupContent.Name, GID: groupContent.GID, UGID: groupContent.UGID}) - } - } - - return nil -} - -// updateUserAndGroups updates the pivot table for user to groups and group to users. It handles any update -// to groups uid is not part of anymore. -func updateUsersAndGroups(buckets map[string]bucketWithName, uid uint32, groupContents []GroupDB, previousGIDs []uint32) error { - var currentGIDs []uint32 - for _, groupContent := range groupContents { - currentGIDs = append(currentGIDs, groupContent.GID) - grpToUsers, err := getFromBucket[groupToUsersDB](buckets[groupToUsersBucketName], groupContent.GID) - // No data is valid and means that this is the first time we record it. - if err != nil && !errors.Is(err, NoDataFoundError{}) { - return err - } - - grpToUsers.GID = groupContent.GID - if !slices.Contains(grpToUsers.UIDs, uid) { - grpToUsers.UIDs = append(grpToUsers.UIDs, uid) - } - updateBucket(buckets[groupToUsersBucketName], groupContent.GID, grpToUsers) - } - updateBucket(buckets[userToGroupsBucketName], uid, userToGroupsDB{UID: uid, GIDs: currentGIDs}) - - // Remove UID from any groups this user is not part of anymore. - for _, previousGID := range previousGIDs { - if slices.Contains(currentGIDs, previousGID) { - continue - } - if err := deleteUserFromGroup(buckets, uid, previousGID); err != nil { - return err - } - } - - return nil -} - // updateBucket is a generic function to update any bucket. It panics if we call it in RO transaction. func updateBucket[K uint32 | string](bucket bucketWithName, key K, value any) { data, err := json.Marshal(value) @@ -172,25 +28,3 @@ func updateBucket[K uint32 | string](bucket bucketWithName, key K, value any) { panic(fmt.Sprintf("programming error: Put is not executed in a RW transaction: %v", err)) } } - -// UpdateBrokerForUser updates the last broker the user successfully authenticated with. -func (c *Database) UpdateBrokerForUser(username, brokerID string) error { - c.mu.RLock() - defer c.mu.RUnlock() - - u, err := c.UserByName(username) - if err != nil { - return err - } - - err = c.db.Update(func(tx *bbolt.Tx) error { - bucket, err := getBucket(tx, userToBrokerBucketName) - if err != nil { - return err - } - updateBucket(bucket, u.UID, brokerID) - return nil - }) - - return err -} From 0db14dfe76261c927096ff7c9291b7ae0fbbfa96 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 10:11:02 +0000 Subject: [PATCH 0502/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-version: 1.77.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f93a84fb2b..0940d1c924 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.2 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.76.0 + github.com/microsoftgraph/msgraph-sdk-go v1.77.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 diff --git a/go.sum b/go.sum index 001fc0c69b..7f50896704 100644 --- a/go.sum +++ b/go.sum @@ -65,8 +65,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.1.2 h1:1pUyA1QgIeKslQwb github.com/microsoft/kiota-serialization-multipart-go v1.1.2/go.mod h1:j2K7ZyYErloDu7Kuuk993DsvfoP7LPWvAo7rfDpdPio= github.com/microsoft/kiota-serialization-text-go v1.1.2 h1:7OfKFlzdjpPygca/+OtqafkEqCWR7+94efUFGC28cLw= github.com/microsoft/kiota-serialization-text-go v1.1.2/go.mod h1:QNTcswkBPFY3QVBFmzfk00UMNViKQtV0AQKCrRw5ibM= -github.com/microsoftgraph/msgraph-sdk-go v1.76.0 h1:rv3zQMUxX0xJ1R/Ot0NC9qN/9BwPEypzWvqlmkICty8= -github.com/microsoftgraph/msgraph-sdk-go v1.76.0/go.mod h1:5ncg4aauxM5XKHo/xvAq7Cjl6+Dqu6lOtoihSGKtDt4= +github.com/microsoftgraph/msgraph-sdk-go v1.77.0 h1:Dy6RQCsEgWlARfynx0Rai+PJyK8THgdNOwkpYaPCvJc= +github.com/microsoftgraph/msgraph-sdk-go v1.77.0/go.mod h1:5ncg4aauxM5XKHo/xvAq7Cjl6+Dqu6lOtoihSGKtDt4= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 h1:5jCUSosTKaINzPPQXsz7wsHWwknyBmJSu8+ZWxx3kdQ= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2/go.mod h1:iD75MK3LX8EuwjDYCmh0hkojKXK6VKME33u4daCo3cE= github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= From 947f25a8cfdb5bfd35bcfc39f630a474018316cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 7 Jul 2025 11:09:30 +0200 Subject: [PATCH 0503/1670] ci/qa: Install llvm-symbolizer to symbolize ASAN reports The reports we're getting from ASAN tests are not really very useful, let's see if adding a symbolizer helps --- .github/workflows/qa.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/qa.yaml b/.github/workflows/qa.yaml index 6807afa843..6ad1c50a44 100644 --- a/.github/workflows/qa.yaml +++ b/.github/workflows/qa.yaml @@ -316,6 +316,9 @@ jobs: # Print executed commands to ease debugging set -x + # For llvm-symbolizer + sudo apt install -y llvm + go test -C ./pam/internal -json -asan -gcflags=all="${GO_GC_FLAGS}" -failfast -timeout ${GO_TESTS_TIMEOUT} ./... | \ gotestfmt --logfile "${AUTHD_TESTS_ARTIFACTS_PATH}/gotestfmt.pam-internal-asan.log" || exit_code=$? if [ -n "${exit_code:-}" ]; then From e0a68864d04e76f8d8137ad32e0d77f31c740b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 7 Jul 2025 13:18:22 +0200 Subject: [PATCH 0504/1670] ci/qa: Also install glibc debug symbols They may be useful to detect NSS issues --- .github/workflows/qa.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/qa.yaml b/.github/workflows/qa.yaml index 6ad1c50a44..7590751dc7 100644 --- a/.github/workflows/qa.yaml +++ b/.github/workflows/qa.yaml @@ -162,11 +162,11 @@ jobs: sudo ln -s /usr/share/apparmor/extra-profiles/bwrap-userns-restrict /etc/apparmor.d/ sudo apparmor_parser /etc/apparmor.d/bwrap-userns-restrict - - name: Install PAM and GLib debug symbols + - name: Install glibc, PAM and GLib debug symbols continue-on-error: true run: | set -eu - sudo apt-get install ubuntu-dbgsym-keyring -y + sudo apt-get install -y ubuntu-dbgsym-keyring libc6-dbg echo "deb http://ddebs.ubuntu.com $(lsb_release -cs) main restricted universe multiverse deb http://ddebs.ubuntu.com $(lsb_release -cs)-updates main restricted universe multiverse deb http://ddebs.ubuntu.com $(lsb_release -cs)-proposed main restricted universe multiverse" | \ From 89125686515a36bc4081d52a338568b803c5400c Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 9 Jul 2025 15:50:52 +0200 Subject: [PATCH 0505/1670] Return a custom error when a IsAuthenticated call was canceled This allows the caller to detect this case and handle it differently than unexpected error (by not logging an error message). --- internal/dbusservice/methods.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/internal/dbusservice/methods.go b/internal/dbusservice/methods.go index 0747ee87c8..26846a6da9 100644 --- a/internal/dbusservice/methods.go +++ b/internal/dbusservice/methods.go @@ -2,6 +2,7 @@ package dbusservice import ( "context" + "errors" "github.com/godbus/dbus/v5" "github.com/ubuntu/authd/log" @@ -37,6 +38,9 @@ func (s *Service) SelectAuthenticationMode(sessionID, authenticationModeName str // IsAuthenticated is the method through which the broker and the daemon will communicate once dbusInterface.IsAuthenticated is called. func (s *Service) IsAuthenticated(sessionID, authenticationData string) (access, data string, dbusErr *dbus.Error) { access, data, err := s.broker.IsAuthenticated(sessionID, authenticationData) + if errors.Is(err, context.Canceled) { + return access, data, makeCanceledError() + } if err != nil { log.Warningf(context.Background(), "IsAuthenticated error: %v", err) return "", "", dbus.MakeFailedError(err) @@ -68,3 +72,8 @@ func (s *Service) UserPreCheck(username string) (userinfo string, dbusErr *dbus. } return userinfo, nil } + +// makeCanceledError creates a dbus.Error for a canceled operation. +func makeCanceledError() *dbus.Error { + return &dbus.Error{Name: "com.ubuntu.authd.Canceled"} +} From ca5ea4c46a3ac4d328a8b982ff162ecff6df0e61 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 27 Jun 2025 18:43:59 +0200 Subject: [PATCH 0506/1670] Avoid printing each "error" returned by a gRPC method authd spams the journal with "rpc error: code = NotFound desc =" warning messages, which are printed each time an NSS request is sent for a user or group which authd doesn't know about (which apparently happens quite regularly during normal system operation). We decided to fix this by stopping logging warning messages for each error returned by a gRPC method, and instead log errors which we actually want to log in the method implementations before returning. --- internal/services/errmessages/redactor.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/services/errmessages/redactor.go b/internal/services/errmessages/redactor.go index 217bdfe956..c0881458f1 100644 --- a/internal/services/errmessages/redactor.go +++ b/internal/services/errmessages/redactor.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" - "github.com/ubuntu/authd/log" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -18,7 +17,6 @@ import ( func RedactErrorInterceptor(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { m, err := handler(ctx, req) if err != nil { - log.Warning(context.TODO(), err.Error()) var redactedError ToDisplayError if !errors.As(err, &redactedError) { return m, err From 953d9ef0a7bd6e1e33353ccee5d46d10ecb3cc11 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 27 Jun 2025 20:02:55 +0200 Subject: [PATCH 0507/1670] Fix comment --- internal/services/pam/pam.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/services/pam/pam.go b/internal/services/pam/pam.go index 6125eb6731..fa295e9f4d 100644 --- a/internal/services/pam/pam.go +++ b/internal/services/pam/pam.go @@ -105,7 +105,7 @@ func (s Service) GetPreviousBroker(ctx context.Context, req *authd.GPBRequest) ( return &authd.GPBResponse{}, nil } - // Database the broker which should be used for the user, so that we don't have to query the database again next time - + // Cache the broker which should be used for the user, so that we don't have to query the database again next time - // except if the broker is the local broker, because then the decision to use the local broker should be made each // time the user tries to log in, based on whether the user is provided by any other NSS service. if brokerID == brokers.LocalBrokerName { From b19450d2b431f703909806436cc7c4cef501f914 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 26 Jun 2025 13:14:50 +0200 Subject: [PATCH 0508/1670] users/locking: Improve error messages --- internal/users/locking/locking.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/users/locking/locking.go b/internal/users/locking/locking.go index 70873ace45..3fefa7ee77 100644 --- a/internal/users/locking/locking.go +++ b/internal/users/locking/locking.go @@ -20,10 +20,10 @@ var ( var ( // ErrLock is the error when locking the database fails. - ErrLock = errors.New("failed to lock the shadow password database") + ErrLock = errors.New("failed to lock the system's user database") // ErrUnlock is the error when unlocking the database fails. - ErrUnlock = errors.New("failed to unlock the shadow password database") + ErrUnlock = errors.New("failed to unlock the system's user database") // ErrLockTimeout is the error when unlocking the database fails because of timeout. ErrLockTimeout = fmt.Errorf("%w: timeout", ErrLock) From 4405bdcbf8d5841e751e67c89d7c10f0fd0668f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 20 Jun 2025 20:23:15 +0200 Subject: [PATCH 0509/1670] users/locking: Add debug data to the locking machinery It can be useful to check that we are doing things under a locked or unlocked state --- internal/users/locking/locking_c.go | 4 ++++ internal/users/locking/testutils.go | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/internal/users/locking/locking_c.go b/internal/users/locking/locking_c.go index c122689d3d..af8b45b3c0 100644 --- a/internal/users/locking/locking_c.go +++ b/internal/users/locking/locking_c.go @@ -6,10 +6,12 @@ package userslocking import "C" import ( + "context" "errors" "fmt" "github.com/ubuntu/authd/internal/errno" + "github.com/ubuntu/authd/log" ) // writeLock is the default locking implementation. @@ -18,6 +20,7 @@ func writeLock() error { defer errno.Unlock() if C.lckpwdf() == 0 { + log.Debug(context.Background(), "glibc lckpwdf: Local entries locked!") return nil } @@ -39,6 +42,7 @@ func writeUnlock() error { defer errno.Unlock() if C.ulckpwdf() == 0 { + log.Debug(context.Background(), "glibc lckpwdf: Local entries unlocked!") return nil } diff --git a/internal/users/locking/testutils.go b/internal/users/locking/testutils.go index 2a3d0c7eba..edef31fc88 100644 --- a/internal/users/locking/testutils.go +++ b/internal/users/locking/testutils.go @@ -1,9 +1,11 @@ package userslocking import ( + "context" "sync/atomic" "github.com/ubuntu/authd/internal/testsdetection" + "github.com/ubuntu/authd/log" ) var overrideLocked atomic.Bool @@ -21,6 +23,8 @@ func Z_ForTests_OverrideLocking() { if !overrideLocked.CompareAndSwap(false, true) { return ErrLock } + + log.Debug(context.Background(), "TestOverride: Local entries locked!") return nil } @@ -28,6 +32,8 @@ func Z_ForTests_OverrideLocking() { if !overrideLocked.CompareAndSwap(true, false) { return ErrUnlock } + + log.Debug(context.Background(), "TestOverride: Local entries unlocked!") return nil } } From 48c1c52bbda87984c7da3bc7d73dbfb57f27c914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 20 Jun 2025 15:24:00 +0200 Subject: [PATCH 0510/1670] users/locking/bwrap-test: Fix potential races By calling Run we may act on the locker process that we are then checking in different goroutines, so let's just split the call and keep a local track of it --- internal/users/locking/locking_bwrap_test.go | 36 +++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/internal/users/locking/locking_bwrap_test.go b/internal/users/locking/locking_bwrap_test.go index 2179345d34..69f941405f 100644 --- a/internal/users/locking/locking_bwrap_test.go +++ b/internal/users/locking/locking_bwrap_test.go @@ -174,23 +174,27 @@ func TestLockingLockedDatabase(t *testing.T) { cmd := exec.CommandContext(ctx, testLockerUtility) t.Logf("Running command: %s", cmd.Args) + err = cmd.Start() + require.NoError(t, err, "Setup: Locker utility should start") + lockerProcess := cmd.Process + lockerExited := make(chan error) writeLockExited := make(chan error) t.Cleanup(func() { cancel() - syscall.Kill(cmd.Process.Pid, syscall.SIGKILL) + syscall.Kill(lockerProcess.Pid, syscall.SIGKILL) require.Error(t, <-lockerExited, "Stopping locking process") require.NoError(t, <-writeLockExited, "Final locking") require.NoError(t, userslocking.WriteUnlock(), "Final unlocking") }) go func() { - lockerExited <- cmd.Run() + lockerExited <- cmd.Wait() }() select { case <-time.After(1 * time.Second): - t.Cleanup(func() { cmd.Process.Kill() }) + t.Cleanup(func() { lockerProcess.Kill() }) // If we're time-outing: it's fine, it means the test-locker process is running case err := <-lockerExited: require.NoError(t, err, "test locker should not have failed") @@ -232,21 +236,25 @@ func TestLockingLockedDatabaseFailsAfterTimeout(t *testing.T) { cmd := exec.CommandContext(ctx, testLockerUtility) t.Logf("Running command: %s", cmd.Args) + err := cmd.Start() + require.NoError(t, err, "Setup: Locker utility should start") + lockerProcess := cmd.Process + lockerExited := make(chan error) t.Cleanup(func() { cancel() - syscall.Kill(cmd.Process.Pid, syscall.SIGKILL) + syscall.Kill(lockerProcess.Pid, syscall.SIGKILL) require.Error(t, <-lockerExited, "Stopping locking process") require.NoError(t, userslocking.WriteUnlock(), "Final unlocking") }) go func() { - lockerExited <- cmd.Run() + lockerExited <- cmd.Wait() }() select { - case <-time.After(2 * time.Second): - t.Cleanup(func() { cmd.Process.Kill() }) + case <-time.After(1 * time.Second): + t.Cleanup(func() { lockerProcess.Kill() }) // If we're time-outing: it's fine, it means the test-locker process is running case err := <-lockerExited: require.NoError(t, err, "test locker should not have failed") @@ -258,7 +266,7 @@ func TestLockingLockedDatabaseFailsAfterTimeout(t *testing.T) { writeLockExited <- userslocking.WriteLock() }() - err := <-writeLockExited + err = <-writeLockExited t.Log("Done waiting for lock!") require.ErrorIs(t, err, userslocking.ErrLock) require.ErrorIs(t, err, userslocking.ErrLockTimeout) @@ -275,16 +283,20 @@ func TestLockingLockedDatabaseWorksAfterUnlock(t *testing.T) { lockerCmd := exec.CommandContext(ctx, testLockerUtility) t.Logf("Running command: %s", lockerCmd.Args) + err := lockerCmd.Start() + require.NoError(t, err, "Setup: Locker utility should start") + lockerProcess := lockerCmd.Process + lockerExited := make(chan error) go func() { - lockerExited <- lockerCmd.Run() + lockerExited <- lockerCmd.Wait() }() select { case <-time.After(1 * time.Second): // If we're time-outing: it's fine, it means the test-locker process is // still running and holding the lock. - t.Cleanup(func() { lockerCmd.Process.Kill() }) + t.Cleanup(func() { lockerProcess.Kill() }) case err := <-lockerExited: require.NoError(t, err, "test locker should not have failed") } @@ -317,12 +329,12 @@ func TestLockingLockedDatabaseWorksAfterUnlock(t *testing.T) { t.Log("Killing locking process") cancel() - syscall.Kill(lockerCmd.Process.Pid, syscall.SIGKILL) + syscall.Kill(lockerProcess.Pid, syscall.SIGKILL) // Do not wait for the locker being exited yet, so that we can ensure that // our function call wait is over. t.Log("We should get the lock now!") - err := <-writeLockExited + err = <-writeLockExited require.NoError(t, err, "We should have the lock now") t.Log("Ensure locking process has been stopped!") From 9a4f467447169efe0d7a598ef5782a16f59bfb58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 25 Jun 2025 13:26:49 +0000 Subject: [PATCH 0511/1670] users/locking/tests: Check unlock error on cleanup --- internal/users/locking/locking_bwrap_test.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/internal/users/locking/locking_bwrap_test.go b/internal/users/locking/locking_bwrap_test.go index 69f941405f..8f987c3382 100644 --- a/internal/users/locking/locking_bwrap_test.go +++ b/internal/users/locking/locking_bwrap_test.go @@ -76,7 +76,10 @@ testgroup:x:1001:testuser` err = userslocking.WriteLock() require.NoError(t, err, "Locking once it is allowed") - t.Cleanup(func() { userslocking.WriteUnlock() }) + t.Cleanup(func() { + err := userslocking.WriteUnlock() + require.NoError(t, err, "Unlocking should be allowed") + }) output, err := runCmd(t, "getent", "group", "root", "testgroup") require.NoError(t, err, "Reading should be allowed") @@ -110,7 +113,11 @@ func TestLockAndLockAgainGroupFileOverridden(t *testing.T) { err = userslocking.WriteLock() require.NoError(t, err, "Locking once it is allowed") - t.Cleanup(func() { userslocking.WriteUnlock() }) + t.Cleanup(func() { + // Ignore the error here, as it's expected to return an error if the + // WriteUnlock further below is called first. + _ = userslocking.WriteUnlock() + }) gPasswdExited := make(chan error) go func() { From e86b65424bc77993d5c3cd054c982c5a6b82c4f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 18 Jun 2025 21:52:28 +0200 Subject: [PATCH 0512/1670] users/locking/testutils: Return a clearer error when override lock fails --- internal/users/locking/testutils.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/users/locking/testutils.go b/internal/users/locking/testutils.go index edef31fc88..9e51f3c2a4 100644 --- a/internal/users/locking/testutils.go +++ b/internal/users/locking/testutils.go @@ -2,6 +2,7 @@ package userslocking import ( "context" + "fmt" "sync/atomic" "github.com/ubuntu/authd/internal/testsdetection" @@ -21,7 +22,7 @@ func Z_ForTests_OverrideLocking() { writeLockImpl = func() error { if !overrideLocked.CompareAndSwap(false, true) { - return ErrLock + return fmt.Errorf("%w: already locked", ErrLock) } log.Debug(context.Background(), "TestOverride: Local entries locked!") @@ -30,7 +31,7 @@ func Z_ForTests_OverrideLocking() { writeUnlockImpl = func() error { if !overrideLocked.CompareAndSwap(true, false) { - return ErrUnlock + return fmt.Errorf("%w: already unlocked", ErrUnlock) } log.Debug(context.Background(), "TestOverride: Local entries unlocked!") From 4f15d8ef41e1f361d4bf44e387bc13dfaf41c69d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 17 Jun 2025 21:35:06 +0200 Subject: [PATCH 0513/1670] users/locking: Add test utility functions with automatic cleanups Add functions that automatically reset the state when a test is finished, without having to repeat the same multiple times. At the same time we still need to have the non-cleanup cases for integration tests --- cmd/authd/daemon/migration_test.go | 3 +-- internal/users/db/db_test.go | 24 +++++++------------- internal/users/locking/locking_bwrap_test.go | 3 +-- internal/users/locking/locking_test.go | 3 +-- internal/users/locking/testutils.go | 16 +++++++++++++ 5 files changed, 27 insertions(+), 22 deletions(-) diff --git a/cmd/authd/daemon/migration_test.go b/cmd/authd/daemon/migration_test.go index 4677b0da49..3dff5d6e21 100644 --- a/cmd/authd/daemon/migration_test.go +++ b/cmd/authd/daemon/migration_test.go @@ -127,8 +127,7 @@ func TestMaybeMigrateBBoltToSQLite(t *testing.T) { // Make the userslocking package use a locking mechanism which doesn't // require root privileges. - userslocking.Z_ForTests_OverrideLocking() - t.Cleanup(userslocking.Z_ForTests_RestoreLocking) + userslocking.Z_ForTests_OverrideLockingWithCleanup(t) testCases := map[string]struct { bboltExists bool diff --git a/internal/users/db/db_test.go b/internal/users/db/db_test.go index cc17c73460..bc4b2a5bea 100644 --- a/internal/users/db/db_test.go +++ b/internal/users/db/db_test.go @@ -135,8 +135,7 @@ LocalTestGroup:x:12345:TestUser t.Cleanup(func() { db.SetGroupFile(origGroupFile) }) // Make the userutils package to use test locking for the group file - userslocking.Z_ForTests_OverrideLocking() - t.Cleanup(userslocking.Z_ForTests_RestoreLocking) + userslocking.Z_ForTests_OverrideLockingWithCleanup(t) // Run the migrations m, err := db.New(dbDir) @@ -178,8 +177,7 @@ func TestMigrationToLowercaseUserAndGroupNamesEmptyDB(t *testing.T) { t.Cleanup(func() { db.SetGroupFile(origGroupFile) }) // Make the userutils package to use test locking for the group file - userslocking.Z_ForTests_OverrideLocking() - t.Cleanup(userslocking.Z_ForTests_RestoreLocking) + userslocking.Z_ForTests_OverrideLockingWithCleanup(t) // Run the migrations m, err := db.New(dbDir) @@ -220,8 +218,7 @@ func TestMigrationToLowercaseUserAndGroupNamesAlreadyUpdated(t *testing.T) { t.Cleanup(func() { db.SetGroupFile(origGroupFile) }) // Make the userutils package to use test locking for the group file - userslocking.Z_ForTests_OverrideLocking() - t.Cleanup(userslocking.Z_ForTests_RestoreLocking) + userslocking.Z_ForTests_OverrideLockingWithCleanup(t) // Run the migrations m, err := db.New(dbDir) @@ -266,8 +263,7 @@ func TestMigrationToLowercaseUserAndGroupNamesWithSymlinkedGroupFile(t *testing. t.Cleanup(func() { db.SetGroupFile(origGroupFile) }) // Make the userutils package to use test locking for the group file - userslocking.Z_ForTests_OverrideLocking() - t.Cleanup(userslocking.Z_ForTests_RestoreLocking) + userslocking.Z_ForTests_OverrideLockingWithCleanup(t) // Run the migrations m, err := db.New(dbDir) @@ -319,8 +315,7 @@ func TestMigrationToLowercaseUserAndGroupNamesWithPreviousBackup(t *testing.T) { require.NoError(t, err, "Setup: could not create group file") // Make the userutils package to use test locking for the group file - userslocking.Z_ForTests_OverrideLocking() - t.Cleanup(userslocking.Z_ForTests_RestoreLocking) + userslocking.Z_ForTests_OverrideLockingWithCleanup(t) // Run the migrations m, err := db.New(dbDir) @@ -372,8 +367,7 @@ func TestMigrationToLowercaseUserAndGroupNamesWithSymlinkedPreviousBackup(t *tes require.NoError(t, err, "Setup: could not create group file backup symlink") // Make the userutils package to use test locking for the group file - userslocking.Z_ForTests_OverrideLocking() - t.Cleanup(userslocking.Z_ForTests_RestoreLocking) + userslocking.Z_ForTests_OverrideLockingWithCleanup(t) // Run the migrations m, err := db.New(dbDir) @@ -424,8 +418,7 @@ func TestMigrationToLowercaseUserAndGroupNamesFails(t *testing.T) { t.Cleanup(func() { db.SetGroupFile(origGroupFile) }) // Make the userutils package to use test locking for the group file - userslocking.Z_ForTests_OverrideLocking() - t.Cleanup(userslocking.Z_ForTests_RestoreLocking) + userslocking.Z_ForTests_OverrideLockingWithCleanup(t) // Run the migrations m, err := db.New(dbDir) @@ -468,8 +461,7 @@ func TestMigrationToLowercaseUserAndGroupNamesWithBackupFailure(t *testing.T) { require.NoError(t, err, "Setup: touching a file in %q", db.GroupFileBackupPath()) // Make the userutils package to use test locking for the group file - userslocking.Z_ForTests_OverrideLocking() - t.Cleanup(userslocking.Z_ForTests_RestoreLocking) + userslocking.Z_ForTests_OverrideLockingWithCleanup(t) // Run the migrations m, err := db.New(dbDir) diff --git a/internal/users/locking/locking_bwrap_test.go b/internal/users/locking/locking_bwrap_test.go index 8f987c3382..9a561d6265 100644 --- a/internal/users/locking/locking_bwrap_test.go +++ b/internal/users/locking/locking_bwrap_test.go @@ -137,8 +137,7 @@ func TestLockAndLockAgainGroupFileOverridden(t *testing.T) { } func TestUnlockUnlockedOverridden(t *testing.T) { - userslocking.Z_ForTests_OverrideLocking() - t.Cleanup(userslocking.Z_ForTests_RestoreLocking) + userslocking.Z_ForTests_OverrideLockingWithCleanup(t) err := userslocking.WriteUnlock() require.ErrorIs(t, err, userslocking.ErrUnlock, "Unlocking unlocked should not be allowed") diff --git a/internal/users/locking/locking_test.go b/internal/users/locking/locking_test.go index e21ea2aee6..e100f62748 100644 --- a/internal/users/locking/locking_test.go +++ b/internal/users/locking/locking_test.go @@ -118,8 +118,7 @@ func compileLockerBinary(t *testing.T) string { func TestUsersLockingOverride(t *testing.T) { // This cannot be parallel. - userslocking.Z_ForTests_OverrideLocking() - t.Cleanup(userslocking.Z_ForTests_RestoreLocking) + userslocking.Z_ForTests_OverrideLockingWithCleanup(t) err := userslocking.WriteLock() require.NoError(t, err, "Locking should be allowed") diff --git a/internal/users/locking/testutils.go b/internal/users/locking/testutils.go index 9e51f3c2a4..66d95ce807 100644 --- a/internal/users/locking/testutils.go +++ b/internal/users/locking/testutils.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "sync/atomic" + "testing" "github.com/ubuntu/authd/internal/testsdetection" "github.com/ubuntu/authd/log" @@ -39,6 +40,21 @@ func Z_ForTests_OverrideLocking() { } } +// Z_ForTests_OverrideLockingWithCleanup is a function to override the locking +// functions for testing purposes. +// It simulates the real behavior but without actual file locking. +// This implicitly calls [Z_ForTests_RestoreLocking] once the test is completed. +// +// nolint:revive,nolintlint // We want to use underscores in the function name here. +func Z_ForTests_OverrideLockingWithCleanup(t *testing.T) { + t.Helper() + + testsdetection.MustBeTesting() + + Z_ForTests_OverrideLocking() + t.Cleanup(Z_ForTests_RestoreLocking) +} + // Z_ForTests_RestoreLocking restores the locking overridden done by // [Z_ForTests_OverrideLocking]. // From 56fec3615be0497ddbe7634d4bce3264622d0baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 22 Apr 2025 22:18:29 +0200 Subject: [PATCH 0514/1670] users/locking/testutils: Add override simulating being locked by another process When another process has the lock the lock request is waiting, so ensure that we behave in the same way --- internal/users/locking/locking.go | 8 +- internal/users/locking/locking_test.go | 32 +++++++ internal/users/locking/testutils.go | 115 ++++++++++++++++++++++++- 3 files changed, 150 insertions(+), 5 deletions(-) diff --git a/internal/users/locking/locking.go b/internal/users/locking/locking.go index 3fefa7ee77..408c2704ce 100644 --- a/internal/users/locking/locking.go +++ b/internal/users/locking/locking.go @@ -16,6 +16,10 @@ import ( var ( writeLockImpl = writeLock writeUnlockImpl = writeUnlock + + // maxWait is the maximum wait time for a lock to happen. + // We mimic the libc behavior, in case we don't get SIGALRM'ed. + maxWait = 16 * time.Second ) var ( @@ -38,6 +42,8 @@ var ( // lock is already hold by this process. func WriteLock() error { done := make(chan error) + writeLockImpl := writeLockImpl + go func() { done <- writeLockImpl() }() @@ -47,7 +53,7 @@ func WriteLock() error { // because alarms are handled by go runtime, so do it manually here by // failing if "lock not obtained within 15 seconds" as per lckpwdf.3. // Keep this in sync with what lckpwdf does, adding an extra second. - case <-time.After(16 * time.Second): + case <-time.After(maxWait): return ErrLockTimeout case err := <-done: return err diff --git a/internal/users/locking/locking_test.go b/internal/users/locking/locking_test.go index e100f62748..8f7a0113fa 100644 --- a/internal/users/locking/locking_test.go +++ b/internal/users/locking/locking_test.go @@ -3,12 +3,14 @@ package userslocking_test import ( + "context" "fmt" "os/exec" "path/filepath" "regexp" "strings" "testing" + "time" "github.com/stretchr/testify/require" "github.com/ubuntu/authd/internal/fileutils" @@ -132,3 +134,33 @@ func TestUsersLockingOverride(t *testing.T) { err = userslocking.WriteUnlock() require.ErrorIs(t, err, userslocking.ErrUnlock, "Unlocking unlocked should not be allowed") } + +func TestUsersLockingOverrideAsLockedExternally(t *testing.T) { + // This cannot be parallel. + userslocking.Z_ForTests_OverrideLockingAsLockedExternally(t, context.Background()) + + lockingExited := make(chan error) + go func() { + lockingExited <- userslocking.WriteLock() + }() + + select { + case <-time.After(1 * time.Second): + // If we're time-outing: it's fine, it means we were locked! + case err := <-lockingExited: + t.Errorf("We should have not been exited, but we did with error %v", err) + t.FailNow() + } + + err := userslocking.WriteUnlock() + require.NoError(t, err, "Unlocking should be allowed") + + err = <-lockingExited + require.NoError(t, err, "Previous concurrent locking should have been allowed now") + + err = userslocking.WriteUnlock() + require.NoError(t, err, "Unlocking should be allowed") + + err = userslocking.WriteUnlock() + require.ErrorIs(t, err, userslocking.ErrUnlock, "Unlocking unlocked should not be allowed") +} diff --git a/internal/users/locking/testutils.go b/internal/users/locking/testutils.go index 66d95ce807..6efa41aeb9 100644 --- a/internal/users/locking/testutils.go +++ b/internal/users/locking/testutils.go @@ -3,14 +3,23 @@ package userslocking import ( "context" "fmt" + "sync" "sync/atomic" "testing" + "time" + "github.com/stretchr/testify/require" "github.com/ubuntu/authd/internal/testsdetection" "github.com/ubuntu/authd/log" ) -var overrideLocked atomic.Bool +var ( + overrideLocked atomic.Bool + + overriddenMu sync.Mutex + overriddenWriteLockImpl []func() error + overriddenWriteUnlockImpl []func() error +) // Z_ForTests_OverrideLocking is a function to override the locking functions // for testing purposes. @@ -21,6 +30,10 @@ var overrideLocked atomic.Bool func Z_ForTests_OverrideLocking() { testsdetection.MustBeTesting() + overriddenMu.Lock() + defer overriddenMu.Unlock() + + overriddenWriteLockImpl = append(overriddenWriteLockImpl, writeLockImpl) writeLockImpl = func() error { if !overrideLocked.CompareAndSwap(false, true) { return fmt.Errorf("%w: already locked", ErrLock) @@ -30,6 +43,7 @@ func Z_ForTests_OverrideLocking() { return nil } + overriddenWriteUnlockImpl = append(overriddenWriteUnlockImpl, writeUnlockImpl) writeUnlockImpl = func() error { if !overrideLocked.CompareAndSwap(true, false) { return fmt.Errorf("%w: already unlocked", ErrUnlock) @@ -55,13 +69,106 @@ func Z_ForTests_OverrideLockingWithCleanup(t *testing.T) { t.Cleanup(Z_ForTests_RestoreLocking) } +// Z_ForTests_OverrideLockingAsLockedExternally simulates a scenario where the +// user database is locked by an external process. +// +// When called, it marks the user database as locked, causing any subsequent +// locking attempts by authd (via [WriteLock]) to block until the provided +// context is cancelled. +// +// This does not use real file locking. The lock can be released either +// by cancelling the context or by calling [WriteUnlock]. After the test, +// [Z_ForTests_RestoreLocking] is called automatically to restore normal behavior. +// +// nolint:revive,nolintlint // We want to use underscores in the function name here. +func Z_ForTests_OverrideLockingAsLockedExternally(t *testing.T, ctx context.Context) { + t.Helper() + + testsdetection.MustBeTesting() + + overriddenMu.Lock() + defer overriddenMu.Unlock() + + t.Cleanup(Z_ForTests_RestoreLocking) + + // This channel is used to synchronize the lock and unlock operations. + // It uses a buffer of size 1 so that it can be locked exactly once + // and then blocks until the unlock operation is called. + lockCh := make(chan struct{}, 1) + + overriddenWriteLockImpl = append(overriddenWriteLockImpl, writeLockImpl) + writeLockImpl = func() error { + for { + select { + case lockCh <- struct{}{}: + log.Debug(ctx, "TestOverrideExternallyLocked: Local entries external lock released!") + case <-time.After(maxWait): + return ErrLockTimeout + } + + if overrideLocked.CompareAndSwap(false, true) { + log.Debug(ctx, "TestOverrideExternallyLocked: Local entries locked!") + break + } + } + return nil + } + + overriddenWriteUnlockImpl = append(overriddenWriteUnlockImpl, writeUnlockImpl) + writeUnlockImpl = func() error { + if !overrideLocked.CompareAndSwap(true, false) { + return ErrUnlock + } + + <-lockCh + log.Debug(ctx, "TestOverrideExternallyLocked: Local entries unlocked!") + return nil + } + + done := atomic.Bool{} + writeUnlockImpl := writeUnlockImpl + cleanup := func() { + if !done.CompareAndSwap(false, true) { + return + } + if !overrideLocked.Load() { + return + } + err := writeUnlockImpl() + require.NoError(t, err, "Unlocking should be allowed") + } + + t.Cleanup(cleanup) + + err := writeLockImpl() + require.NoError(t, err, "Locking should be allowed") + + go func() { + <-ctx.Done() + cleanup() + }() +} + // Z_ForTests_RestoreLocking restores the locking overridden done by -// [Z_ForTests_OverrideLocking]. +// [Z_ForTests_OverrideLocking] or [Z_ForTests_OverrideLockingAsLockedExternally]. // // nolint:revive,nolintlint // We want to use underscores in the function name here. func Z_ForTests_RestoreLocking() { testsdetection.MustBeTesting() - writeLockImpl = writeLock - writeUnlockImpl = writeUnlock + if overrideLocked.Load() { + panic("Lock has not been released before restoring!") + } + + overriddenMu.Lock() + defer overriddenMu.Unlock() + + popLast := func(l []func() error) ([]func() error, func() error) { + n := len(l) - 1 + v, l := l[n], l[:n] + return l, v + } + + overriddenWriteLockImpl, writeLockImpl = popLast(overriddenWriteLockImpl) + overriddenWriteUnlockImpl, writeUnlockImpl = popLast(overriddenWriteUnlockImpl) } From 846b18dae5c63f1908bd2c65579a4d43410b103e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 17 Jun 2025 16:16:00 +0200 Subject: [PATCH 0515/1670] users/locking: Add support for recursive locking During authd execution we may need to be able to lock and unlock the pwd database multiple times and from different threads, however implies blocking at times. We can avoid this by using recursive locking instead so that we can both ensure that WriteLock is a blocking operation, if we need to, but at the same time we can add further locks to avoid lock/unlock dances --- internal/users/locking/locking.go | 74 ++++ internal/users/locking/locking_bwrap_test.go | 423 +++++++++++-------- internal/users/locking/locking_test.go | 126 ++++++ 3 files changed, 446 insertions(+), 177 deletions(-) diff --git a/internal/users/locking/locking.go b/internal/users/locking/locking.go index 408c2704ce..32c22d4747 100644 --- a/internal/users/locking/locking.go +++ b/internal/users/locking/locking.go @@ -10,6 +10,7 @@ package userslocking import ( "errors" "fmt" + "sync" "time" ) @@ -17,6 +18,9 @@ var ( writeLockImpl = writeLock writeUnlockImpl = writeUnlock + writeLocksCount uint64 + writeLocksCountMu sync.RWMutex + // maxWait is the maximum wait time for a lock to happen. // We mimic the libc behavior, in case we don't get SIGALRM'ed. maxWait = 16 * time.Second @@ -41,6 +45,17 @@ var ( // database in write mode, while it will return an error if called while the // lock is already hold by this process. func WriteLock() error { + writeLocksCountMu.RLock() + defer writeLocksCountMu.RUnlock() + + if writeLocksCount > 0 { + return fmt.Errorf("%w: mixing recursive and normal locking", ErrLock) + } + + return writeLockInternal() +} + +func writeLockInternal() error { done := make(chan error) writeLockImpl := writeLockImpl @@ -65,5 +80,64 @@ func WriteLock() error { // As soon as this function is called all the other waiting processes will be // allowed to take the lock. func WriteUnlock() error { + writeLocksCountMu.RLock() + defer writeLocksCountMu.RUnlock() + + if writeLocksCount > 0 { + return fmt.Errorf("%w: mixing recursive and normal locking", ErrUnlock) + } + return writeUnlockImpl() } + +// WriteRecLock locks the system's user database for writing. +// While the lock is held, all other processes trying to lock the database +// will block until the lock is released (or a timeout of 15 seconds is reached). +// Note that this implies that if some other process owns the lock when +// this function is called, it will block until the other process releases the +// lock. +// +// This function is recursive, it can be called multiple times without +// deadlocking even by different goroutines - the system user database is locked +// only once, when the reference count is 0, else it just increments the +// reference count. +// +// [WriteRecUnlock] must be called the same number of times as [WriteRecLock] to +// release the lock. +func WriteRecLock() error { + writeLocksCountMu.Lock() + defer writeLocksCountMu.Unlock() + + if writeLocksCount == 0 { + if err := writeLockInternal(); err != nil { + return err + } + } + + writeLocksCount++ + return nil +} + +// WriteRecUnlock decreases the reference count of the lock acquired by. +// [WriteRecLock]. If the reference count reaches 0, it releases the lock +// on the system's user database. +func WriteRecUnlock() error { + writeLocksCountMu.Lock() + defer writeLocksCountMu.Unlock() + + if writeLocksCount == 0 { + return fmt.Errorf("%w: no locks found", ErrUnlock) + } + + if writeLocksCount > 1 { + writeLocksCount-- + return nil + } + + if err := writeUnlockImpl(); err != nil { + return err + } + + writeLocksCount-- + return nil +} diff --git a/internal/users/locking/locking_bwrap_test.go b/internal/users/locking/locking_bwrap_test.go index 9a561d6265..1c73459422 100644 --- a/internal/users/locking/locking_bwrap_test.go +++ b/internal/users/locking/locking_bwrap_test.go @@ -4,6 +4,7 @@ package userslocking_test import ( "context" + "maps" "os" "os/exec" "path/filepath" @@ -16,6 +17,34 @@ import ( userslocking "github.com/ubuntu/authd/internal/users/locking" ) +var lockingCases = map[string]struct { + WriteLock func() error + WriteUnlock func() error +}{ + "NormalLocking": { + WriteLock: userslocking.WriteLock, + WriteUnlock: userslocking.WriteUnlock, + }, + "RecLocking": { + WriteLock: func() error { + for i := 0; i < 5; i++ { + if err := userslocking.WriteRecLock(); err != nil { + return err + } + } + return nil + }, + WriteUnlock: func() error { + for i := 0; i < 5; i++ { + if err := userslocking.WriteRecUnlock(); err != nil { + return err + } + } + return nil + }, + }, +} + func TestLockAndWriteUnlock(t *testing.T) { require.Zero(t, os.Geteuid(), "Not root") @@ -25,42 +54,46 @@ func TestLockAndWriteUnlock(t *testing.T) { err := os.WriteFile(groupFile, []byte("root:x:0:\n"+newGroupContents), 0644) require.NoError(t, err, "Writing group file") - // Try using gpasswd to modify the group file. This should succeed, because - // the group file is not locked. - output, err := runGPasswd(t, "--add", "root", "testgroup") - require.NoError(t, err, "Output: %s", output) - - // Lock the group file - err = userslocking.WriteLock() - require.NoError(t, err, "Locking database") - - output, err = runCmd(t, "getent", "group", "testgroup") - require.NoError(t, err, "Output: %s", output) - require.Equal(t, output, newGroupContents+",root", "Group not found") - - // Try using gpasswd to modify the group file. This should fail, because - // the group file is locked. - output, err = runGPasswd(t, "--delete", "root", "testgroup") - require.Error(t, err, output) - require.Contains(t, output, "gpasswd: cannot lock /etc/group") - - // Reading is allowed when locked. - output, err = runCmd(t, "getent", "group", "testgroup") - require.NoError(t, err, "Output: %s", output) - require.Equal(t, output, newGroupContents+",root", "Group not found") - - // Unlock the group file - err = userslocking.WriteUnlock() - require.NoError(t, err, "Unlocking database") - - // Try using gpasswd to modify the group file again. This should succeed, - // because the group file is unlocked. - output, err = runGPasswd(t, "--delete", "root", "testgroup") - require.NoError(t, err, "Output: %s", output) - - output, err = runCmd(t, "getent", "group", "testgroup") - require.NoError(t, err, "Output: %s", output) - require.Equal(t, output, newGroupContents, "Group not found") + for name, f := range maps.Clone(lockingCases) { + t.Run(name, func(t *testing.T) { + // Try using gpasswd to modify the group file. This should succeed, because + // the group file is not locked. + output, err := runGPasswd(t, "--add", "root", "testgroup") + require.NoError(t, err, "Output: %s", output) + + // Lock the group file + err = f.WriteLock() + require.NoError(t, err, "Locking database") + + output, err = runCmd(t, "getent", "group", "testgroup") + require.NoError(t, err, "Output: %s", output) + require.Equal(t, output, newGroupContents+",root", "Group not found") + + // Try using gpasswd to modify the group file. This should fail, because + // the group file is locked. + output, err = runGPasswd(t, "--delete", "root", "testgroup") + require.Error(t, err, output) + require.Contains(t, output, "gpasswd: cannot lock /etc/group") + + // Reading is allowed when locked. + output, err = runCmd(t, "getent", "group", "testgroup") + require.NoError(t, err, "Output: %s", output) + require.Equal(t, output, newGroupContents+",root", "Group not found") + + // Unlock the group file + err = f.WriteUnlock() + require.NoError(t, err, "Unlocking database") + + // Try using gpasswd to modify the group file again. This should succeed, + // because the group file is unlocked. + output, err = runGPasswd(t, "--delete", "root", "testgroup") + require.NoError(t, err, "Output: %s", output) + + output, err = runCmd(t, "getent", "group", "testgroup") + require.NoError(t, err, "Output: %s", output) + require.Equal(t, output, newGroupContents, "Group not found") + }) + } } func TestReadWhileLocked(t *testing.T) { @@ -74,16 +107,20 @@ testgroup:x:1001:testuser` err := os.WriteFile(groupFile, []byte(groupContents), 0644) require.NoError(t, err, "Writing group file") - err = userslocking.WriteLock() - require.NoError(t, err, "Locking once it is allowed") - t.Cleanup(func() { - err := userslocking.WriteUnlock() - require.NoError(t, err, "Unlocking should be allowed") - }) - - output, err := runCmd(t, "getent", "group", "root", "testgroup") - require.NoError(t, err, "Reading should be allowed") - require.Equal(t, groupContents, output) + for name, f := range maps.Clone(lockingCases) { + t.Run(name, func(t *testing.T) { + err = f.WriteLock() + require.NoError(t, err, "Locking once it is allowed") + t.Cleanup(func() { + err := f.WriteUnlock() + require.NoError(t, err, "Unlocking should be allowed") + }) + + output, err := runCmd(t, "getent", "group", "root", "testgroup") + require.NoError(t, err, "Reading should be allowed") + require.Equal(t, groupContents, output) + }) + } } func TestLockAndLockAgainGroupFileOverridden(t *testing.T) { @@ -159,8 +196,30 @@ func TestLockAndLockAgainGroupFile(t *testing.T) { func TestUnlockUnlocked(t *testing.T) { require.Zero(t, os.Geteuid(), "Not root") - err := userslocking.WriteUnlock() - require.ErrorIs(t, err, userslocking.ErrUnlock, "Unlocking unlocked should not be allowed") + for name, f := range maps.Clone(lockingCases) { + t.Run(name, func(t *testing.T) { + t.Parallel() + + err := f.WriteUnlock() + require.ErrorIs(t, err, userslocking.ErrUnlock, "Unlocking unlocked should not be allowed") + }) + } +} + +func TestRecLockAndRecLockAgainGroupFile(t *testing.T) { + require.Zero(t, os.Geteuid(), "Not root") + + err := userslocking.WriteRecLock() + require.NoError(t, err, "Locking once it is allowed") + + err = userslocking.WriteRecLock() + require.NoError(t, err, "Locking twice it is allowed") + + err = userslocking.WriteRecUnlock() + require.NoError(t, err, "Unlocking should be allowed once") + + err = userslocking.WriteRecUnlock() + require.NoError(t, err, "Unlocking should be allowed twice") } func TestLockingLockedDatabase(t *testing.T) { @@ -176,59 +235,63 @@ func TestLockingLockedDatabase(t *testing.T) { err := os.WriteFile(groupFile, []byte(groupContents), 0644) require.NoError(t, err, "Writing group file") - ctx, cancel := context.WithCancel(context.Background()) - cmd := exec.CommandContext(ctx, testLockerUtility) - t.Logf("Running command: %s", cmd.Args) - - err = cmd.Start() - require.NoError(t, err, "Setup: Locker utility should start") - lockerProcess := cmd.Process - - lockerExited := make(chan error) - writeLockExited := make(chan error) - t.Cleanup(func() { - cancel() - syscall.Kill(lockerProcess.Pid, syscall.SIGKILL) - require.Error(t, <-lockerExited, "Stopping locking process") - require.NoError(t, <-writeLockExited, "Final locking") - require.NoError(t, userslocking.WriteUnlock(), "Final unlocking") - }) - - go func() { - lockerExited <- cmd.Wait() - }() - - select { - case <-time.After(1 * time.Second): - t.Cleanup(func() { lockerProcess.Kill() }) - // If we're time-outing: it's fine, it means the test-locker process is running - case err := <-lockerExited: - require.NoError(t, err, "test locker should not have failed") - } - - gPasswdExited := make(chan error) - go func() { - _, err := runGPasswd(t, "--add", "root", "testgroup") - gPasswdExited <- err - }() - - select { - case <-time.After(3 * time.Second): - // If we're time-outing: it's fine, it means we were locked! - case err := <-gPasswdExited: - require.ErrorIs(t, err, userslocking.ErrLock, "GPasswd should fail") - } - - go func() { - writeLockExited <- userslocking.WriteLock() - }() - - select { - case <-time.After(3 * time.Second): - // If we're time-outing: it's fine, it means the test-locker process is - // still running and holding the lock. - case err := <-writeLockExited: - require.ErrorIs(t, err, userslocking.ErrLock, "Locking should not work") + for name, f := range maps.Clone(lockingCases) { + t.Run(name, func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + cmd := exec.CommandContext(ctx, testLockerUtility) + t.Logf("Running command: %s", cmd.Args) + + err := cmd.Start() + require.NoError(t, err, "Setup: Locker utility should start") + lockerProcess := cmd.Process + + lockerExited := make(chan error) + writeLockExited := make(chan error) + t.Cleanup(func() { + cancel() + syscall.Kill(lockerProcess.Pid, syscall.SIGKILL) + require.Error(t, <-lockerExited, "Stopping locking process") + require.NoError(t, <-writeLockExited, "Final locking") + require.NoError(t, f.WriteUnlock(), "Final unlocking") + }) + + go func() { + lockerExited <- cmd.Wait() + }() + + select { + case <-time.After(1 * time.Second): + t.Cleanup(func() { lockerProcess.Kill() }) + // If we're time-outing: it's fine, it means the test-locker process is running + case err := <-lockerExited: + require.NoError(t, err, "test locker should not have failed") + } + + gPasswdExited := make(chan error) + go func() { + _, err := runGPasswd(t, "--add", "root", "testgroup") + gPasswdExited <- err + }() + + select { + case <-time.After(3 * time.Second): + // If we're time-outing: it's fine, it means we were locked! + case err := <-gPasswdExited: + require.ErrorIs(t, err, userslocking.ErrLock, "GPasswd should fail") + } + + go func() { + writeLockExited <- f.WriteLock() + }() + + select { + case <-time.After(3 * time.Second): + // If we're time-outing: it's fine, it means the test-locker process is + // still running and holding the lock. + case err := <-writeLockExited: + require.ErrorIs(t, err, userslocking.ErrLock, "Locking should not work") + } + }) } } @@ -251,7 +314,6 @@ func TestLockingLockedDatabaseFailsAfterTimeout(t *testing.T) { cancel() syscall.Kill(lockerProcess.Pid, syscall.SIGKILL) require.Error(t, <-lockerExited, "Stopping locking process") - require.NoError(t, userslocking.WriteUnlock(), "Final unlocking") }) go func() { @@ -266,16 +328,20 @@ func TestLockingLockedDatabaseFailsAfterTimeout(t *testing.T) { require.NoError(t, err, "test locker should not have failed") } - t.Log("Waiting for lock") - writeLockExited := make(chan error) - go func() { - writeLockExited <- userslocking.WriteLock() - }() - - err = <-writeLockExited - t.Log("Done waiting for lock!") - require.ErrorIs(t, err, userslocking.ErrLock) - require.ErrorIs(t, err, userslocking.ErrLockTimeout) + for name, f := range maps.Clone(lockingCases) { + t.Run(name, func(t *testing.T) { + t.Log("Waiting for lock") + writeLockExited := make(chan error) + go func() { + writeLockExited <- f.WriteLock() + }() + + err := <-writeLockExited + t.Log("Done waiting for lock!") + require.ErrorIs(t, err, userslocking.ErrLock) + require.ErrorIs(t, err, userslocking.ErrLockTimeout) + }) + } } func TestLockingLockedDatabaseWorksAfterUnlock(t *testing.T) { @@ -284,72 +350,75 @@ func TestLockingLockedDatabaseWorksAfterUnlock(t *testing.T) { testLockerUtility := os.Getenv("AUTHD_TESTS_PASSWD_LOCKER_UTILITY") require.NotEmpty(t, testLockerUtility, "Setup: Locker utility unset") - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - lockerCmd := exec.CommandContext(ctx, testLockerUtility) - t.Logf("Running command: %s", lockerCmd.Args) - - err := lockerCmd.Start() - require.NoError(t, err, "Setup: Locker utility should start") - lockerProcess := lockerCmd.Process - - lockerExited := make(chan error) - go func() { - lockerExited <- lockerCmd.Wait() - }() - - select { - case <-time.After(1 * time.Second): - // If we're time-outing: it's fine, it means the test-locker process is - // still running and holding the lock. - t.Cleanup(func() { lockerProcess.Kill() }) - case err := <-lockerExited: - require.NoError(t, err, "test locker should not have failed") + for name, f := range maps.Clone(lockingCases) { + t.Run(name, func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + lockerCmd := exec.CommandContext(ctx, testLockerUtility) + t.Logf("Running command: %s", lockerCmd.Args) + + err := lockerCmd.Start() + require.NoError(t, err, "Setup: Locker utility should start") + lockerProcess := lockerCmd.Process + + lockerExited := make(chan error) + go func() { + lockerExited <- lockerCmd.Wait() + }() + + select { + case <-time.After(1 * time.Second): + // If we're time-outing: it's fine, it means the test-locker process is + // still running and holding the lock. + t.Cleanup(func() { lockerProcess.Kill() }) + case err := <-lockerExited: + require.NoError(t, err, "test locker should not have failed") + } + + writeLockExited := make(chan error) + go func() { + writeLockExited <- f.WriteLock() + }() + + select { + case <-time.After(3 * time.Second): + // If we're time-outing: it's fine, it means the test-locker process is + // still running and holding the lock. + case err := <-writeLockExited: + require.ErrorIs(t, err, userslocking.ErrLock, "Locking should not work") + } + + writeUnLockExited := make(chan error) + go func() { + writeUnLockExited <- f.WriteUnlock() + }() + + select { + case <-time.After(1 * time.Second): + // If we're time-outing: it's fine, it means the test-locker process is + // still running and holding the lock. + case err := <-writeUnLockExited: + require.ErrorIs(t, err, userslocking.ErrUnlock, "Locking should not work") + } + + t.Log("Killing locking process") + cancel() + syscall.Kill(lockerProcess.Pid, syscall.SIGKILL) + // Do not wait for the locker being exited yet, so that we can ensure that + // our function call wait is over. + + t.Log("We should get the lock now!") + err = <-writeLockExited + require.NoError(t, err, "We should have the lock now") + + t.Log("Ensure locking process has been stopped!") + err = <-lockerExited + require.Error(t, err, "Locker should exit with failure") + + err = <-writeUnLockExited + require.NoError(t, err, "We should be able to unlock now") + }) } - - writeLockExited := make(chan error) - go func() { - writeLockExited <- userslocking.WriteLock() - }() - - select { - case <-time.After(3 * time.Second): - // If we're time-outing: it's fine, it means the test-locker process is - // still running and holding the lock. - case err := <-writeLockExited: - require.ErrorIs(t, err, userslocking.ErrLock, "Locking should not work") - } - - writeUnLockExited := make(chan error) - go func() { - writeUnLockExited <- userslocking.WriteUnlock() - }() - - select { - case <-time.After(1 * time.Second): - // If we're time-outing: it's fine, it means the test-locker process is - // still running and holding the lock. - case err := <-writeUnLockExited: - require.ErrorIs(t, err, userslocking.ErrUnlock, "Locking should not work") - } - - t.Log("Killing locking process") - cancel() - syscall.Kill(lockerProcess.Pid, syscall.SIGKILL) - // Do not wait for the locker being exited yet, so that we can ensure that - // our function call wait is over. - - t.Log("We should get the lock now!") - err = <-writeLockExited - require.NoError(t, err, "We should have the lock now") - - t.Log("Ensure locking process has been stopped!") - err = <-lockerExited - require.Error(t, err, "Locker should exit with failure") - - err = <-writeUnLockExited - // err = userslocking.WriteUnlock() - require.NoError(t, err, "We should be able to unlock now") } func runCmd(t *testing.T, command string, args ...string) (string, error) { diff --git a/internal/users/locking/locking_test.go b/internal/users/locking/locking_test.go index 8f7a0113fa..3b313e26e7 100644 --- a/internal/users/locking/locking_test.go +++ b/internal/users/locking/locking_test.go @@ -9,6 +9,7 @@ import ( "path/filepath" "regexp" "strings" + "sync" "testing" "time" @@ -164,3 +165,128 @@ func TestUsersLockingOverrideAsLockedExternally(t *testing.T) { err = userslocking.WriteUnlock() require.ErrorIs(t, err, userslocking.ErrUnlock, "Unlocking unlocked should not be allowed") } + +func TestUsersLockingRecLockingOverride(t *testing.T) { + // This cannot be parallel. + + userslocking.Z_ForTests_OverrideLockingWithCleanup(t) + + err := userslocking.WriteRecLock() + require.NoError(t, err, "Locking should be allowed") + + err = userslocking.WriteRecLock() + require.NoError(t, err, "Locking again should be allowed") + + err = userslocking.WriteRecUnlock() + require.NoError(t, err, "Unlocking should be allowed") + + err = userslocking.WriteRecUnlock() + require.NoError(t, err, "Unlocking again should be allowed") + + err = userslocking.WriteRecUnlock() + require.ErrorIs(t, err, userslocking.ErrUnlock, "Unlocking unlocked should not be allowed") + + err = userslocking.WriteLock() + require.NoError(t, err, "Locking should be allowed") + + err = userslocking.WriteUnlock() + require.NoError(t, err, "Unlocking should be allowed") +} + +func TestUsersLockingRecLockingMixedWithLockOverride(t *testing.T) { + // This cannot be parallel. + + userslocking.Z_ForTests_OverrideLockingWithCleanup(t) + + // Using RecLock first, then trying to use normal lock in between. + err := userslocking.WriteRecLock() + require.NoError(t, err, "Locking should be allowed") + + err = userslocking.WriteRecLock() + require.NoError(t, err, "Locking again should be allowed") + + err = userslocking.WriteLock() + require.ErrorIs(t, err, userslocking.ErrLock, "Locking again should not be allowed") + + err = userslocking.WriteRecUnlock() + require.NoError(t, err, "Unlocking should be allowed") + + err = userslocking.WriteUnlock() + require.ErrorIs(t, err, userslocking.ErrUnlock, "Normal unlocking unlocked should not be allowed") + + err = userslocking.WriteRecUnlock() + require.NoError(t, err, "Unlocking again should be allowed") + + // Use normal lock, then try to RecLock meanwhile + + err = userslocking.WriteLock() + require.NoError(t, err, "Locking should be allowed") + + err = userslocking.WriteRecLock() + require.ErrorIs(t, err, userslocking.ErrLock, "Locking again should not be allowed") + + err = userslocking.WriteRecUnlock() + require.ErrorIs(t, err, userslocking.ErrUnlock, "Normal unlocking unlocked should not be allowed") + + err = userslocking.WriteUnlock() + require.NoError(t, err, "Unlocking should be allowed") +} + +func TestUsersLockingRecLockingOverrideAsLockedExternally(t *testing.T) { + // This cannot be parallel. + lockCtx, lockCancel := context.WithCancel(context.Background()) + userslocking.Z_ForTests_OverrideLockingAsLockedExternally(t, lockCtx) + + wg := sync.WaitGroup{} + wg.Add(2) + go func() { + err := userslocking.WriteRecLock() + require.NoError(t, err, "Locking should not fail") + wg.Done() + }() + go func() { + err := userslocking.WriteRecLock() + require.NoError(t, err, "Locking should not fail") + wg.Done() + }() + + doneWaiting := make(chan struct{}) + go func() { + wg.Wait() + close(doneWaiting) + }() + + select { + case <-time.After(1 * time.Second): + // If we're time-outing: it's fine, it means we were locked! + case <-doneWaiting: + t.Error("We should not be unlocked, but we are") + t.FailNow() + } + + wg.Add(2) + go func() { + err := userslocking.WriteRecUnlock() + require.NoError(t, err, "Unlocking should not fail") + wg.Done() + }() + go func() { + err := userslocking.WriteRecUnlock() + require.NoError(t, err, "Unlocking should not fail") + wg.Done() + }() + t.Cleanup(wg.Wait) + + select { + case <-time.After(1 * time.Second): + // If we're time-outing: it's fine, it means we were locked! + case <-doneWaiting: + t.Error("We should not be unlocked, but we are") + t.FailNow() + } + + // Remove the "external" lock now. + lockCancel() + + <-doneWaiting +} From 55018d4234981f048dfc80b8d2c056a4d418015b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 17 Jun 2025 22:23:13 +0200 Subject: [PATCH 0516/1670] users/locking: Add ability to reduce the wait time in tests It allows to reduce the wait time in various tests where there's not much need to wait for the whole time --- internal/users/locking/locking_bwrap_test.go | 12 ++++--- internal/users/locking/testutils.go | 33 ++++++++++++++++++-- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/internal/users/locking/locking_bwrap_test.go b/internal/users/locking/locking_bwrap_test.go index 1c73459422..9bd81114f9 100644 --- a/internal/users/locking/locking_bwrap_test.go +++ b/internal/users/locking/locking_bwrap_test.go @@ -285,7 +285,7 @@ func TestLockingLockedDatabase(t *testing.T) { }() select { - case <-time.After(3 * time.Second): + case <-time.After(1 * time.Second): // If we're time-outing: it's fine, it means the test-locker process is // still running and holding the lock. case err := <-writeLockExited: @@ -298,6 +298,8 @@ func TestLockingLockedDatabase(t *testing.T) { func TestLockingLockedDatabaseFailsAfterTimeout(t *testing.T) { require.Zero(t, os.Geteuid(), "Not root") + userslocking.Z_ForTests_SetMaxWaitTime(t, 2*time.Second) + testLockerUtility := os.Getenv("AUTHD_TESTS_PASSWD_LOCKER_UTILITY") require.NotEmpty(t, testLockerUtility, "Setup: Locker utility unset") @@ -350,6 +352,8 @@ func TestLockingLockedDatabaseWorksAfterUnlock(t *testing.T) { testLockerUtility := os.Getenv("AUTHD_TESTS_PASSWD_LOCKER_UTILITY") require.NotEmpty(t, testLockerUtility, "Setup: Locker utility unset") + userslocking.Z_ForTests_SetMaxWaitTime(t, 3*time.Second) + for name, f := range maps.Clone(lockingCases) { t.Run(name, func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) @@ -367,7 +371,7 @@ func TestLockingLockedDatabaseWorksAfterUnlock(t *testing.T) { }() select { - case <-time.After(1 * time.Second): + case <-time.After(500 * time.Millisecond): // If we're time-outing: it's fine, it means the test-locker process is // still running and holding the lock. t.Cleanup(func() { lockerProcess.Kill() }) @@ -381,7 +385,7 @@ func TestLockingLockedDatabaseWorksAfterUnlock(t *testing.T) { }() select { - case <-time.After(3 * time.Second): + case <-time.After(1 * time.Second): // If we're time-outing: it's fine, it means the test-locker process is // still running and holding the lock. case err := <-writeLockExited: @@ -394,7 +398,7 @@ func TestLockingLockedDatabaseWorksAfterUnlock(t *testing.T) { }() select { - case <-time.After(1 * time.Second): + case <-time.After(500 * time.Millisecond): // If we're time-outing: it's fine, it means the test-locker process is // still running and holding the lock. case err := <-writeUnLockExited: diff --git a/internal/users/locking/testutils.go b/internal/users/locking/testutils.go index 6efa41aeb9..4d7d3cabdb 100644 --- a/internal/users/locking/testutils.go +++ b/internal/users/locking/testutils.go @@ -14,13 +14,18 @@ import ( ) var ( - overrideLocked atomic.Bool + overrideLocked atomic.Bool + overrideMaxWait atomic.Int64 overriddenMu sync.Mutex overriddenWriteLockImpl []func() error overriddenWriteUnlockImpl []func() error ) +func init() { + overrideMaxWait.Store(int64(maxWait)) +} + // Z_ForTests_OverrideLocking is a function to override the locking functions // for testing purposes. // It simulates the real behavior but without actual file locking. @@ -99,11 +104,13 @@ func Z_ForTests_OverrideLockingAsLockedExternally(t *testing.T, ctx context.Cont overriddenWriteLockImpl = append(overriddenWriteLockImpl, writeLockImpl) writeLockImpl = func() error { for { + maxWait := time.Duration(overrideMaxWait.Load()) + select { case lockCh <- struct{}{}: log.Debug(ctx, "TestOverrideExternallyLocked: Local entries external lock released!") case <-time.After(maxWait): - return ErrLockTimeout + return fmt.Errorf("failed waiting for %v: %w", maxWait, ErrLockTimeout) } if overrideLocked.CompareAndSwap(false, true) { @@ -172,3 +179,25 @@ func Z_ForTests_RestoreLocking() { overriddenWriteLockImpl, writeLockImpl = popLast(overriddenWriteLockImpl) overriddenWriteUnlockImpl, writeUnlockImpl = popLast(overriddenWriteUnlockImpl) } + +// Z_ForTests_SetMaxWaitTime sets the max time that we should wait before +// returning a failure in [WriteLock] and [WriteRecLock]. +// +// nolint:revive,nolintlint // We want to use underscores in the function name here. +func Z_ForTests_SetMaxWaitTime(t *testing.T, maxWaitTime time.Duration) { + t.Helper() + + testsdetection.MustBeTesting() + + require.True(t, overrideMaxWait.CompareAndSwap(int64(maxWait), int64(maxWaitTime)), + "Waiting time has an unexpected value: %v", overrideMaxWait.Load()) + + defaultMaxWait := maxWait + maxWait = maxWaitTime + + t.Cleanup(func() { + require.True(t, overrideMaxWait.CompareAndSwap(int64(maxWaitTime), int64(defaultMaxWait)), + "Waiting time has been changed: %v", overrideMaxWait.Load()) + maxWait = defaultMaxWait + }) +} From 35f6f307b2139cc79935fc9e5003cacbf2dd4d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 25 Jun 2025 13:54:42 +0000 Subject: [PATCH 0517/1670] users/locking: Remove the non-recursive locking We do not use it anymore everywhere a part in tests, so let's simplify the code. --- internal/users/db/migration.go | 4 +- internal/users/locking/locking.go | 33 -- internal/users/locking/locking_bwrap_test.go | 431 ++++++++---------- internal/users/locking/locking_test.go | 93 ---- .../users/locking/testlocker/testlocker.go | 2 +- internal/users/locking/testutils.go | 4 +- 6 files changed, 195 insertions(+), 372 deletions(-) diff --git a/internal/users/db/migration.go b/internal/users/db/migration.go index 91d2d21f16..6e41ac2a1f 100644 --- a/internal/users/db/migration.go +++ b/internal/users/db/migration.go @@ -262,12 +262,12 @@ func renameUsersInGroupFile(oldNames, newNames []string) error { // Note that we can't use gpasswd here because `gpasswd --add` checks for the existence of the user, which causes an // NSS request to be sent to authd, but authd is not ready yet because we are still migrating the database. - err := userslocking.WriteLock() + err := userslocking.WriteRecLock() if err != nil { return fmt.Errorf("failed to lock group file: %w", err) } defer func() { - if err := userslocking.WriteUnlock(); err != nil { + if err := userslocking.WriteRecUnlock(); err != nil { log.Warningf(context.Background(), "Failed to unlock group file: %v", err) } }() diff --git a/internal/users/locking/locking.go b/internal/users/locking/locking.go index 32c22d4747..066a8f857e 100644 --- a/internal/users/locking/locking.go +++ b/internal/users/locking/locking.go @@ -37,24 +37,6 @@ var ( ErrLockTimeout = fmt.Errorf("%w: timeout", ErrLock) ) -// WriteLock locks for writing the the local user entries database by using -// the standard libc lckpwdf() function. -// While the database is locked read operations can happen, but no other process -// is allowed to write. -// Note that this call will block all the other processes trying to access the -// database in write mode, while it will return an error if called while the -// lock is already hold by this process. -func WriteLock() error { - writeLocksCountMu.RLock() - defer writeLocksCountMu.RUnlock() - - if writeLocksCount > 0 { - return fmt.Errorf("%w: mixing recursive and normal locking", ErrLock) - } - - return writeLockInternal() -} - func writeLockInternal() error { done := make(chan error) writeLockImpl := writeLockImpl @@ -75,21 +57,6 @@ func writeLockInternal() error { } } -// WriteUnlock unlocks for writing the local user entries database by using -// the standard libc ulckpwdf() function. -// As soon as this function is called all the other waiting processes will be -// allowed to take the lock. -func WriteUnlock() error { - writeLocksCountMu.RLock() - defer writeLocksCountMu.RUnlock() - - if writeLocksCount > 0 { - return fmt.Errorf("%w: mixing recursive and normal locking", ErrUnlock) - } - - return writeUnlockImpl() -} - // WriteRecLock locks the system's user database for writing. // While the lock is held, all other processes trying to lock the database // will block until the lock is released (or a timeout of 15 seconds is reached). diff --git a/internal/users/locking/locking_bwrap_test.go b/internal/users/locking/locking_bwrap_test.go index 9bd81114f9..0dc9c4f37f 100644 --- a/internal/users/locking/locking_bwrap_test.go +++ b/internal/users/locking/locking_bwrap_test.go @@ -4,7 +4,6 @@ package userslocking_test import ( "context" - "maps" "os" "os/exec" "path/filepath" @@ -17,33 +16,25 @@ import ( userslocking "github.com/ubuntu/authd/internal/users/locking" ) -var lockingCases = map[string]struct { - WriteLock func() error - WriteUnlock func() error -}{ - "NormalLocking": { - WriteLock: userslocking.WriteLock, - WriteUnlock: userslocking.WriteUnlock, - }, - "RecLocking": { - WriteLock: func() error { - for i := 0; i < 5; i++ { - if err := userslocking.WriteRecLock(); err != nil { - return err - } +var ( + writeLock = func() error { + for i := 0; i < 5; i++ { + if err := userslocking.WriteRecLock(); err != nil { + return err } - return nil - }, - WriteUnlock: func() error { - for i := 0; i < 5; i++ { - if err := userslocking.WriteRecUnlock(); err != nil { - return err - } + } + return nil + } + + writeUnlock = func() error { + for i := 0; i < 5; i++ { + if err := userslocking.WriteRecUnlock(); err != nil { + return err } - return nil - }, - }, -} + } + return nil + } +) func TestLockAndWriteUnlock(t *testing.T) { require.Zero(t, os.Geteuid(), "Not root") @@ -54,46 +45,42 @@ func TestLockAndWriteUnlock(t *testing.T) { err := os.WriteFile(groupFile, []byte("root:x:0:\n"+newGroupContents), 0644) require.NoError(t, err, "Writing group file") - for name, f := range maps.Clone(lockingCases) { - t.Run(name, func(t *testing.T) { - // Try using gpasswd to modify the group file. This should succeed, because - // the group file is not locked. - output, err := runGPasswd(t, "--add", "root", "testgroup") - require.NoError(t, err, "Output: %s", output) - - // Lock the group file - err = f.WriteLock() - require.NoError(t, err, "Locking database") - - output, err = runCmd(t, "getent", "group", "testgroup") - require.NoError(t, err, "Output: %s", output) - require.Equal(t, output, newGroupContents+",root", "Group not found") - - // Try using gpasswd to modify the group file. This should fail, because - // the group file is locked. - output, err = runGPasswd(t, "--delete", "root", "testgroup") - require.Error(t, err, output) - require.Contains(t, output, "gpasswd: cannot lock /etc/group") - - // Reading is allowed when locked. - output, err = runCmd(t, "getent", "group", "testgroup") - require.NoError(t, err, "Output: %s", output) - require.Equal(t, output, newGroupContents+",root", "Group not found") - - // Unlock the group file - err = f.WriteUnlock() - require.NoError(t, err, "Unlocking database") - - // Try using gpasswd to modify the group file again. This should succeed, - // because the group file is unlocked. - output, err = runGPasswd(t, "--delete", "root", "testgroup") - require.NoError(t, err, "Output: %s", output) - - output, err = runCmd(t, "getent", "group", "testgroup") - require.NoError(t, err, "Output: %s", output) - require.Equal(t, output, newGroupContents, "Group not found") - }) - } + // Try using gpasswd to modify the group file. This should succeed, because + // the group file is not locked. + output, err := runGPasswd(t, "--add", "root", "testgroup") + require.NoError(t, err, "Output: %s", output) + + // Lock the group file + err = writeLock() + require.NoError(t, err, "Locking database") + + output, err = runCmd(t, "getent", "group", "testgroup") + require.NoError(t, err, "Output: %s", output) + require.Equal(t, output, newGroupContents+",root", "Group not found") + + // Try using gpasswd to modify the group file. This should fail, because + // the group file is locked. + output, err = runGPasswd(t, "--delete", "root", "testgroup") + require.Error(t, err, output) + require.Contains(t, output, "gpasswd: cannot lock /etc/group") + + // Reading is allowed when locked. + output, err = runCmd(t, "getent", "group", "testgroup") + require.NoError(t, err, "Output: %s", output) + require.Equal(t, output, newGroupContents+",root", "Group not found") + + // Unlock the group file + err = writeUnlock() + require.NoError(t, err, "Unlocking database") + + // Try using gpasswd to modify the group file again. This should succeed, + // because the group file is unlocked. + output, err = runGPasswd(t, "--delete", "root", "testgroup") + require.NoError(t, err, "Output: %s", output) + + output, err = runCmd(t, "getent", "group", "testgroup") + require.NoError(t, err, "Output: %s", output) + require.Equal(t, output, newGroupContents, "Group not found") } func TestReadWhileLocked(t *testing.T) { @@ -107,20 +94,16 @@ testgroup:x:1001:testuser` err := os.WriteFile(groupFile, []byte(groupContents), 0644) require.NoError(t, err, "Writing group file") - for name, f := range maps.Clone(lockingCases) { - t.Run(name, func(t *testing.T) { - err = f.WriteLock() - require.NoError(t, err, "Locking once it is allowed") - t.Cleanup(func() { - err := f.WriteUnlock() - require.NoError(t, err, "Unlocking should be allowed") - }) - - output, err := runCmd(t, "getent", "group", "root", "testgroup") - require.NoError(t, err, "Reading should be allowed") - require.Equal(t, groupContents, output) - }) - } + err = writeLock() + require.NoError(t, err, "Locking once it is allowed") + t.Cleanup(func() { + err := writeUnlock() + require.NoError(t, err, "Unlocking should be allowed") + }) + + output, err := runCmd(t, "getent", "group", "root", "testgroup") + require.NoError(t, err, "Reading should be allowed") + require.Equal(t, groupContents, output) } func TestLockAndLockAgainGroupFileOverridden(t *testing.T) { @@ -128,13 +111,10 @@ func TestLockAndLockAgainGroupFileOverridden(t *testing.T) { restoreFunc := userslocking.Z_ForTests_RestoreLocking t.Cleanup(func() { restoreFunc() }) - err := userslocking.WriteLock() + err := userslocking.WriteRecLock() require.NoError(t, err, "Locking once it is allowed") - err = userslocking.WriteLock() - require.ErrorIs(t, err, userslocking.ErrLock, "Locking again should not be allowed") - - err = userslocking.WriteUnlock() + err = userslocking.WriteRecUnlock() require.NoError(t, err, "Unlocking should be allowed") // Ensure restoring works as expected. @@ -148,12 +128,12 @@ func TestLockAndLockAgainGroupFileOverridden(t *testing.T) { err = os.WriteFile(groupFile, []byte(groupContents), 0644) require.NoError(t, err, "Writing group file") - err = userslocking.WriteLock() + err = userslocking.WriteRecLock() require.NoError(t, err, "Locking once it is allowed") t.Cleanup(func() { // Ignore the error here, as it's expected to return an error if the - // WriteUnlock further below is called first. - _ = userslocking.WriteUnlock() + // WriteRecUnlock further below is called first. + _ = userslocking.WriteRecUnlock() }) gPasswdExited := make(chan error) @@ -169,41 +149,22 @@ func TestLockAndLockAgainGroupFileOverridden(t *testing.T) { require.ErrorIs(t, err, userslocking.ErrLock, "GPasswd should fail") } - require.NoError(t, userslocking.WriteUnlock()) + require.NoError(t, userslocking.WriteRecUnlock()) <-gPasswdExited } func TestUnlockUnlockedOverridden(t *testing.T) { userslocking.Z_ForTests_OverrideLockingWithCleanup(t) - err := userslocking.WriteUnlock() + err := userslocking.WriteRecUnlock() require.ErrorIs(t, err, userslocking.ErrUnlock, "Unlocking unlocked should not be allowed") } -func TestLockAndLockAgainGroupFile(t *testing.T) { - require.Zero(t, os.Geteuid(), "Not root") - - err := userslocking.WriteLock() - require.NoError(t, err, "Locking once it is allowed") - - err = userslocking.WriteLock() - require.ErrorIs(t, err, userslocking.ErrLock, "Locking again should not be allowed") - - err = userslocking.WriteUnlock() - require.NoError(t, err, "Unlocking should be allowed") -} - func TestUnlockUnlocked(t *testing.T) { require.Zero(t, os.Geteuid(), "Not root") - for name, f := range maps.Clone(lockingCases) { - t.Run(name, func(t *testing.T) { - t.Parallel() - - err := f.WriteUnlock() - require.ErrorIs(t, err, userslocking.ErrUnlock, "Unlocking unlocked should not be allowed") - }) - } + err := writeUnlock() + require.ErrorIs(t, err, userslocking.ErrUnlock, "Unlocking unlocked should not be allowed") } func TestRecLockAndRecLockAgainGroupFile(t *testing.T) { @@ -235,63 +196,59 @@ func TestLockingLockedDatabase(t *testing.T) { err := os.WriteFile(groupFile, []byte(groupContents), 0644) require.NoError(t, err, "Writing group file") - for name, f := range maps.Clone(lockingCases) { - t.Run(name, func(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - cmd := exec.CommandContext(ctx, testLockerUtility) - t.Logf("Running command: %s", cmd.Args) - - err := cmd.Start() - require.NoError(t, err, "Setup: Locker utility should start") - lockerProcess := cmd.Process - - lockerExited := make(chan error) - writeLockExited := make(chan error) - t.Cleanup(func() { - cancel() - syscall.Kill(lockerProcess.Pid, syscall.SIGKILL) - require.Error(t, <-lockerExited, "Stopping locking process") - require.NoError(t, <-writeLockExited, "Final locking") - require.NoError(t, f.WriteUnlock(), "Final unlocking") - }) - - go func() { - lockerExited <- cmd.Wait() - }() - - select { - case <-time.After(1 * time.Second): - t.Cleanup(func() { lockerProcess.Kill() }) - // If we're time-outing: it's fine, it means the test-locker process is running - case err := <-lockerExited: - require.NoError(t, err, "test locker should not have failed") - } + ctx, cancel := context.WithCancel(context.Background()) + cmd := exec.CommandContext(ctx, testLockerUtility) + t.Logf("Running command: %s", cmd.Args) - gPasswdExited := make(chan error) - go func() { - _, err := runGPasswd(t, "--add", "root", "testgroup") - gPasswdExited <- err - }() - - select { - case <-time.After(3 * time.Second): - // If we're time-outing: it's fine, it means we were locked! - case err := <-gPasswdExited: - require.ErrorIs(t, err, userslocking.ErrLock, "GPasswd should fail") - } + err = cmd.Start() + require.NoError(t, err, "Setup: Locker utility should start") + lockerProcess := cmd.Process - go func() { - writeLockExited <- f.WriteLock() - }() + lockerExited := make(chan error) + writeLockExited := make(chan error) + t.Cleanup(func() { + cancel() + syscall.Kill(lockerProcess.Pid, syscall.SIGKILL) + require.Error(t, <-lockerExited, "Stopping locking process") + require.NoError(t, <-writeLockExited, "Final locking") + require.NoError(t, writeUnlock(), "Final unlocking") + }) - select { - case <-time.After(1 * time.Second): - // If we're time-outing: it's fine, it means the test-locker process is - // still running and holding the lock. - case err := <-writeLockExited: - require.ErrorIs(t, err, userslocking.ErrLock, "Locking should not work") - } - }) + go func() { + lockerExited <- cmd.Wait() + }() + + select { + case <-time.After(1 * time.Second): + t.Cleanup(func() { lockerProcess.Kill() }) + // If we're time-outing: it's fine, it means the test-locker process is running + case err := <-lockerExited: + require.NoError(t, err, "test locker should not have failed") + } + + gPasswdExited := make(chan error) + go func() { + _, err := runGPasswd(t, "--add", "root", "testgroup") + gPasswdExited <- err + }() + + select { + case <-time.After(3 * time.Second): + // If we're time-outing: it's fine, it means we were locked! + case err := <-gPasswdExited: + require.ErrorIs(t, err, userslocking.ErrLock, "GPasswd should fail") + } + + go func() { + writeLockExited <- writeLock() + }() + + select { + case <-time.After(1 * time.Second): + // If we're time-outing: it's fine, it means the test-locker process is + // still running and holding the lock. + case err := <-writeLockExited: + require.ErrorIs(t, err, userslocking.ErrLock, "Locking should not work") } } @@ -330,20 +287,16 @@ func TestLockingLockedDatabaseFailsAfterTimeout(t *testing.T) { require.NoError(t, err, "test locker should not have failed") } - for name, f := range maps.Clone(lockingCases) { - t.Run(name, func(t *testing.T) { - t.Log("Waiting for lock") - writeLockExited := make(chan error) - go func() { - writeLockExited <- f.WriteLock() - }() - - err := <-writeLockExited - t.Log("Done waiting for lock!") - require.ErrorIs(t, err, userslocking.ErrLock) - require.ErrorIs(t, err, userslocking.ErrLockTimeout) - }) - } + t.Log("Waiting for lock") + writeLockExited := make(chan error) + go func() { + writeLockExited <- writeLock() + }() + + err = <-writeLockExited + t.Log("Done waiting for lock!") + require.ErrorIs(t, err, userslocking.ErrLock) + require.ErrorIs(t, err, userslocking.ErrLockTimeout) } func TestLockingLockedDatabaseWorksAfterUnlock(t *testing.T) { @@ -354,75 +307,71 @@ func TestLockingLockedDatabaseWorksAfterUnlock(t *testing.T) { userslocking.Z_ForTests_SetMaxWaitTime(t, 3*time.Second) - for name, f := range maps.Clone(lockingCases) { - t.Run(name, func(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - lockerCmd := exec.CommandContext(ctx, testLockerUtility) - t.Logf("Running command: %s", lockerCmd.Args) - - err := lockerCmd.Start() - require.NoError(t, err, "Setup: Locker utility should start") - lockerProcess := lockerCmd.Process - - lockerExited := make(chan error) - go func() { - lockerExited <- lockerCmd.Wait() - }() - - select { - case <-time.After(500 * time.Millisecond): - // If we're time-outing: it's fine, it means the test-locker process is - // still running and holding the lock. - t.Cleanup(func() { lockerProcess.Kill() }) - case err := <-lockerExited: - require.NoError(t, err, "test locker should not have failed") - } + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + lockerCmd := exec.CommandContext(ctx, testLockerUtility) + t.Logf("Running command: %s", lockerCmd.Args) - writeLockExited := make(chan error) - go func() { - writeLockExited <- f.WriteLock() - }() - - select { - case <-time.After(1 * time.Second): - // If we're time-outing: it's fine, it means the test-locker process is - // still running and holding the lock. - case err := <-writeLockExited: - require.ErrorIs(t, err, userslocking.ErrLock, "Locking should not work") - } + err := lockerCmd.Start() + require.NoError(t, err, "Setup: Locker utility should start") + lockerProcess := lockerCmd.Process - writeUnLockExited := make(chan error) - go func() { - writeUnLockExited <- f.WriteUnlock() - }() - - select { - case <-time.After(500 * time.Millisecond): - // If we're time-outing: it's fine, it means the test-locker process is - // still running and holding the lock. - case err := <-writeUnLockExited: - require.ErrorIs(t, err, userslocking.ErrUnlock, "Locking should not work") - } + lockerExited := make(chan error) + go func() { + lockerExited <- lockerCmd.Wait() + }() - t.Log("Killing locking process") - cancel() - syscall.Kill(lockerProcess.Pid, syscall.SIGKILL) - // Do not wait for the locker being exited yet, so that we can ensure that - // our function call wait is over. + select { + case <-time.After(500 * time.Millisecond): + // If we're time-outing: it's fine, it means the test-locker process is + // still running and holding the lock. + t.Cleanup(func() { lockerProcess.Kill() }) + case err := <-lockerExited: + require.NoError(t, err, "test locker should not have failed") + } - t.Log("We should get the lock now!") - err = <-writeLockExited - require.NoError(t, err, "We should have the lock now") + writeLockExited := make(chan error) + go func() { + writeLockExited <- writeLock() + }() + + select { + case <-time.After(1 * time.Second): + // If we're time-outing: it's fine, it means the test-locker process is + // still running and holding the lock. + case err := <-writeLockExited: + require.ErrorIs(t, err, userslocking.ErrLock, "Locking should not work") + } - t.Log("Ensure locking process has been stopped!") - err = <-lockerExited - require.Error(t, err, "Locker should exit with failure") + writeUnLockExited := make(chan error) + go func() { + writeUnLockExited <- writeUnlock() + }() - err = <-writeUnLockExited - require.NoError(t, err, "We should be able to unlock now") - }) + select { + case <-time.After(500 * time.Millisecond): + // If we're time-outing: it's fine, it means the test-locker process is + // still running and holding the lock. + case err := <-writeUnLockExited: + require.ErrorIs(t, err, userslocking.ErrUnlock, "Locking should not work") } + + t.Log("Killing locking process") + cancel() + syscall.Kill(lockerProcess.Pid, syscall.SIGKILL) + // Do not wait for the locker being exited yet, so that we can ensure that + // our function call wait is over. + + t.Log("We should get the lock now!") + err = <-writeLockExited + require.NoError(t, err, "We should have the lock now") + + t.Log("Ensure locking process has been stopped!") + err = <-lockerExited + require.Error(t, err, "Locker should exit with failure") + + err = <-writeUnLockExited + require.NoError(t, err, "We should be able to unlock now") } func runCmd(t *testing.T, command string, args ...string) (string, error) { diff --git a/internal/users/locking/locking_test.go b/internal/users/locking/locking_test.go index 3b313e26e7..44780668f7 100644 --- a/internal/users/locking/locking_test.go +++ b/internal/users/locking/locking_test.go @@ -118,54 +118,6 @@ func compileLockerBinary(t *testing.T) string { return testLocker } -func TestUsersLockingOverride(t *testing.T) { - // This cannot be parallel. - - userslocking.Z_ForTests_OverrideLockingWithCleanup(t) - - err := userslocking.WriteLock() - require.NoError(t, err, "Locking should be allowed") - - err = userslocking.WriteLock() - require.ErrorIs(t, err, userslocking.ErrLock, "Locking again should not be allowed") - - err = userslocking.WriteUnlock() - require.NoError(t, err, "Unlocking should be allowed") - - err = userslocking.WriteUnlock() - require.ErrorIs(t, err, userslocking.ErrUnlock, "Unlocking unlocked should not be allowed") -} - -func TestUsersLockingOverrideAsLockedExternally(t *testing.T) { - // This cannot be parallel. - userslocking.Z_ForTests_OverrideLockingAsLockedExternally(t, context.Background()) - - lockingExited := make(chan error) - go func() { - lockingExited <- userslocking.WriteLock() - }() - - select { - case <-time.After(1 * time.Second): - // If we're time-outing: it's fine, it means we were locked! - case err := <-lockingExited: - t.Errorf("We should have not been exited, but we did with error %v", err) - t.FailNow() - } - - err := userslocking.WriteUnlock() - require.NoError(t, err, "Unlocking should be allowed") - - err = <-lockingExited - require.NoError(t, err, "Previous concurrent locking should have been allowed now") - - err = userslocking.WriteUnlock() - require.NoError(t, err, "Unlocking should be allowed") - - err = userslocking.WriteUnlock() - require.ErrorIs(t, err, userslocking.ErrUnlock, "Unlocking unlocked should not be allowed") -} - func TestUsersLockingRecLockingOverride(t *testing.T) { // This cannot be parallel. @@ -185,51 +137,6 @@ func TestUsersLockingRecLockingOverride(t *testing.T) { err = userslocking.WriteRecUnlock() require.ErrorIs(t, err, userslocking.ErrUnlock, "Unlocking unlocked should not be allowed") - - err = userslocking.WriteLock() - require.NoError(t, err, "Locking should be allowed") - - err = userslocking.WriteUnlock() - require.NoError(t, err, "Unlocking should be allowed") -} - -func TestUsersLockingRecLockingMixedWithLockOverride(t *testing.T) { - // This cannot be parallel. - - userslocking.Z_ForTests_OverrideLockingWithCleanup(t) - - // Using RecLock first, then trying to use normal lock in between. - err := userslocking.WriteRecLock() - require.NoError(t, err, "Locking should be allowed") - - err = userslocking.WriteRecLock() - require.NoError(t, err, "Locking again should be allowed") - - err = userslocking.WriteLock() - require.ErrorIs(t, err, userslocking.ErrLock, "Locking again should not be allowed") - - err = userslocking.WriteRecUnlock() - require.NoError(t, err, "Unlocking should be allowed") - - err = userslocking.WriteUnlock() - require.ErrorIs(t, err, userslocking.ErrUnlock, "Normal unlocking unlocked should not be allowed") - - err = userslocking.WriteRecUnlock() - require.NoError(t, err, "Unlocking again should be allowed") - - // Use normal lock, then try to RecLock meanwhile - - err = userslocking.WriteLock() - require.NoError(t, err, "Locking should be allowed") - - err = userslocking.WriteRecLock() - require.ErrorIs(t, err, userslocking.ErrLock, "Locking again should not be allowed") - - err = userslocking.WriteRecUnlock() - require.ErrorIs(t, err, userslocking.ErrUnlock, "Normal unlocking unlocked should not be allowed") - - err = userslocking.WriteUnlock() - require.NoError(t, err, "Unlocking should be allowed") } func TestUsersLockingRecLockingOverrideAsLockedExternally(t *testing.T) { diff --git a/internal/users/locking/testlocker/testlocker.go b/internal/users/locking/testlocker/testlocker.go index df36e50585..d134cb2535 100644 --- a/internal/users/locking/testlocker/testlocker.go +++ b/internal/users/locking/testlocker/testlocker.go @@ -12,7 +12,7 @@ import ( func main() { log.Println("Locking database...") - err := userslocking.WriteLock() + err := userslocking.WriteRecLock() if err != nil { log.Fatal(err) } diff --git a/internal/users/locking/testutils.go b/internal/users/locking/testutils.go index 4d7d3cabdb..f660b80b86 100644 --- a/internal/users/locking/testutils.go +++ b/internal/users/locking/testutils.go @@ -78,7 +78,7 @@ func Z_ForTests_OverrideLockingWithCleanup(t *testing.T) { // user database is locked by an external process. // // When called, it marks the user database as locked, causing any subsequent -// locking attempts by authd (via [WriteLock]) to block until the provided +// locking attempts by authd (via [WriteRecLock]) to block until the provided // context is cancelled. // // This does not use real file locking. The lock can be released either @@ -181,7 +181,7 @@ func Z_ForTests_RestoreLocking() { } // Z_ForTests_SetMaxWaitTime sets the max time that we should wait before -// returning a failure in [WriteLock] and [WriteRecLock]. +// returning a failure in [WriteRecLock]. // // nolint:revive,nolintlint // We want to use underscores in the function name here. func Z_ForTests_SetMaxWaitTime(t *testing.T, maxWaitTime time.Duration) { From a3a7c0669b8323ceb37aad7f8911047d98a1b1d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 18 Jun 2025 16:23:23 +0200 Subject: [PATCH 0518/1670] users/tempentries/preauth: Improve golden file sanitizing Do not fully hide the actual user name, since it has not to be empty --- internal/users/tempentries/preauth.go | 5 ++++- internal/users/tempentries/preauth_test.go | 9 ++++++--- ...r_when_registering_a_pre-auth_user_with_the_same_name | 2 +- .../Successfully_register_a_pre-auth_user | 2 +- ...uth_user_if_the_first_generated_UID_is_already_in_use | 2 +- .../Successfully_get_a_user_by_ID_and_name | 2 +- 6 files changed, 14 insertions(+), 8 deletions(-) diff --git a/internal/users/tempentries/preauth.go b/internal/users/tempentries/preauth.go index e420bce1a0..11d26f6e5c 100644 --- a/internal/users/tempentries/preauth.go +++ b/internal/users/tempentries/preauth.go @@ -21,6 +21,9 @@ const ( // defined by UID_MIN and UID_MAX in the config file), otherwise finding a unique UID by trial and error can take // too long. MaxPreAuthUsers = 4096 + + // UserPrefix is the prefix used as login name by the pre-auth temporary users. + UserPrefix = "authd-pre-auth-user" ) type preAuthUser struct { @@ -208,7 +211,7 @@ func (r *preAuthUserRecords) addPreAuthUser(uid uint32, loginName string) (name if _, err := rand.Read(bytes); err != nil { return "", nil, fmt.Errorf("failed to generate random name: %w", err) } - name = fmt.Sprintf("authd-pre-auth-user-%x", bytes) + name = fmt.Sprintf("%s-%x", UserPrefix, bytes) user := preAuthUser{name: name, uid: uid, loginName: loginName} r.users[uid] = user diff --git a/internal/users/tempentries/preauth_test.go b/internal/users/tempentries/preauth_test.go index 67dc89e149..8d3dac6677 100644 --- a/internal/users/tempentries/preauth_test.go +++ b/internal/users/tempentries/preauth_test.go @@ -1,6 +1,7 @@ package tempentries import ( + "strings" "testing" "github.com/stretchr/testify/require" @@ -138,9 +139,11 @@ func TestPreAuthUserByIDAndName(t *testing.T) { func checkPreAuthUser(t *testing.T, user types.UserEntry) { t.Helper() - // The name field is randomly generated, so unset it before comparing the user with the golden file. - require.NotEmpty(t, user.Name, "Name should not be empty") - user.Name = "" + // The name field contains a randomly generated part, so we replace that part + // before comparing the user with the golden file. + require.True(t, strings.HasPrefix(user.Name, UserPrefix), + "Name should have %q prefix", UserPrefix) + user.Name = UserPrefix + "-XXXXXXXX" golden.CheckOrUpdateYAML(t, user) } diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUser/No_error_when_registering_a_pre-auth_user_with_the_same_name b/internal/users/tempentries/testdata/golden/TestPreAuthUser/No_error_when_registering_a_pre-auth_user_with_the_same_name index fa0bd512de..8b0eea6888 100644 --- a/internal/users/tempentries/testdata/golden/TestPreAuthUser/No_error_when_registering_a_pre-auth_user_with_the_same_name +++ b/internal/users/tempentries/testdata/golden/TestPreAuthUser/No_error_when_registering_a_pre-auth_user_with_the_same_name @@ -1,4 +1,4 @@ -name: "" +name: authd-pre-auth-user-XXXXXXXX uid: 12345 gid: 12345 gecos: test diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user index fa0bd512de..8b0eea6888 100644 --- a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user +++ b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user @@ -1,4 +1,4 @@ -name: "" +name: authd-pre-auth-user-XXXXXXXX uid: 12345 gid: 12345 gecos: test diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_in_use b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_in_use index fa0bd512de..8b0eea6888 100644 --- a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_in_use +++ b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_in_use @@ -1,4 +1,4 @@ -name: "" +name: authd-pre-auth-user-XXXXXXXX uid: 12345 gid: 12345 gecos: test diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUserByIDAndName/Successfully_get_a_user_by_ID_and_name b/internal/users/tempentries/testdata/golden/TestPreAuthUserByIDAndName/Successfully_get_a_user_by_ID_and_name index fa0bd512de..8b0eea6888 100644 --- a/internal/users/tempentries/testdata/golden/TestPreAuthUserByIDAndName/Successfully_get_a_user_by_ID_and_name +++ b/internal/users/tempentries/testdata/golden/TestPreAuthUserByIDAndName/Successfully_get_a_user_by_ID_and_name @@ -1,4 +1,4 @@ -name: "" +name: authd-pre-auth-user-XXXXXXXX uid: 12345 gid: 12345 gecos: test From 80e1893bf72aebc679103245e0855c86642c774b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 20 Jun 2025 17:00:35 +0200 Subject: [PATCH 0519/1670] testutils: Add utility function to compute the multiplied sleep duration We had one already in helpers but we may use it in more places now --- internal/testutils/args.go | 8 ++++++++ pam/integration-tests/helpers_test.go | 3 +-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/internal/testutils/args.go b/internal/testutils/args.go index 6ade22d0c2..949b314353 100644 --- a/internal/testutils/args.go +++ b/internal/testutils/args.go @@ -1,10 +1,12 @@ package testutils import ( + "math" "os" "runtime/debug" "strconv" "sync" + "time" ) var ( @@ -75,3 +77,9 @@ func SleepMultiplier() float64 { return sleepMultiplier } + +// MultipliedSleepDuration returns a duration multiplied by the sleep multiplier +// provided by [MultipliedSleepDuration]. +func MultipliedSleepDuration(in time.Duration) time.Duration { + return time.Duration(math.Round(float64(in) * SleepMultiplier())) +} diff --git a/pam/integration-tests/helpers_test.go b/pam/integration-tests/helpers_test.go index 8db286e2da..ad7386b17f 100644 --- a/pam/integration-tests/helpers_test.go +++ b/pam/integration-tests/helpers_test.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io/fs" - "math" "os" "os/exec" "path/filepath" @@ -329,7 +328,7 @@ func saveArtifactsForDebugOnCleanup(t *testing.T, artifacts []string) { } func sleepDuration(in time.Duration) time.Duration { - return time.Duration(math.Round(float64(in) * testutils.SleepMultiplier())) + return testutils.MultipliedSleepDuration(in) } // prependBinToPath returns the value of the GOPATH defined in go env prepended to PATH. From 0d0f07ec793aa2e07fe1dfb598d0e918824f9b63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 23 Jun 2025 05:37:00 +0200 Subject: [PATCH 0520/1670] users/localentries/localgroups: Drop unused functions We don't really use these cleanup functions, since they can just be implemented via the Update case, so let's just drop this test-only code --- internal/users/localentries/export_test.go | 7 - internal/users/localentries/localgroups.go | 128 +----------------- .../users/localentries/localgroups_test.go | 111 --------------- .../Cleans_up_user_from_group | 1 - .../Cleans_up_user_from_multiple_groups | 2 - .../Cleans_up_multiple_users_from_group | 3 - ...ans_up_multiple_users_from_multiple_groups | 6 - .../Cleans_up_user_from_group | 1 - .../Cleans_up_user_from_multiple_groups | 3 - ...rror_on_any_unignored_delete_gpasswd_error | 5 - .../localentries/testutils/localgroups.go | 12 +- 11 files changed, 6 insertions(+), 273 deletions(-) delete mode 100644 internal/users/localentries/testdata/golden/TestCleanUserFromlocalentries/Cleans_up_user_from_group delete mode 100644 internal/users/localentries/testdata/golden/TestCleanUserFromlocalentries/Cleans_up_user_from_multiple_groups delete mode 100644 internal/users/localentries/testdata/golden/TestCleanlocalentries/Cleans_up_multiple_users_from_group delete mode 100644 internal/users/localentries/testdata/golden/TestCleanlocalentries/Cleans_up_multiple_users_from_multiple_groups delete mode 100644 internal/users/localentries/testdata/golden/TestCleanlocalentries/Cleans_up_user_from_group delete mode 100644 internal/users/localentries/testdata/golden/TestCleanlocalentries/Cleans_up_user_from_multiple_groups delete mode 100644 internal/users/localentries/testdata/golden/TestCleanlocalentries/Error_on_any_unignored_delete_gpasswd_error diff --git a/internal/users/localentries/export_test.go b/internal/users/localentries/export_test.go index 45fdd38129..9a6ce34e07 100644 --- a/internal/users/localentries/export_test.go +++ b/internal/users/localentries/export_test.go @@ -13,10 +13,3 @@ func WithGpasswdCmd(cmds []string) Option { o.gpasswdCmd = cmds } } - -// WithGetUsersFunc overrides the getusers func with a custom one for tests. -func WithGetUsersFunc(getUsersFunc func() ([]string, error)) Option { - return func(o *options) { - o.getUsersFunc = getUsersFunc - } -} diff --git a/internal/users/localentries/localgroups.go b/internal/users/localentries/localgroups.go index e604b93564..86f05f5c99 100644 --- a/internal/users/localentries/localgroups.go +++ b/internal/users/localentries/localgroups.go @@ -4,7 +4,6 @@ package localentries import ( "bufio" "context" - "errors" "fmt" "os" "os/exec" @@ -21,15 +20,13 @@ import ( const GroupFile = "/etc/group" var defaultOptions = options{ - groupPath: GroupFile, - gpasswdCmd: []string{"gpasswd"}, - getUsersFunc: getPasswdUsernames, + groupPath: GroupFile, + gpasswdCmd: []string{"gpasswd"}, } type options struct { - groupPath string - gpasswdCmd []string - getUsersFunc func() ([]string, error) + groupPath string + gpasswdCmd []string } // Option represents an optional function to override UpdateLocalGroups default values. @@ -80,20 +77,6 @@ func Update(username string, newGroups []string, oldGroups []string, args ...Opt return nil } -// getPasswdUsernames gets the passwd entries and returns their usernames. -func getPasswdUsernames() ([]string, error) { - var usernames []string - entries, err := GetPasswdEntries() - if err != nil { - return nil, err - } - for _, e := range entries { - usernames = append(usernames, e.Name) - } - - return usernames, nil -} - // existingLocalGroups returns which groups from groupPath the user is part of. func existingLocalGroups(user, groupPath string) (groups []string, err error) { defer decorate.OnError(&err, "could not fetch existing local group") @@ -135,109 +118,6 @@ func existingLocalGroups(user, groupPath string) (groups []string, err error) { return groups, nil } -// CleanUser removes the user from all local groups. -func CleanUser(user string, args ...Option) (err error) { - defer decorate.OnError(&err, "could not clean user %q from local groups", user) - - opts := defaultOptions - for _, arg := range args { - arg(&opts) - } - - // Get the list of local groups the user belong to - groups, err := existingLocalGroups(user, opts.groupPath) - if err != nil { - return err - } - - localGroupsMu.Lock() - defer localGroupsMu.Unlock() - for _, group := range groups { - args := opts.gpasswdCmd[1:] - args = append(args, "--delete", user, group) - if err := runGPasswd(opts.gpasswdCmd[0], args...); err != nil { - return err - } - } - - return nil -} - -// Clean removes all unexistent users from the local groups. -func Clean(args ...Option) (err error) { - defer decorate.OnError(&err, "could not clean local groups completely") - - opts := defaultOptions - for _, arg := range args { - arg(&opts) - } - - localGroupsMu.Lock() - defer localGroupsMu.Unlock() - - // Add the existingUsers to a map to speed up search - existingUsers := make(map[string]struct{}) - usernames, err := opts.getUsersFunc() - if err != nil { - return err - } - for _, username := range usernames { - existingUsers[username] = struct{}{} - } - // If no username was returned, something probably went wrong during the getpwent call and we should stop, - // otherwise we would remove all users from the local groups. - if len(existingUsers) == 0 { - return errors.New("no existing users found, local groups won't be cleaned") - } - - // Get the list of local groups - f, err := os.Open(opts.groupPath) - if err != nil { - return err - } - defer f.Close() - - // Format of a line composing the group file is: - // group_name:password:group_id:user1,…,usern - var delOps [][]string - scanner := bufio.NewScanner(f) - for scanner.Scan() { - t := strings.TrimSpace(scanner.Text()) - if t == "" { - continue - } - elems := strings.Split(t, ":") - if len(elems) != 4 { - return fmt.Errorf("malformed entry in group file (should have 4 separators): %q", t) - } - - groupName := elems[0] - users := strings.Split(elems[3], ",") - for _, user := range users { - if _, ok := existingUsers[user]; ok { - continue - } - - // User doesn't exist anymore, remove it from the group - args := opts.gpasswdCmd[1:] - delOps = append(delOps, append(args, "--delete", user, groupName)) - } - } - if scanner.Err() != nil { - return scanner.Err() - } - f.Close() - - // Execute the deletion operations - for _, op := range delOps { - if cmdErr := runGPasswd(opts.gpasswdCmd[0], op...); cmdErr != nil { - err = errors.Join(err, cmdErr) - } - } - - return err -} - // runGPasswd is a wrapper to cmdName ignoring exit code 3, meaning that the group doesn't exist. // Note: it’s the same return code for user not existing, but it’s something we are in control of as we // are responsible for the user itself and parsing the output is not desired. diff --git a/internal/users/localentries/localgroups_test.go b/internal/users/localentries/localgroups_test.go index 02212455ef..c8a2d0a704 100644 --- a/internal/users/localentries/localgroups_test.go +++ b/internal/users/localentries/localgroups_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/stretchr/testify/require" - "github.com/ubuntu/authd/internal/testutils/golden" "github.com/ubuntu/authd/internal/users/localentries" localentriestestutils "github.com/ubuntu/authd/internal/users/localentries/testutils" ) @@ -86,116 +85,6 @@ func TestUpdatelocalentries(t *testing.T) { require.NoError(t, err, "Updatelocalentries should not have failed") } - localentriestestutils.RequireGPasswdOutput(t, destCmdsFile, golden.Path(t)) - }) - } -} - -func TestCleanlocalentries(t *testing.T) { - t.Parallel() - - tests := map[string]struct { - groupFilePath string - - getUsersReturn []string - - wantErr bool - }{ - "No-op_when_there_are_no_inactive_users": {groupFilePath: "user_in_many_groups.group"}, - "Cleans_up_user_from_group": {groupFilePath: "inactive_user_in_one_group.group"}, - "Cleans_up_user_from_multiple_groups": {groupFilePath: "inactive_user_in_many_groups.group"}, - "Cleans_up_multiple_users_from_group": {groupFilePath: "inactive_users_in_one_group.group"}, - "Cleans_up_multiple_users_from_multiple_groups": {groupFilePath: "inactive_users_in_many_groups.group"}, - - "Error_if_there_is_no_active_user": {groupFilePath: "user_in_many_groups.group", getUsersReturn: []string{}, wantErr: true}, - "Error_on_missing_groups_file": {groupFilePath: "does_not_exists.group", wantErr: true}, - "Error_when_groups_file_is_malformed": {groupFilePath: "malformed_file.group", wantErr: true}, - "Error_on_any_unignored_delete_gpasswd_error": {groupFilePath: "gpasswdfail_in_deleted_group.group", wantErr: true}, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - t.Parallel() - - destCmdsFile := filepath.Join(t.TempDir(), "gpasswd.output") - groupFilePath := filepath.Join("testdata", tc.groupFilePath) - gpasswdCmd := []string{"env", "GO_WANT_HELPER_PROCESS=1", - os.Args[0], "-test.run=TestMockgpasswd", "--", - groupFilePath, destCmdsFile, - } - - if tc.getUsersReturn == nil { - tc.getUsersReturn = []string{"myuser", "otheruser", "otheruser2", "otheruser3", "otheruser4"} - } - - cleanupOptions := []localentries.Option{ - localentries.WithGpasswdCmd(gpasswdCmd), - localentries.WithGroupPath(groupFilePath), - localentries.WithGetUsersFunc(func() ([]string, error) { return tc.getUsersReturn, nil }), - } - err := localentries.Clean(cleanupOptions...) - if tc.wantErr { - require.Error(t, err, "Cleanuplocalentries should have failed") - } else { - require.NoError(t, err, "Cleanuplocalentries should not have failed") - } - - localentriestestutils.RequireGPasswdOutput(t, destCmdsFile, golden.Path(t)) - }) - } -} - -func TestCleanUserFromlocalentries(t *testing.T) { - t.Parallel() - - tests := map[string]struct { - username string - - groupFilePath string - wantMockFailure bool - - wantErr bool - }{ - "Cleans_up_user_from_group": {}, - "Cleans_up_user_from_multiple_groups": {groupFilePath: "user_in_many_groups.group"}, - "No_op_if_user_does_not_belong_to_any_groups": {username: "groupless"}, - - "Error_on_missing_groups_file": {groupFilePath: "does_not_exists.group", wantErr: true}, - "Error_when_groups_file_is_malformed": {groupFilePath: "malformed_file.group", wantErr: true}, - "Error_on_any_unignored_delete_gpasswd_error": {wantMockFailure: true, wantErr: true}, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - t.Parallel() - - if tc.username == "" { - tc.username = "myuser" - } - if tc.groupFilePath == "" { - tc.groupFilePath = "user_in_one_group.group" - } - - destCmdsFile := filepath.Join(t.TempDir(), "gpasswd.output") - groupFilePath := filepath.Join("testdata", tc.groupFilePath) - gpasswdCmd := []string{"env", "GO_WANT_HELPER_PROCESS=1", - os.Args[0], "-test.run=TestMockgpasswd", "--", - groupFilePath, destCmdsFile, - } - if tc.wantMockFailure { - gpasswdCmd = append(gpasswdCmd, "gpasswdfail") - } - - cleanupOptions := []localentries.Option{ - localentries.WithGpasswdCmd(gpasswdCmd), - localentries.WithGroupPath(groupFilePath), - } - err := localentries.CleanUser(tc.username, cleanupOptions...) - if tc.wantErr { - require.Error(t, err, "CleanUserFromlocalentries should have failed") - } else { - require.NoError(t, err, "CleanUserFromlocalentries should not have failed") - } - - localentriestestutils.RequireGPasswdOutput(t, destCmdsFile, golden.Path(t)) }) } } diff --git a/internal/users/localentries/testdata/golden/TestCleanUserFromlocalentries/Cleans_up_user_from_group b/internal/users/localentries/testdata/golden/TestCleanUserFromlocalentries/Cleans_up_user_from_group deleted file mode 100644 index cf402a20b0..0000000000 --- a/internal/users/localentries/testdata/golden/TestCleanUserFromlocalentries/Cleans_up_user_from_group +++ /dev/null @@ -1 +0,0 @@ ---delete myuser localgroup1 \ No newline at end of file diff --git a/internal/users/localentries/testdata/golden/TestCleanUserFromlocalentries/Cleans_up_user_from_multiple_groups b/internal/users/localentries/testdata/golden/TestCleanUserFromlocalentries/Cleans_up_user_from_multiple_groups deleted file mode 100644 index cf2886bdff..0000000000 --- a/internal/users/localentries/testdata/golden/TestCleanUserFromlocalentries/Cleans_up_user_from_multiple_groups +++ /dev/null @@ -1,2 +0,0 @@ ---delete myuser localgroup1 ---delete myuser localgroup2 \ No newline at end of file diff --git a/internal/users/localentries/testdata/golden/TestCleanlocalentries/Cleans_up_multiple_users_from_group b/internal/users/localentries/testdata/golden/TestCleanlocalentries/Cleans_up_multiple_users_from_group deleted file mode 100644 index 541c95018c..0000000000 --- a/internal/users/localentries/testdata/golden/TestCleanlocalentries/Cleans_up_multiple_users_from_group +++ /dev/null @@ -1,3 +0,0 @@ ---delete inactiveuser localgroup1 ---delete inactiveuser2 localgroup1 ---delete inactiveuser3 localgroup1 \ No newline at end of file diff --git a/internal/users/localentries/testdata/golden/TestCleanlocalentries/Cleans_up_multiple_users_from_multiple_groups b/internal/users/localentries/testdata/golden/TestCleanlocalentries/Cleans_up_multiple_users_from_multiple_groups deleted file mode 100644 index 3be8a9f0f4..0000000000 --- a/internal/users/localentries/testdata/golden/TestCleanlocalentries/Cleans_up_multiple_users_from_multiple_groups +++ /dev/null @@ -1,6 +0,0 @@ ---delete inactiveuser localgroup2 ---delete inactiveuser localgroup3 ---delete inactiveuser2 localgroup1 ---delete inactiveuser2 localgroup3 ---delete inactiveuser3 localgroup1 ---delete inactiveuser3 localgroup2 \ No newline at end of file diff --git a/internal/users/localentries/testdata/golden/TestCleanlocalentries/Cleans_up_user_from_group b/internal/users/localentries/testdata/golden/TestCleanlocalentries/Cleans_up_user_from_group deleted file mode 100644 index 1cb5f6df8f..0000000000 --- a/internal/users/localentries/testdata/golden/TestCleanlocalentries/Cleans_up_user_from_group +++ /dev/null @@ -1 +0,0 @@ ---delete inactiveuser localgroup1 \ No newline at end of file diff --git a/internal/users/localentries/testdata/golden/TestCleanlocalentries/Cleans_up_user_from_multiple_groups b/internal/users/localentries/testdata/golden/TestCleanlocalentries/Cleans_up_user_from_multiple_groups deleted file mode 100644 index f792ff0282..0000000000 --- a/internal/users/localentries/testdata/golden/TestCleanlocalentries/Cleans_up_user_from_multiple_groups +++ /dev/null @@ -1,3 +0,0 @@ ---delete inactiveuser localgroup1 ---delete inactiveuser localgroup3 ---delete inactiveuser localgroup4 \ No newline at end of file diff --git a/internal/users/localentries/testdata/golden/TestCleanlocalentries/Error_on_any_unignored_delete_gpasswd_error b/internal/users/localentries/testdata/golden/TestCleanlocalentries/Error_on_any_unignored_delete_gpasswd_error deleted file mode 100644 index dac35797d5..0000000000 --- a/internal/users/localentries/testdata/golden/TestCleanlocalentries/Error_on_any_unignored_delete_gpasswd_error +++ /dev/null @@ -1,5 +0,0 @@ ---delete cloudgroup1 ---delete cloudgroup2 ---delete localgroup1 ---delete localgroup3 ---delete localgroup4 \ No newline at end of file diff --git a/internal/users/localentries/testutils/localgroups.go b/internal/users/localentries/testutils/localgroups.go index b63cd27e2d..9160a928a5 100644 --- a/internal/users/localentries/testutils/localgroups.go +++ b/internal/users/localentries/testutils/localgroups.go @@ -1,13 +1,7 @@ // Package localgrouptestutils export users test functionalities used by other packages to change cmdline and group file. package localgrouptestutils -//nolint:gci // We import unsafe as it is needed for go:linkname, but the nolint comment confuses gofmt and it adds -// a blank space between the imports, which creates problems with gci so we need to ignore it. import ( - - //nolint:revive,nolintlint // needed for go:linkname, but only used in tests. nolintlint as false positive then. - _ "unsafe" - "github.com/ubuntu/authd/internal/testsdetection" ) @@ -17,11 +11,9 @@ func init() { } var ( - //go:linkname defaultOptions github.com/ubuntu/authd/internal/users/localentries.defaultOptions defaultOptions struct { - groupPath string - gpasswdCmd []string - getUsersFunc func() []string + groupPath string + gpasswdCmd []string } ) From 7445adc24844152a46a8e7ec13703f242ff2e8cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Sat, 21 Jun 2025 16:58:00 +0200 Subject: [PATCH 0521/1670] users/localentries: Reuse generic types for user and group entries We already have types to handle this, no need to have specific ones --- internal/users/localentries/getgrent_c.go | 21 ++++++++------------ internal/users/localentries/getgrent_test.go | 3 ++- internal/users/localentries/getpwent_c.go | 20 +++++++------------ internal/users/localentries/getpwent_test.go | 3 ++- 4 files changed, 19 insertions(+), 28 deletions(-) diff --git a/internal/users/localentries/getgrent_c.go b/internal/users/localentries/getgrent_c.go index acdee706f7..87436b208b 100644 --- a/internal/users/localentries/getgrent_c.go +++ b/internal/users/localentries/getgrent_c.go @@ -19,20 +19,15 @@ import ( "syscall" "unsafe" + "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/decorate" ) -// Group represents a group entry. -type Group struct { - Name string - GID uint32 - Passwd string -} - +// types.GroupEntry represents a group entry. var getgrentMu sync.Mutex // GetGroupEntries returns all group entries. -func GetGroupEntries() (entries []Group, err error) { +func GetGroupEntries() (entries []types.GroupEntry, err error) { decorate.OnError(&err, "getgrent_r") // This function repeatedly calls getgrent_r, which iterates over the records in the group database. @@ -71,7 +66,7 @@ func GetGroupEntries() (entries []Group, err error) { return nil, errno } - entries = append(entries, Group{ + entries = append(entries, types.GroupEntry{ Name: C.GoString(groupPtr.gr_name), Passwd: C.GoString(groupPtr.gr_passwd), GID: uint32(groupPtr.gr_gid), @@ -83,7 +78,7 @@ func GetGroupEntries() (entries []Group, err error) { var ErrGroupNotFound = errors.New("group not found") // GetGroupByName returns the group with the given name. -func GetGroupByName(name string) (g Group, err error) { +func GetGroupByName(name string) (g types.GroupEntry, err error) { decorate.OnError(&err, "getgrnam_r") var group C.struct_group @@ -113,13 +108,13 @@ func GetGroupByName(name string) (g Group, err error) { errors.Is(errno, syscall.ESRCH) || errors.Is(errno, syscall.EBADF) || errors.Is(errno, syscall.EPERM) { - return Group{}, ErrGroupNotFound + return types.GroupEntry{}, ErrGroupNotFound } if !errors.Is(errno, syscall.Errno(0)) { - return Group{}, errno + return types.GroupEntry{}, errno } - return Group{ + return types.GroupEntry{ Name: C.GoString(groupPtr.gr_name), GID: uint32(groupPtr.gr_gid), Passwd: C.GoString(groupPtr.gr_passwd), diff --git a/internal/users/localentries/getgrent_test.go b/internal/users/localentries/getgrent_test.go index c8a6e9558c..73c72bd40b 100644 --- a/internal/users/localentries/getgrent_test.go +++ b/internal/users/localentries/getgrent_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/ubuntu/authd/internal/users/types" ) func TestGetGroupEntries(t *testing.T) { @@ -19,7 +20,7 @@ func TestGetGroupEntries(t *testing.T) { got, err := GetGroupEntries() require.NoError(t, err, "GetGroupEntries should never return an error") require.NotEmpty(t, got, "GetGroupEntries should never return an empty list") - require.True(t, slices.ContainsFunc(got, func(g Group) bool { + require.True(t, slices.ContainsFunc(got, func(g types.GroupEntry) bool { return g.Name == "root" && g.GID == 0 }), "GetGroupEntries should return root") }) diff --git a/internal/users/localentries/getpwent_c.go b/internal/users/localentries/getpwent_c.go index cf4d96768a..60d16d0a64 100644 --- a/internal/users/localentries/getpwent_c.go +++ b/internal/users/localentries/getpwent_c.go @@ -17,20 +17,14 @@ import ( "syscall" "unsafe" + "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/decorate" ) -// Passwd represents a passwd entry. -type Passwd struct { - Name string - UID uint32 - Gecos string -} - var getpwentMu sync.Mutex // GetPasswdEntries returns all passwd entries. -func GetPasswdEntries() (entries []Passwd, err error) { +func GetPasswdEntries() (entries []types.UserEntry, err error) { decorate.OnError(&err, "getpwent_r") // This function repeatedly calls getpwent_r, which iterates over the records in the passwd database. @@ -69,7 +63,7 @@ func GetPasswdEntries() (entries []Passwd, err error) { return nil, errno } - entries = append(entries, Passwd{ + entries = append(entries, types.UserEntry{ Name: C.GoString(passwdPtr.pw_name), UID: uint32(passwdPtr.pw_uid), Gecos: C.GoString(passwdPtr.pw_gecos), @@ -81,7 +75,7 @@ func GetPasswdEntries() (entries []Passwd, err error) { var ErrUserNotFound = errors.New("user not found") // GetPasswdByName returns the user with the given name. -func GetPasswdByName(name string) (p Passwd, err error) { +func GetPasswdByName(name string) (p types.UserEntry, err error) { decorate.OnError(&err, "getgrnam_r") var passwd C.struct_passwd @@ -111,13 +105,13 @@ func GetPasswdByName(name string) (p Passwd, err error) { errors.Is(errno, syscall.ESRCH) || errors.Is(errno, syscall.EBADF) || errors.Is(errno, syscall.EPERM) { - return Passwd{}, ErrUserNotFound + return types.UserEntry{}, ErrUserNotFound } if !errors.Is(errno, syscall.Errno(0)) { - return Passwd{}, errno + return types.UserEntry{}, errno } - return Passwd{ + return types.UserEntry{ Name: C.GoString(passwdPtr.pw_name), UID: uint32(passwdPtr.pw_uid), Gecos: C.GoString(passwdPtr.pw_gecos), diff --git a/internal/users/localentries/getpwent_test.go b/internal/users/localentries/getpwent_test.go index 163c22d452..b7b6f20a6f 100644 --- a/internal/users/localentries/getpwent_test.go +++ b/internal/users/localentries/getpwent_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/ubuntu/authd/internal/users/types" ) func TestGetPasswdEntries(t *testing.T) { @@ -20,7 +21,7 @@ func TestGetPasswdEntries(t *testing.T) { require.NotEmpty(t, got, "GetPasswdEntries should never return an empty list") // Check if the root user is present in the list - rootFound := slices.ContainsFunc(got, func(entry Passwd) bool { + rootFound := slices.ContainsFunc(got, func(entry types.UserEntry) bool { return entry.Name == "root" && entry.UID == 0 }) require.True(t, rootFound, "GetPasswdEntries should always return root") From d51ad646492221f69c5c6ac2741dc29f383886fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 23 Jun 2025 21:09:23 +0200 Subject: [PATCH 0522/1670] sliceutils: Add utility function to map a slice to other content It's often use needed to convert slices to different types or subtypes but we don't have a quick way to do it and we end up always repeating the same loops, so add a simple generic way to do it --- internal/sliceutils/sliceutils.go | 13 ++++++++++ internal/sliceutils/sliceutils_test.go | 35 ++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/internal/sliceutils/sliceutils.go b/internal/sliceutils/sliceutils.go index 2276e959ee..3fa81ac464 100644 --- a/internal/sliceutils/sliceutils.go +++ b/internal/sliceutils/sliceutils.go @@ -32,3 +32,16 @@ func Intersection[T comparable](a, b []T) []T { } return intersection } + +// Map maps the slice to another slice of the same size, using the provided function. +func Map[T any, S ~[]E, E any](a S, f func(E) T) []T { + if a == nil { + return nil + } + + mapped := make([]T, 0, len(a)) + for _, v := range a { + mapped = append(mapped, f(v)) + } + return mapped +} diff --git a/internal/sliceutils/sliceutils_test.go b/internal/sliceutils/sliceutils_test.go index 708d4d64fa..bc0e41def6 100644 --- a/internal/sliceutils/sliceutils_test.go +++ b/internal/sliceutils/sliceutils_test.go @@ -82,3 +82,38 @@ func TestIntersection(t *testing.T) { }) } } + +func TestMap(t *testing.T) { + t.Parallel() + + type intStruct struct { + i int + } + + tests := map[string]struct { + a []intStruct + want []int + }{ + "test_mapping_a_slice": { + a: []intStruct{{1}, {2}, {3}, {4}, {5}}, + want: []int{1, 2, 3, 4, 5}, + }, + "test_mapping_an empty_slice": { + a: []intStruct{}, + want: []int{}, + }, + "test_mapping_a_nil_slice": { + a: []intStruct(nil), + want: []int(nil), + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := sliceutils.Map(tc.a, func(s intStruct) int { return s.i }) + require.Equal(t, tc.want, got) + }) + } +} From bd0904cff588794018165051af1ad7050c878d33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 24 Jun 2025 18:21:25 +0200 Subject: [PATCH 0523/1670] users/types: Add validation checks for GroupEntry We need to check the values are safe to use before writing them, so let's add a simple function to check the basic group values --- internal/sliceutils/sliceutils.go | 31 +++ internal/sliceutils/sliceutils_test.go | 82 ++++++ internal/users/types/groupentry.go | 89 ++++++ internal/users/types/groupentry_test.go | 343 ++++++++++++++++++++++++ 4 files changed, 545 insertions(+) create mode 100644 internal/users/types/groupentry.go create mode 100644 internal/users/types/groupentry_test.go diff --git a/internal/sliceutils/sliceutils.go b/internal/sliceutils/sliceutils.go index 3fa81ac464..cc3d465227 100644 --- a/internal/sliceutils/sliceutils.go +++ b/internal/sliceutils/sliceutils.go @@ -1,6 +1,8 @@ // Package sliceutils provides utility functions for slices. package sliceutils +import "slices" + // Difference returns a slice with the elements that are in a but not in b. func Difference[T comparable](a, b []T) []T { setB := make(map[T]struct{}, len(b)) @@ -45,3 +47,32 @@ func Map[T any, S ~[]E, E any](a S, f func(E) T) []T { } return mapped } + +// EqualContent compares two slices, ensuring that their content is equal. +func EqualContent[S ~[]E, E comparable](a S, b S) bool { + if len(a) != len(b) { + return false + } + + for _, av := range a { + if !slices.Contains(b, av) { + return false + } + } + return true +} + +// EqualContentFunc compares two slices, ensuring that their content is equal +// using the provided function to compare. +func EqualContentFunc[S ~[]E, E any](a S, b S, f func(E, E) bool) bool { + if len(a) != len(b) { + return false + } + + for _, av := range a { + if !slices.ContainsFunc(b, func(bv E) bool { return f(av, bv) }) { + return false + } + } + return true +} diff --git a/internal/sliceutils/sliceutils_test.go b/internal/sliceutils/sliceutils_test.go index bc0e41def6..72a8feecc5 100644 --- a/internal/sliceutils/sliceutils_test.go +++ b/internal/sliceutils/sliceutils_test.go @@ -117,3 +117,85 @@ func TestMap(t *testing.T) { }) } } + +func TestEqualsContentFunc(t *testing.T) { + t.Parallel() + + type notComparable struct { + i int + ii []int + } + + notComparableCompareFunc := func(a, b notComparable) bool { + return a.i == b.i && sliceutils.EqualContent(a.ii, b.ii) + } + + tests := map[string]struct { + a, b []notComparable + want bool + }{ + "equal_slices_same_order": { + a: []notComparable{{i: 1}, {i: 2}, {i: 3}}, + b: []notComparable{{i: 1}, {i: 2}, {i: 3}}, + want: true, + }, + "equal_slices_different_order": { + a: []notComparable{{i: 1}, {i: 2}, {i: 3}}, + b: []notComparable{{i: 3}, {i: 1}, {i: 2}}, + want: true, + }, + "equal_with_nested_slices": { + a: []notComparable{{i: 1, ii: []int{1, 2}}, {i: 2, ii: []int{3}}}, + b: []notComparable{{i: 2, ii: []int{3}}, {i: 1, ii: []int{1, 2}}}, + want: true, + }, + "equal_with_nested_slices_with_different_order": { + a: []notComparable{{i: 1, ii: []int{1, 2}}, {i: 2, ii: []int{3}}}, + b: []notComparable{{i: 2, ii: []int{3}}, {i: 1, ii: []int{2, 1}}}, + want: true, + }, + "both_empty": { + a: []notComparable{}, + b: []notComparable{}, + want: true, + }, + "both_nil": { + a: nil, + b: nil, + want: true, + }, + "nil_and_empty": { + a: nil, + b: []notComparable{}, + want: true, + }, + "not_equal_different_lengths": { + a: []notComparable{{i: 1}, {i: 2}}, + b: []notComparable{{i: 1}, {i: 2}, {i: 3}}, + want: false, + }, + "not_equal_different_content": { + a: []notComparable{{i: 1}, {i: 2}, {i: 3}}, + b: []notComparable{{i: 1}, {i: 2}, {i: 4}}, + want: false, + }, + "not_equal_with_nested_slices_with_different_content": { + a: []notComparable{{i: 1, ii: []int{1, 2}}, {i: 2, ii: []int{3}}}, + b: []notComparable{{i: 2, ii: []int{4}}, {i: 1, ii: []int{2, 1}}}, + want: false, + }, + "one_empty_one_nonempty": { + a: []notComparable{}, + b: []notComparable{{i: 1}}, + want: false, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + got := sliceutils.EqualContentFunc(tc.a, tc.b, notComparableCompareFunc) + require.Equal(t, tc.want, got) + }) + } +} diff --git a/internal/users/types/groupentry.go b/internal/users/types/groupentry.go new file mode 100644 index 0000000000..9437691671 --- /dev/null +++ b/internal/users/types/groupentry.go @@ -0,0 +1,89 @@ +package types + +import ( + "context" + "fmt" + "slices" + "strings" + + "github.com/ubuntu/authd/internal/sliceutils" + "github.com/ubuntu/authd/log" +) + +// Validate validates the group entry values. +func (g GroupEntry) Validate() error { + if g.Name == "" { + return fmt.Errorf("group %q cannot have empty name", g) + } + + if g.GID == 0 && g.Name != "root" { + return fmt.Errorf("only root group can have GID 0, not %q", g.Name) + } + + if strings.ContainsRune(g.Name, ',') { + return fmt.Errorf("group %q cannot contain ',' character", g.Name) + } + + if strings.ContainsRune(g.Passwd, ',') { + return fmt.Errorf("group %q passwd %q cannot contain ',' character", g.Name, g.Passwd) + } + + if slices.ContainsFunc(g.Users, func(u string) bool { return strings.ContainsRune(u, ',') }) { + return fmt.Errorf("group %q cannot contain users with ',' character (%v)", g, g.Users) + } + + return nil +} + +// Equals checks that two groups are equal. +func (g GroupEntry) Equals(other GroupEntry) bool { + return g.Name == other.Name && + g.GID == other.GID && + g.Passwd == other.Passwd && + sliceutils.EqualContent(g.Users, other.Users) +} + +// DeepCopy makes a deep copy of the group entry. +func (g GroupEntry) DeepCopy() GroupEntry { + g.Users = slices.Clone(g.Users) + return g +} + +// DeepCopyGroupEntries makes a deep copy of group entries. +func DeepCopyGroupEntries(groups []GroupEntry) []GroupEntry { + return sliceutils.Map(groups, func(g GroupEntry) GroupEntry { + return g.DeepCopy() + }) +} + +// ValidateGroupEntries validates a list of group entries, ensuring they respect +// the [GroupEntry.Validate] constraints and that the names and the GID are unique. +func ValidateGroupEntries(groups []GroupEntry) error { + groupNames := make(map[string]*GroupEntry, len(groups)) + groupIDs := make(map[uint32]*GroupEntry, len(groups)) + + for _, g := range groups { + if err := g.Validate(); err != nil { + return fmt.Errorf("Group %q is not valid: %w", g, err) + } + + if otherGroup, ok := groupNames[g.Name]; ok { + if g.Equals(*otherGroup) { + log.Debugf(context.Background(), + "Skipping group %q, it's a duplicate!", g) + continue + } + + return fmt.Errorf("group %q is duplicate", g.Name) + } + if otherGroup, ok := groupIDs[g.GID]; ok { + return fmt.Errorf("GID %d for group %q is duplicated by %q", + g.GID, g.Name, otherGroup.Name) + } + + groupNames[g.Name] = &g + groupIDs[g.GID] = &g + } + + return nil +} diff --git a/internal/users/types/groupentry_test.go b/internal/users/types/groupentry_test.go new file mode 100644 index 0000000000..0b75daf71a --- /dev/null +++ b/internal/users/types/groupentry_test.go @@ -0,0 +1,343 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestValidateGroupEntry(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + group GroupEntry + wantErr bool + }{ + { + name: "Valid_group", + group: GroupEntry{Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1", "user2"}}, + }, + { + name: "Valid_root_group", + group: GroupEntry{Name: "root", Passwd: "x"}, + }, + { + name: "Valid_group_with_empty_users_list", + group: GroupEntry{Name: "group1", GID: 1006, Passwd: "x", Users: []string{}}, + }, + + // Error cases. + { + name: "Error_on_empty_group", + wantErr: true, + }, + { + name: "Error_on_non_root_group_with_zero_GID", + group: GroupEntry{Name: "i-wish-i-was-root"}, + wantErr: true, + }, + { + name: "Error_on_empty_name", + group: GroupEntry{Name: "", GID: 1001, Passwd: "x", Users: []string{"user1"}}, + wantErr: true, + }, + { + name: "Error_on_name_contains_comma", + group: GroupEntry{Name: "ad,mins", GID: 1002, Passwd: "x", Users: []string{"user1"}}, + wantErr: true, + }, + { + name: "Error_on_passwd_contains_comma", + group: GroupEntry{Name: "group1", GID: 1003, Passwd: "x,", Users: []string{"user1"}}, + wantErr: true, + }, + { + name: "Error_on_user_contains_comma", + group: GroupEntry{Name: "group1", GID: 1004, Passwd: "x", Users: []string{"al,ice"}}, + wantErr: true, + }, + { + name: "Error_on_multiple_users_one_with_comma", + group: GroupEntry{Name: "group1", GID: 1005, Passwd: "x", Users: []string{"user1", "b,ob"}}, + wantErr: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + err := tc.group.Validate() + if tc.wantErr { + require.Error(t, err, "Validate should return error but it did not") + return + } + + require.NoError(t, err, "Validate should not return error but it did") + }) + } +} + +func TestGroupEntryEquals(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + a GroupEntry + b GroupEntry + want bool + }{ + { + name: "Equal_groups_all_fields", + a: GroupEntry{Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1", "user2"}}, + b: GroupEntry{Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1", "user2"}}, + want: true, + }, + { + name: "Different_Users_order", + a: GroupEntry{Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1", "user2"}}, + b: GroupEntry{Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user2", "user1"}}, + want: true, + }, + { + name: "Both_empty_users", + a: GroupEntry{Name: "group1", GID: 1000, Passwd: "x", Users: []string{}}, + b: GroupEntry{Name: "group1", GID: 1000, Passwd: "x", Users: []string{}}, + want: true, + }, + { + name: "Both_empty", + a: GroupEntry{}, + b: GroupEntry{}, + want: true, + }, + { + name: "Different_name", + a: GroupEntry{Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + b: GroupEntry{Name: "group2", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + want: false, + }, + { + name: "Different_GID", + a: GroupEntry{Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + b: GroupEntry{Name: "group1", GID: 1001, Passwd: "x", Users: []string{"user1"}}, + want: false, + }, + { + name: "Different_passwd", + a: GroupEntry{Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + b: GroupEntry{Name: "group1", GID: 1000, Passwd: "y", Users: []string{"user1"}}, + want: false, + }, + { + name: "Different_users", + a: GroupEntry{Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + b: GroupEntry{Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user2"}}, + want: false, + }, + { + name: "Different_multiple_users", + a: GroupEntry{Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1", "user2"}}, + b: GroupEntry{Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user2", "user3"}}, + want: false, + }, + { + name: "One_empty_one_filled", + a: GroupEntry{}, + b: GroupEntry{Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + want: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + got := tc.a.Equals(tc.b) + require.Equal(t, tc.want, got, "Equals not matching expected; want %v", tc.want) + }) + } +} + +func TestValidateGroupEntries(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + groups []GroupEntry + wantErr bool + }{ + { + name: "Valid_multiple_groups", + groups: []GroupEntry{ + {Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1", "user2"}}, + {Name: "group2", GID: 1001, Passwd: "x", Users: []string{"user3"}}, + }, + }, + { + name: "Valid_duplicate_group_structs_with_default_passwd", + groups: []GroupEntry{ + {Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + {Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + }, + }, + { + name: "Valid_duplicate_group_structs_with_unset_passwd", + groups: []GroupEntry{ + {Name: "group1", GID: 1000, Users: []string{"user1"}}, + {Name: "group1", GID: 1000, Users: []string{"user1"}}, + }, + }, + { + name: "Valid_empty_list", + groups: []GroupEntry{}, + }, + { + name: "Valid_root_group_GID_0", + groups: []GroupEntry{ + {Name: "root", GID: 0, Passwd: "x"}, + }, + }, + { + name: "Valid_group_with_empty_passwd", + groups: []GroupEntry{ + {Name: "group1", GID: 1002, Passwd: "", Users: []string{"user1"}}, + }, + }, + + // Error cases + { + name: "Error_duplicate_group_name_different_content", + groups: []GroupEntry{ + {Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + {Name: "group1", GID: 1001, Passwd: "x", Users: []string{"user2"}}, + }, + wantErr: true, + }, + { + name: "Error_duplicate_GID", + groups: []GroupEntry{ + {Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + {Name: "group2", GID: 1000, Passwd: "x", Users: []string{"user2"}}, + }, + wantErr: true, + }, + { + name: "Error_duplicate_group_structs_with_partial_default_passwd", + groups: []GroupEntry{ + {Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + {Name: "group1", GID: 1000, Users: []string{"user1"}}, + }, + wantErr: true, + }, + { + name: "Error_invalid_group_entry", + groups: []GroupEntry{ + {Name: "", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + }, + wantErr: true, + }, + { + name: "Error_non_root_group_GID_0", + groups: []GroupEntry{ + {Name: "group1", GID: 0, Passwd: "x"}, + }, + wantErr: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + err := ValidateGroupEntries(tc.groups) + if tc.wantErr { + require.Error(t, err, "ValidateGroupEntries should return error but did not") + return + } + + require.NoError(t, err, "ValidateGroupEntries should not return error but did") + }) + } +} + +func TestGroupEntryDeepCopy(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + original GroupEntry + }{ + { + name: "Empty_group", + original: GroupEntry{}, + }, + { + name: "Group_with_users", + original: GroupEntry{Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1", "user2"}}, + }, + { + name: "Group_with_empty_users", + original: GroupEntry{Name: "group2", GID: 1001, Passwd: "x", Users: []string{}}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + copied := tc.original.DeepCopy() + require.Equal(t, tc.original, copied, "DeepCopy should produce an equal struct") + + // Mutate the copy's Users slice and ensure original is not affected + if copied.Users != nil { + copied.Users = append(copied.Users, "newuser") + require.NotEqual(t, tc.original.Users, copied.Users, "Users slice should be independent after DeepCopy") + } + }) + } +} + +func TestDeepCopyGroupEntries(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + original []GroupEntry + }{ + { + name: "Empty_slice", + original: []GroupEntry{}, + }, + { + name: "Multiple_groups", + original: []GroupEntry{ + {Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + {Name: "group2", GID: 1001, Passwd: "y", Users: []string{"user2", "user3"}}, + }, + }, + { + name: "Groups_with_empty_users", + original: []GroupEntry{ + {Name: "group3", GID: 1002, Passwd: "z", Users: []string{}}, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + copied := DeepCopyGroupEntries(tc.original) + require.Equal(t, tc.original, copied, "DeepCopyGroupEntries should produce an equal slice") + + // Mutate the copy and ensure original is not affected + if len(copied) > 0 && len(copied[0].Users) > 0 { + copied[0].Users[0] = "mutated" + require.NotEqual(t, tc.original[0].Users[0], copied[0].Users[0], "Users slice should be independent after DeepCopyGroupEntries") + } + if len(copied) > 0 { + copied[0].Name = "mutated" + require.NotEqual(t, tc.original[0].Name, copied[0].Name, "Struct fields should be independent after DeepCopyGroupEntries") + } + }) + } +} From ba4f8672b62ca839072803d332e0366ade2fbe73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 23 Jun 2025 01:35:38 +0200 Subject: [PATCH 0524/1670] users/localentries: Parse user groups using types.UserEntry In this way we can reuse the same code in other places --- .../IsAuthenticated | 2 +- internal/users/localentries/localgroups.go | 59 +++++++++++++++---- .../users/localentries/localgroups_test.go | 21 +++++-- .../testdata/malformed_file_duplicated.group | 7 +++ .../testdata/malformed_file_invalid_gid.group | 6 ++ ...oup => malformed_file_missing_field.group} | 0 .../malformed_file_no_group_name.group | 6 ++ 7 files changed, 84 insertions(+), 17 deletions(-) create mode 100644 internal/users/localentries/testdata/malformed_file_duplicated.group create mode 100644 internal/users/localentries/testdata/malformed_file_invalid_gid.group rename internal/users/localentries/testdata/{malformed_file.group => malformed_file_missing_field.group} (100%) create mode 100644 internal/users/localentries/testdata/malformed_file_no_group_name.group diff --git a/internal/services/pam/testdata/golden/TestIsAuthenticated/Error_on_updating_local_groups_with_unexisting_file/IsAuthenticated b/internal/services/pam/testdata/golden/TestIsAuthenticated/Error_on_updating_local_groups_with_unexisting_file/IsAuthenticated index bfed5941be..8f862f03f0 100644 --- a/internal/services/pam/testdata/golden/TestIsAuthenticated/Error_on_updating_local_groups_with_unexisting_file/IsAuthenticated +++ b/internal/services/pam/testdata/golden/TestIsAuthenticated/Error_on_updating_local_groups_with_unexisting_file/IsAuthenticated @@ -1,4 +1,4 @@ FIRST CALL: access: msg: - err: can't check authentication: failed to update user "testisauthenticated/error_on_updating_local_groups_with_unexisting_file_separator_success_with_local_groups": could not update local groups for user "testisauthenticated/error_on_updating_local_groups_with_unexisting_file_separator_success_with_local_groups": could not fetch existing local group: open testdata/TestIsAuthenticated/does_not_exists.group: no such file or directory + err: can't check authentication: failed to update user "testisauthenticated/error_on_updating_local_groups_with_unexisting_file_separator_success_with_local_groups": could not update local groups for user "testisauthenticated/error_on_updating_local_groups_with_unexisting_file_separator_success_with_local_groups": could not fetch existing local group for user "testisauthenticated/error_on_updating_local_groups_with_unexisting_file_separator_success_with_local_groups": could not fetch existing local group: open testdata/TestIsAuthenticated/does_not_exists.group: no such file or directory diff --git a/internal/users/localentries/localgroups.go b/internal/users/localentries/localgroups.go index 86f05f5c99..811968948c 100644 --- a/internal/users/localentries/localgroups.go +++ b/internal/users/localentries/localgroups.go @@ -5,13 +5,16 @@ import ( "bufio" "context" "fmt" + "math" "os" "os/exec" "slices" + "strconv" "strings" "sync" "github.com/ubuntu/authd/internal/sliceutils" + "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/authd/log" "github.com/ubuntu/decorate" ) @@ -49,13 +52,17 @@ func Update(username string, newGroups []string, oldGroups []string, args ...Opt return err } + currentGroupsNames := sliceutils.Map(currentGroups, func(g types.GroupEntry) string { + return g.Name + }) + localGroupsMu.Lock() defer localGroupsMu.Unlock() - groupsToAdd := sliceutils.Difference(newGroups, currentGroups) + groupsToAdd := sliceutils.Difference(newGroups, currentGroupsNames) log.Debugf(context.TODO(), "Adding to local groups: %v", groupsToAdd) groupsToRemove := sliceutils.Difference(oldGroups, newGroups) // Only remove user from groups which they are part of - groupsToRemove = sliceutils.Intersection(groupsToRemove, currentGroups) + groupsToRemove = sliceutils.Intersection(groupsToRemove, currentGroupsNames) log.Debugf(context.TODO(), "Removing from local groups: %v", groupsToRemove) // Do all this in a goroutine as we don't want to hang. @@ -77,12 +84,11 @@ func Update(username string, newGroups []string, oldGroups []string, args ...Opt return nil } -// existingLocalGroups returns which groups from groupPath the user is part of. -func existingLocalGroups(user, groupPath string) (groups []string, err error) { +func parseLocalGroups(groupPath string) (groups []types.GroupEntry, err error) { defer decorate.OnError(&err, "could not fetch existing local group") - localGroupsMu.RLock() - defer localGroupsMu.RUnlock() + log.Debugf(context.Background(), "Reading groups from %q", groupPath) + f, err := os.Open(groupPath) if err != nil { return nil, err @@ -99,25 +105,54 @@ func existingLocalGroups(user, groupPath string) (groups []string, err error) { } elems := strings.Split(t, ":") if len(elems) != 4 { - return nil, fmt.Errorf("malformed entry in group file (should have 4 separators): %q", t) + return nil, fmt.Errorf("malformed entry in group file (should have 4 separators, got %d): %q", len(elems), t) } - n := elems[0] - users := strings.Split(elems[3], ",") - if !slices.Contains(users, user) { - continue + name, passwd, gidValue, usersValue := elems[0], elems[1], elems[2], elems[3] + + gid, err := strconv.ParseUint(gidValue, 10, 0) + if err != nil || gid > math.MaxUint32 { + return nil, fmt.Errorf("failed parsing entry %q, unexpected GID value", t) } - groups = append(groups, n) + var users []string + if usersValue != "" { + users = strings.Split(usersValue, ",") + } + + groups = append(groups, types.GroupEntry{ + Name: name, + Passwd: passwd, + GID: uint32(gid), + Users: users, + }) } if err := scanner.Err(); err != nil { return nil, err } + if err := types.ValidateGroupEntries(groups); err != nil { + return nil, err + } + return groups, nil } +// existingLocalGroups returns which groups from groupPath the user is part of. +func existingLocalGroups(user, groupPath string) (groups []types.GroupEntry, err error) { + defer decorate.OnError(&err, "could not fetch existing local group for user %q", user) + + groups, err = parseLocalGroups(groupPath) + if err != nil { + return nil, err + } + + return slices.DeleteFunc(groups, func(g types.GroupEntry) bool { + return !slices.Contains(g.Users, user) + }), nil +} + // runGPasswd is a wrapper to cmdName ignoring exit code 3, meaning that the group doesn't exist. // Note: it’s the same return code for user not existing, but it’s something we are in control of as we // are responsible for the user itself and parsing the output is not desired. diff --git a/internal/users/localentries/localgroups_test.go b/internal/users/localentries/localgroups_test.go index c8a2d0a704..563446a552 100644 --- a/internal/users/localentries/localgroups_test.go +++ b/internal/users/localentries/localgroups_test.go @@ -50,10 +50,23 @@ func TestUpdatelocalentries(t *testing.T) { "User_is_not_removed_from_groups_they_are_not_part_of": {newGroups: []string{}, oldGroups: []string{"localgroup2"}, groupFilePath: "user_in_one_group.group"}, // Error cases - "Error_on_missing_groups_file": {groupFilePath: "does_not_exists.group", wantErr: true}, - "Error_when_groups_file_is_malformed": {groupFilePath: "malformed_file.group", wantErr: true}, - "Error_on_any_unignored_add_gpasswd_error": {username: "gpasswdfail", groupFilePath: "no_users.group", wantErr: true}, - "Error_on_any_unignored_delete_gpasswd_error": {username: "gpasswdfail", groupFilePath: "gpasswdfail_in_deleted_group.group", wantErr: true}, + "Error_on_missing_groups_file": {groupFilePath: "does_not_exists.group", wantErr: true}, + "Error_when_groups_file_has_missing_fields": { + groupFilePath: "malformed_file_missing_field.group", + wantErr: true, + }, + "Error_when_groups_file_has_invalid_gid": { + groupFilePath: "malformed_file_invalid_gid.group", + wantErr: true, + }, + "Error_when_groups_file_has_no_group_name": { + groupFilePath: "malformed_file_no_group_name.group", + wantErr: true, + }, + "Error_when_groups_file_has_a_duplicated_group": { + groupFilePath: "malformed_file_duplicated.group", + wantErr: true, + }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { diff --git a/internal/users/localentries/testdata/malformed_file_duplicated.group b/internal/users/localentries/testdata/malformed_file_duplicated.group new file mode 100644 index 0000000000..df29880934 --- /dev/null +++ b/internal/users/localentries/testdata/malformed_file_duplicated.group @@ -0,0 +1,7 @@ +localgroup1:x:41:otheruser +localgroup2:x:42:otheruser2 +localgroup3:x:43:invalidGID +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 +localgroup4:x:123:again diff --git a/internal/users/localentries/testdata/malformed_file_invalid_gid.group b/internal/users/localentries/testdata/malformed_file_invalid_gid.group new file mode 100644 index 0000000000..5a55ebf584 --- /dev/null +++ b/internal/users/localentries/testdata/malformed_file_invalid_gid.group @@ -0,0 +1,6 @@ +localgroup1:x:41:otheruser +localgroup2:x:42:otheruser2 +localgroup3:x:gid:invalidGID +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/malformed_file.group b/internal/users/localentries/testdata/malformed_file_missing_field.group similarity index 100% rename from internal/users/localentries/testdata/malformed_file.group rename to internal/users/localentries/testdata/malformed_file_missing_field.group diff --git a/internal/users/localentries/testdata/malformed_file_no_group_name.group b/internal/users/localentries/testdata/malformed_file_no_group_name.group new file mode 100644 index 0000000000..1576cf04aa --- /dev/null +++ b/internal/users/localentries/testdata/malformed_file_no_group_name.group @@ -0,0 +1,6 @@ +localgroup1:x:41:otheruser +localgroup2:x:42:otheruser2 +:passwd-for-group-with-no-name:45: +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 From d2a4672d221b4583bf520a6338dc1f4c8427d927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 25 Jun 2025 08:41:25 +0200 Subject: [PATCH 0525/1670] users/localentries: Add missing fields from getgrnam/getgrent Fill the values so that the returned structs contain all the data we are expecting --- internal/users/localentries/getgrent_c.go | 18 ++++++++++++++++-- internal/users/localentries/getgrent_test.go | 13 +++++++++---- internal/users/localentries/getpwent_c.go | 2 -- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/internal/users/localentries/getgrent_c.go b/internal/users/localentries/getgrent_c.go index 87436b208b..8759d592d9 100644 --- a/internal/users/localentries/getgrent_c.go +++ b/internal/users/localentries/getgrent_c.go @@ -1,6 +1,4 @@ // Package localentries provides functions to access the local user and group database. -// -//nolint:dupl // This it not a duplicate of getpwent_c.go package localentries /* @@ -70,6 +68,7 @@ func GetGroupEntries() (entries []types.GroupEntry, err error) { Name: C.GoString(groupPtr.gr_name), Passwd: C.GoString(groupPtr.gr_passwd), GID: uint32(groupPtr.gr_gid), + Users: strvToSlice(groupPtr.gr_mem), }) } } @@ -118,6 +117,21 @@ func GetGroupByName(name string) (g types.GroupEntry, err error) { Name: C.GoString(groupPtr.gr_name), GID: uint32(groupPtr.gr_gid), Passwd: C.GoString(groupPtr.gr_passwd), + Users: strvToSlice(groupPtr.gr_mem), }, nil } } + +func strvToSlice(strv **C.char) []string { + var users []string + for i := C.uint(0); ; i++ { + s := *(**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(strv)) + + uintptr(i)*unsafe.Sizeof(*strv))) + if s == nil { + break + } + + users = append(users, C.GoString(s)) + } + return users +} diff --git a/internal/users/localentries/getgrent_test.go b/internal/users/localentries/getgrent_test.go index 73c72bd40b..02139019b9 100644 --- a/internal/users/localentries/getgrent_test.go +++ b/internal/users/localentries/getgrent_test.go @@ -23,6 +23,10 @@ func TestGetGroupEntries(t *testing.T) { require.True(t, slices.ContainsFunc(got, func(g types.GroupEntry) bool { return g.Name == "root" && g.GID == 0 }), "GetGroupEntries should return root") + + require.True(t, slices.ContainsFunc(got, func(g types.GroupEntry) bool { + return len(g.Users) > 0 + }), "GetGroupEntries should returns at least one group that have users") }) } } @@ -36,9 +40,10 @@ func TestGetGroupByName(t *testing.T) { got, err := GetGroupByName("root") require.NoError(t, err, "GetGroupByName should not return an error") - require.Equal(t, got.Name, "root") - require.Equal(t, got.GID, uint32(0)) - require.Equal(t, got.Passwd, "x") + require.Equal(t, got.Name, "root", "Name does not match") + require.Equal(t, got.GID, uint32(0), "GID does not match") + require.Equal(t, got.Passwd, "x", "Passwd does not match") + require.Empty(t, got.Users) }) } } @@ -52,7 +57,7 @@ func TestGetGroupByName_NotFound(t *testing.T) { got, err := GetGroupByName(fmt.Sprintf("nonexistent-really-%d", idx)) require.ErrorIs(t, err, ErrGroupNotFound) - require.Equal(t, got.Name, "") + require.Empty(t, got, "Entry should be empty, but is not") }) } } diff --git a/internal/users/localentries/getpwent_c.go b/internal/users/localentries/getpwent_c.go index 60d16d0a64..7b41117f04 100644 --- a/internal/users/localentries/getpwent_c.go +++ b/internal/users/localentries/getpwent_c.go @@ -1,6 +1,4 @@ // Package localentries provides functions to access local passwd entries. -// -//nolint:dupl // This it not a duplicate of getgrent_c.go package localentries /* From 26d20bddb63a5422ac63a59f220176e51b30cc51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 25 Jun 2025 18:35:06 +0200 Subject: [PATCH 0526/1670] users/localentries: Add missing fields from getpwnam/getpwent Fill the values so that the returned structs contain all the data we are expecting --- internal/users/localentries/getpwent_c.go | 6 ++++++ internal/users/localentries/getpwent_test.go | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/internal/users/localentries/getpwent_c.go b/internal/users/localentries/getpwent_c.go index 7b41117f04..54731c9382 100644 --- a/internal/users/localentries/getpwent_c.go +++ b/internal/users/localentries/getpwent_c.go @@ -64,7 +64,10 @@ func GetPasswdEntries() (entries []types.UserEntry, err error) { entries = append(entries, types.UserEntry{ Name: C.GoString(passwdPtr.pw_name), UID: uint32(passwdPtr.pw_uid), + GID: uint32(passwdPtr.pw_gid), Gecos: C.GoString(passwdPtr.pw_gecos), + Dir: C.GoString(passwdPtr.pw_dir), + Shell: C.GoString(passwdPtr.pw_shell), }) } } @@ -112,7 +115,10 @@ func GetPasswdByName(name string) (p types.UserEntry, err error) { return types.UserEntry{ Name: C.GoString(passwdPtr.pw_name), UID: uint32(passwdPtr.pw_uid), + GID: uint32(passwdPtr.pw_gid), Gecos: C.GoString(passwdPtr.pw_gecos), + Dir: C.GoString(passwdPtr.pw_dir), + Shell: C.GoString(passwdPtr.pw_shell), }, nil } } diff --git a/internal/users/localentries/getpwent_test.go b/internal/users/localentries/getpwent_test.go index b7b6f20a6f..8d6434b80e 100644 --- a/internal/users/localentries/getpwent_test.go +++ b/internal/users/localentries/getpwent_test.go @@ -22,7 +22,7 @@ func TestGetPasswdEntries(t *testing.T) { // Check if the root user is present in the list rootFound := slices.ContainsFunc(got, func(entry types.UserEntry) bool { - return entry.Name == "root" && entry.UID == 0 + return entry.Name == "root" && entry.UID == 0 && entry.GID == 0 }) require.True(t, rootFound, "GetPasswdEntries should always return root") }) @@ -40,7 +40,10 @@ func TestGetPasswdByName(t *testing.T) { require.NoError(t, err, "GetPasswdByName should not return an error") require.Equal(t, "root", got.Name, "Name does not match") require.Equal(t, uint32(0), got.UID, "UID does not match") + require.Equal(t, uint32(0), got.GID, "GID does not match") require.Equal(t, "root", got.Gecos, "Gecos does not match") + require.NotEmpty(t, got.Shell, "Shell is not empty") + require.Equal(t, "/root", got.Dir, "Dir does not match") }) } } From 605dc90a3eb7fa7ac02588c5993af7665965bfa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 23 Jun 2025 19:46:00 +0200 Subject: [PATCH 0527/1670] users/localentries/localgroups: Write groups manually taking the write lock While updating the local groups, we need to ensure that no other NSS source can act on them, so we need to take a lock on the pwd entries. Now, doing this implies that gpasswd cannot run meanwhile, thus we need to update the group file manually. To do this we are now directly parsing the group file and writing to it, following a similar strategy of gpasswd, so this includes making backups and using a temporary file --- cmd/authd/integrationtests.go | 16 +- internal/services/pam/pam_test.go | 19 +-- .../IsAuthenticated | 2 +- .../Update_local_groups.group | 6 + .../Update_local_groups.group.backup | 6 + .../Update_local_groups/gpasswd.output | 2 - internal/services/user/user_test.go | 28 ---- internal/testutils/daemon.go | 23 +++ internal/users/localentries/export_test.go | 21 ++- internal/users/localentries/localgroups.go | 148 +++++++++++++----- .../users/localentries/localgroups_test.go | 36 +++-- ...er_from_multiple_groups_with_one_remaining | 1 - ...m_multiple_groups_with_one_remaining.group | 6 + ...ple_groups_with_one_remaining.group.backup | 6 + .../Group_file_with_empty_line_is_ignored | 2 - ...roup_file_with_empty_line_is_ignored.group | 6 + ...e_with_empty_line_is_ignored.group.backup} | 3 +- ...les_with_multiple_other_users_in_our_group | 2 - ...th_multiple_other_users_in_our_group.group | 6 + ...iple_other_users_in_our_group.group.backup | 6 + ..._existing_files_with_no_users_in_our_group | 2 - ...ing_files_with_no_users_in_our_group.group | 6 + ...es_with_no_users_in_our_group.group.backup | 6 + ...isting_files_with_other_users_in_our_group | 2 - ..._files_with_other_users_in_our_group.group | 6 + ...with_other_users_in_our_group.group.backup | 6 + ...Insert_new_user_when_no_users_in_any_group | 2 - ..._new_user_when_no_users_in_any_group.group | 6 + ...er_when_no_users_in_any_group.group.backup | 6 + ...r_in_the_only_local_group_when_not_present | 1 - ...he_only_local_group_when_not_present.group | 6 + ..._local_group_when_not_present.group.backup | 6 + ..._group_when_not_present_even_with_multiple | 1 - ..._when_not_present_even_with_multiple.group | 6 + ...ot_present_even_with_multiple.group.backup | 6 + .../Missing_group_is_ignored | 1 - .../Missing_group_is_ignored.group | 5 + .../Missing_group_is_ignored.group.backup | 5 + ...s_added_to_group_they_were_added_to_before | 1 - ...d_to_group_they_were_added_to_before.group | 6 + ...oup_they_were_added_to_before.group.backup | 6 + ..._from_old_groups_but_not_from_other_groups | 1 - ...old_groups_but_not_from_other_groups.group | 6 + ...ups_but_not_from_other_groups.group.backup | 6 + internal/users/localentries/testutils.go | 32 ++-- .../users/localentries/testutils/gpasswd.go | 147 ----------------- .../localentries/testutils/localgroups.go | 77 ++++++++- internal/users/manager_test.go | 66 ++++---- ...te_user_updating_local_groups_with_changes | 20 +++ ...r_updating_local_groups_with_changes.group | 3 + ...ing_local_groups_with_changes.group.backup | 3 + .../groups/gpasswdfail_in_deleted_group.group | 2 - .../groups/user_mismatching_groups.group | 3 + internal/users/types/groupentry.go | 10 ++ internal/users/types/groupentry_test.go | 49 ++++++ nss/integration-tests/integration_test.go | 23 +-- pam/integration-tests/cli_test.go | 29 ++-- pam/integration-tests/helpers_test.go | 61 ++++---- pam/integration-tests/integration_test.go | 5 - pam/integration-tests/native_test.go | 25 ++- pam/integration-tests/ssh_test.go | 29 ++-- .../{gpasswd.group => groups} | 0 .../{gpasswd.group => groups} | 0 .../{gpasswd.group => groups} | 0 .../{gpasswd.group => groups} | 0 .../{gpasswd.group => groups} | 0 .../TestGdmModule/{gpasswd.group => groups} | 0 .../{gpasswd.group => groups} | 0 .../{gpasswd.group => groups} | 0 .../{gpasswd.group => groups} | 0 .../{gpasswd.group => groups} | 0 .../{gpasswd.group => groups} | 0 .../{gpasswd.group => groups} | 0 .../{gpasswd.group => groups} | 0 .../{gpasswd.group => groups} | 0 .../{gpasswd.group => groups} | 0 ...uthenticate_user_and_add_it_to_local_group | 6 +- ...user_and_add_it_to_local_group.gpasswd_out | 1 - ...icate_user_and_add_it_to_local_group.group | 1 + ...er_and_add_it_to_local_group.group.backup} | 0 ...ser_successfully_after_db_migration.groups | 6 - ..._with_mixed_case_after_db_migration.groups | 6 - ...using_lower_case_after_db_migration.groups | 6 - ...user_and_add_it_to_local_group.gpasswd_out | 1 - ...icate_user_and_add_it_to_local_group.group | 1 + ...er_and_add_it_to_local_group.group.backup} | 0 ...ser_successfully_after_db_migration.groups | 6 - ..._with_mixed_case_after_db_migration.groups | 6 - ...using_lower_case_after_db_migration.groups | 6 - ...user_and_add_it_to_local_group.gpasswd_out | 1 - ...icate_user_and_add_it_to_local_group.group | 1 + ...er_and_add_it_to_local_group.group.backup} | 0 ..._to_local_group_on_shared_SSHd.gpasswd_out | 1 - ...add_it_to_local_group_on_shared_SSHd.group | 1 + ...o_local_group_on_shared_SSHd.group.backup} | 0 ...ser_successfully_after_db_migration.groups | 6 - ...y_after_db_migration_on_shared_SSHd.groups | 6 - ..._with_mixed_case_after_db_migration.groups | 6 - ...e_after_db_migration_on_shared_SSHd.groups | 6 - ...using_lower_case_after_db_migration.groups | 6 - ...e_after_db_migration_on_shared_SSHd.groups | 6 - .../testdata/tapes/cli/local_group.tape | 2 +- 102 files changed, 624 insertions(+), 498 deletions(-) create mode 100644 internal/services/pam/testdata/golden/TestIsAuthenticated/Update_local_groups.group create mode 100644 internal/services/pam/testdata/golden/TestIsAuthenticated/Update_local_groups.group.backup delete mode 100644 internal/services/pam/testdata/golden/TestIsAuthenticated/Update_local_groups/gpasswd.output delete mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Add_and_remove_user_from_multiple_groups_with_one_remaining create mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Add_and_remove_user_from_multiple_groups_with_one_remaining.group create mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Add_and_remove_user_from_multiple_groups_with_one_remaining.group.backup delete mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Group_file_with_empty_line_is_ignored create mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Group_file_with_empty_line_is_ignored.group rename internal/users/localentries/testdata/{gpasswdfail_in_deleted_group.group => golden/TestUpdatelocalentries/Group_file_with_empty_line_is_ignored.group.backup} (76%) delete mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_multiple_other_users_in_our_group create mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_multiple_other_users_in_our_group.group create mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_multiple_other_users_in_our_group.group.backup delete mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_no_users_in_our_group create mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_no_users_in_our_group.group create mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_no_users_in_our_group.group.backup delete mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_other_users_in_our_group create mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_other_users_in_our_group.group create mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_other_users_in_our_group.group.backup delete mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_when_no_users_in_any_group create mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_when_no_users_in_any_group.group create mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_when_no_users_in_any_group.group.backup delete mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_user_in_the_only_local_group_when_not_present create mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_user_in_the_only_local_group_when_not_present.group create mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_user_in_the_only_local_group_when_not_present.group.backup delete mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_user_in_the_only_local_group_when_not_present_even_with_multiple create mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_user_in_the_only_local_group_when_not_present_even_with_multiple.group create mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_user_in_the_only_local_group_when_not_present_even_with_multiple.group.backup delete mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Missing_group_is_ignored create mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Missing_group_is_ignored.group create mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Missing_group_is_ignored.group.backup delete mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/User_is_added_to_group_they_were_added_to_before create mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/User_is_added_to_group_they_were_added_to_before.group create mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/User_is_added_to_group_they_were_added_to_before.group.backup delete mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/User_is_removed_from_old_groups_but_not_from_other_groups create mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/User_is_removed_from_old_groups_but_not_from_other_groups.group create mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/User_is_removed_from_old_groups_but_not_from_other_groups.group.backup delete mode 100644 internal/users/localentries/testutils/gpasswd.go create mode 100644 internal/users/testdata/golden/TestUpdateUser/Successfully_update_user_updating_local_groups_with_changes create mode 100644 internal/users/testdata/golden/TestUpdateUser/Successfully_update_user_updating_local_groups_with_changes.group create mode 100644 internal/users/testdata/golden/TestUpdateUser/Successfully_update_user_updating_local_groups_with_changes.group.backup delete mode 100644 internal/users/testdata/groups/gpasswdfail_in_deleted_group.group create mode 100644 internal/users/testdata/groups/user_mismatching_groups.group rename pam/integration-tests/testdata/TestCLIAuthenticate/Authenticate_user_successfully_after_db_migration/{gpasswd.group => groups} (100%) rename pam/integration-tests/testdata/TestCLIAuthenticate/Authenticate_user_with_mixed_case_after_db_migration/{gpasswd.group => groups} (100%) rename pam/integration-tests/testdata/TestCLIAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration/{gpasswd.group => groups} (100%) rename pam/integration-tests/testdata/TestCLIAuthenticate/{gpasswd.group => groups} (100%) rename pam/integration-tests/testdata/TestCLIChangeAuthTok/{gpasswd.group => groups} (100%) rename pam/integration-tests/testdata/TestGdmModule/{gpasswd.group => groups} (100%) rename pam/integration-tests/testdata/TestNativeAuthenticate/Authenticate_user_successfully_after_db_migration/{gpasswd.group => groups} (100%) rename pam/integration-tests/testdata/TestNativeAuthenticate/Authenticate_user_with_mixed_case_after_db_migration/{gpasswd.group => groups} (100%) rename pam/integration-tests/testdata/TestNativeAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration/{gpasswd.group => groups} (100%) rename pam/integration-tests/testdata/TestNativeAuthenticate/{gpasswd.group => groups} (100%) rename pam/integration-tests/testdata/TestNativeChangeAuthTok/{gpasswd.group => groups} (100%) rename pam/integration-tests/testdata/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration/{gpasswd.group => groups} (100%) rename pam/integration-tests/testdata/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration/{gpasswd.group => groups} (100%) rename pam/integration-tests/testdata/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration/{gpasswd.group => groups} (100%) rename pam/integration-tests/testdata/TestSSHAuthenticate/{gpasswd.group => groups} (100%) delete mode 100644 pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_and_add_it_to_local_group.gpasswd_out create mode 100644 pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_and_add_it_to_local_group.group rename pam/integration-tests/testdata/golden/TestCLIAuthenticate/{Authenticate_user_and_add_it_to_local_group.groups => Authenticate_user_and_add_it_to_local_group.group.backup} (100%) delete mode 100644 pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_successfully_after_db_migration.groups delete mode 100644 pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.groups delete mode 100644 pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.groups delete mode 100644 pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_and_add_it_to_local_group.gpasswd_out create mode 100644 pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_and_add_it_to_local_group.group rename pam/integration-tests/testdata/golden/TestNativeAuthenticate/{Authenticate_user_and_add_it_to_local_group.groups => Authenticate_user_and_add_it_to_local_group.group.backup} (100%) delete mode 100644 pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_successfully_after_db_migration.groups delete mode 100644 pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.groups delete mode 100644 pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.groups delete mode 100644 pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_and_add_it_to_local_group.gpasswd_out create mode 100644 pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_and_add_it_to_local_group.group rename pam/integration-tests/testdata/golden/TestSSHAuthenticate/{Authenticate_user_and_add_it_to_local_group.groups => Authenticate_user_and_add_it_to_local_group.group.backup} (100%) delete mode 100644 pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_and_add_it_to_local_group_on_shared_SSHd.gpasswd_out create mode 100644 pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_and_add_it_to_local_group_on_shared_SSHd.group rename pam/integration-tests/testdata/golden/TestSSHAuthenticate/{Authenticate_user_and_add_it_to_local_group_on_shared_SSHd.groups => Authenticate_user_and_add_it_to_local_group_on_shared_SSHd.group.backup} (100%) delete mode 100644 pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration.groups delete mode 100644 pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration_on_shared_SSHd.groups delete mode 100644 pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.groups delete mode 100644 pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration_on_shared_SSHd.groups delete mode 100644 pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.groups delete mode 100644 pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration_on_shared_SSHd.groups diff --git a/cmd/authd/integrationtests.go b/cmd/authd/integrationtests.go index 85c1e86ff0..63edada1d2 100644 --- a/cmd/authd/integrationtests.go +++ b/cmd/authd/integrationtests.go @@ -5,8 +5,8 @@ package main import ( + "fmt" "os" - "strings" "github.com/ubuntu/authd/internal/services/permissions" "github.com/ubuntu/authd/internal/testsdetection" @@ -23,13 +23,15 @@ func init() { permissions.Z_ForTests_DefaultCurrentUserAsRoot() } - gpasswdArgs := os.Getenv("AUTHD_INTEGRATIONTESTS_GPASSWD_ARGS") - grpFilePath := os.Getenv("AUTHD_INTEGRATIONTESTS_GPASSWD_GRP_FILE_PATH") - if gpasswdArgs == "" || grpFilePath == "" { - panic("AUTHD_INTEGRATIONTESTS_GPASSWD_ARGS and AUTHD_INTEGRATIONTESTS_GPASSWD_GRP_FILE_PATH must be set") + grpFilePath := os.Getenv(localentries.Z_ForTests_GroupFilePathEnv) + if grpFilePath == "" { + panic(fmt.Sprintf("%q must be set", localentries.Z_ForTests_GroupFilePathEnv)) } - localentries.Z_ForTests_SetGpasswdCmd(strings.Split(gpasswdArgs, " ")) - localentries.Z_ForTests_SetGroupPath(grpFilePath) + grpFileOutputPath := os.Getenv(localentries.Z_ForTests_GroupFileOutputPathEnv) + if grpFileOutputPath == "" { + grpFileOutputPath = grpFilePath + } + localentries.Z_ForTests_SetGroupPath(grpFilePath, grpFileOutputPath) db.Z_ForTests_SetGroupFile(grpFilePath) userslocking.Z_ForTests_OverrideLocking() diff --git a/internal/services/pam/pam_test.go b/internal/services/pam/pam_test.go index f900139617..4726d55b76 100644 --- a/internal/services/pam/pam_test.go +++ b/internal/services/pam/pam_test.go @@ -28,6 +28,7 @@ import ( "github.com/ubuntu/authd/internal/users/db" "github.com/ubuntu/authd/internal/users/idgenerator" localgroupstestutils "github.com/ubuntu/authd/internal/users/localentries/testutils" + userslocking "github.com/ubuntu/authd/internal/users/locking" userstestutils "github.com/ubuntu/authd/internal/users/testutils" "github.com/ubuntu/authd/log" "google.golang.org/grpc" @@ -452,9 +453,10 @@ func TestIsAuthenticated(t *testing.T) { t.Parallel() } - var destCmdsFile string + var destGroupFile string if tc.localGroupsFile != "" { - destCmdsFile = localgroupstestutils.SetupGPasswdMock(t, filepath.Join(testutils.TestFamilyPath(t), tc.localGroupsFile)) + destGroupFile = localgroupstestutils.SetupGroupMock(t, + filepath.Join(testutils.TestFamilyPath(t), tc.localGroupsFile)) } dbDir := t.TempDir() @@ -539,7 +541,7 @@ func TestIsAuthenticated(t *testing.T) { require.NoError(t, err, "Setup: failed to dump database for comparing") golden.CheckOrUpdate(t, gotDB, golden.WithPath("cache.db")) - localgroupstestutils.RequireGPasswdOutput(t, destCmdsFile, filepath.Join(golden.Path(t), "gpasswd.output")) + localgroupstestutils.RequireGroupFile(t, destGroupFile, golden.Path(t)) }) } } @@ -700,10 +702,6 @@ func TestEndSession(t *testing.T) { } } -func TestMockgpasswd(t *testing.T) { - localgroupstestutils.Mockgpasswd(t) -} - // initBrokers starts dbus mock brokers on the system bus. It returns its config path. func initBrokers() (brokerConfigPath string, cleanup func(), err error) { tmpDir, err := os.MkdirTemp("", "authd-internal-pam-tests-") @@ -857,12 +855,11 @@ func setupGlobalBrokerMock() (cleanup func(), err error) { } func TestMain(m *testing.M) { - if os.Getenv("GO_WANT_HELPER_PROCESS") != "" { - os.Exit(m.Run()) - } - log.SetLevel(log.DebugLevel) + userslocking.Z_ForTests_OverrideLocking() + defer userslocking.Z_ForTests_RestoreLocking() + cleanup, err := setupGlobalBrokerMock() if err != nil { cleanup() diff --git a/internal/services/pam/testdata/golden/TestIsAuthenticated/Error_on_updating_local_groups_with_unexisting_file/IsAuthenticated b/internal/services/pam/testdata/golden/TestIsAuthenticated/Error_on_updating_local_groups_with_unexisting_file/IsAuthenticated index 8f862f03f0..a5a3f884b8 100644 --- a/internal/services/pam/testdata/golden/TestIsAuthenticated/Error_on_updating_local_groups_with_unexisting_file/IsAuthenticated +++ b/internal/services/pam/testdata/golden/TestIsAuthenticated/Error_on_updating_local_groups_with_unexisting_file/IsAuthenticated @@ -1,4 +1,4 @@ FIRST CALL: access: msg: - err: can't check authentication: failed to update user "testisauthenticated/error_on_updating_local_groups_with_unexisting_file_separator_success_with_local_groups": could not update local groups for user "testisauthenticated/error_on_updating_local_groups_with_unexisting_file_separator_success_with_local_groups": could not fetch existing local group for user "testisauthenticated/error_on_updating_local_groups_with_unexisting_file_separator_success_with_local_groups": could not fetch existing local group: open testdata/TestIsAuthenticated/does_not_exists.group: no such file or directory + err: can't check authentication: failed to update user "testisauthenticated/error_on_updating_local_groups_with_unexisting_file_separator_success_with_local_groups": could not update local groups for user "testisauthenticated/error_on_updating_local_groups_with_unexisting_file_separator_success_with_local_groups": could not fetch existing local group for user "testisauthenticated/error_on_updating_local_groups_with_unexisting_file_separator_success_with_local_groups": could not fetch existing local group: open : no such file or directory diff --git a/internal/services/pam/testdata/golden/TestIsAuthenticated/Update_local_groups.group b/internal/services/pam/testdata/golden/TestIsAuthenticated/Update_local_groups.group new file mode 100644 index 0000000000..bfad1719d4 --- /dev/null +++ b/internal/services/pam/testdata/golden/TestIsAuthenticated/Update_local_groups.group @@ -0,0 +1,6 @@ +localgroup1:x:41:otheruser,success_with_local_groups,otheruser2,testisauthenticated/update_local_groups_separator_success_with_local_groups +localgroup2:x:42:success_with_local_groups +localgroup3:x:43:otheruser2,testisauthenticated/update_local_groups_separator_success_with_local_groups +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/services/pam/testdata/golden/TestIsAuthenticated/Update_local_groups.group.backup b/internal/services/pam/testdata/golden/TestIsAuthenticated/Update_local_groups.group.backup new file mode 100644 index 0000000000..f3dce64ae9 --- /dev/null +++ b/internal/services/pam/testdata/golden/TestIsAuthenticated/Update_local_groups.group.backup @@ -0,0 +1,6 @@ +localgroup1:x:41:otheruser,success_with_local_groups,otheruser2 +localgroup2:x:42:success_with_local_groups +localgroup3:x:43:otheruser2 +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/services/pam/testdata/golden/TestIsAuthenticated/Update_local_groups/gpasswd.output b/internal/services/pam/testdata/golden/TestIsAuthenticated/Update_local_groups/gpasswd.output deleted file mode 100644 index bf1a537a84..0000000000 --- a/internal/services/pam/testdata/golden/TestIsAuthenticated/Update_local_groups/gpasswd.output +++ /dev/null @@ -1,2 +0,0 @@ ---add testisauthenticated/update_local_groups_separator_success_with_local_groups localgroup1 ---add testisauthenticated/update_local_groups_separator_success_with_local_groups localgroup3 \ No newline at end of file diff --git a/internal/services/user/user_test.go b/internal/services/user/user_test.go index 1e82b84d63..01c7754287 100644 --- a/internal/services/user/user_test.go +++ b/internal/services/user/user_test.go @@ -19,7 +19,6 @@ import ( "github.com/ubuntu/authd/internal/users" "github.com/ubuntu/authd/internal/users/db" "github.com/ubuntu/authd/internal/users/idgenerator" - localgroupstestutils "github.com/ubuntu/authd/internal/users/localentries/testutils" "github.com/ubuntu/authd/log" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -67,9 +66,6 @@ func TestGetUserByName(t *testing.T) { } for name, tc := range tests { t.Run(name, func(t *testing.T) { - // We don't care about gpasswd output here as it's already covered in the db unit tests. - _ = localgroupstestutils.SetupGPasswdMock(t, filepath.Join("testdata", "empty.group")) - client := newUserServiceClient(t, tc.dbFile) got, err := client.GetUserByName(context.Background(), &authd.GetUserByNameRequest{Name: tc.username, ShouldPreCheck: tc.shouldPreCheck}) @@ -94,9 +90,6 @@ func TestGetUserByID(t *testing.T) { } for name, tc := range tests { t.Run(name, func(t *testing.T) { - // We don't care about gpasswd output here as it's already covered in the db unit tests. - _ = localgroupstestutils.SetupGPasswdMock(t, filepath.Join("testdata", "empty.group")) - client := newUserServiceClient(t, tc.dbFile) got, err := client.GetUserByID(context.Background(), &authd.GetUserByIDRequest{Id: tc.uid}) @@ -121,9 +114,6 @@ func TestGetGroupByName(t *testing.T) { } for name, tc := range tests { t.Run(name, func(t *testing.T) { - // We don't care about gpasswd output here as it's already covered in the db unit tests. - _ = localgroupstestutils.SetupGPasswdMock(t, filepath.Join("testdata", "empty.group")) - client := newUserServiceClient(t, tc.dbFile) got, err := client.GetGroupByName(context.Background(), &authd.GetGroupByNameRequest{Name: tc.groupname}) @@ -148,9 +138,6 @@ func TestGetGroupByID(t *testing.T) { } for name, tc := range tests { t.Run(name, func(t *testing.T) { - // We don't care about gpasswd output here as it's already covered in the db unit tests. - _ = localgroupstestutils.SetupGPasswdMock(t, filepath.Join("testdata", "empty.group")) - client := newUserServiceClient(t, tc.dbFile) got, err := client.GetGroupByID(context.Background(), &authd.GetGroupByIDRequest{Id: tc.gid}) @@ -170,9 +157,6 @@ func TestListUsers(t *testing.T) { } for name, tc := range tests { t.Run(name, func(t *testing.T) { - // We don't care about gpasswd output here as it's already covered in the db unit tests. - _ = localgroupstestutils.SetupGPasswdMock(t, filepath.Join("testdata", "empty.group")) - if tc.dbFile == "" { tc.dbFile = "default.db.yaml" } @@ -196,9 +180,6 @@ func TestListGroups(t *testing.T) { } for name, tc := range tests { t.Run(name, func(t *testing.T) { - // We don't care about gpasswd output here as it's already covered in the db unit tests. - _ = localgroupstestutils.SetupGPasswdMock(t, filepath.Join("testdata", "empty.group")) - if tc.dbFile == "" { tc.dbFile = "default.db.yaml" } @@ -219,10 +200,6 @@ func TestListGroups(t *testing.T) { } } -func TestMockgpasswd(t *testing.T) { - localgroupstestutils.Mockgpasswd(t) -} - // newUserServiceClient returns a new gRPC client for the CLI service. func newUserServiceClient(t *testing.T, dbFile string) (client authd.UserServiceClient) { t.Helper() @@ -350,11 +327,6 @@ func requireExpectedListResult[T authd.User | authd.Group](t *testing.T, funcNam } func TestMain(m *testing.M) { - // Needed to skip the test setup when running the gpasswd mock. - if os.Getenv("GO_WANT_HELPER_PROCESS") != "" { - os.Exit(m.Run()) - } - log.SetLevel(log.DebugLevel) cleanup, err := testutils.StartSystemBusMock() diff --git a/internal/testutils/daemon.go b/internal/testutils/daemon.go index d2512b206c..24620cb73e 100644 --- a/internal/testutils/daemon.go +++ b/internal/testutils/daemon.go @@ -8,6 +8,8 @@ import ( "os" "os/exec" "path/filepath" + "slices" + "strings" "syscall" "testing" "time" @@ -16,6 +18,7 @@ import ( "github.com/ubuntu/authd/internal/grpcutils" "github.com/ubuntu/authd/internal/services/errmessages" "github.com/ubuntu/authd/internal/users/db" + "github.com/ubuntu/authd/internal/users/localentries" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) @@ -90,6 +93,26 @@ func WithHomeBaseDir(baseDir string) DaemonOption { } } +// WithGroupFile sets the group file. +func WithGroupFile(groupFile string) DaemonOption { + return func(o *daemonOptions) { + o.env = slices.DeleteFunc(o.env, func(e string) bool { + return strings.HasPrefix(e, localentries.Z_ForTests_GroupFilePathEnv+"=") + }) + o.env = append(o.env, fmt.Sprintf("%s=%s", localentries.Z_ForTests_GroupFilePathEnv, groupFile)) + } +} + +// WithGroupFileOutput sets the group output file. +func WithGroupFileOutput(groupFile string) DaemonOption { + return func(o *daemonOptions) { + o.env = slices.DeleteFunc(o.env, func(e string) bool { + return strings.HasPrefix(e, localentries.Z_ForTests_GroupFileOutputPathEnv+"=") + }) + o.env = append(o.env, fmt.Sprintf("%s=%s", localentries.Z_ForTests_GroupFileOutputPathEnv, groupFile)) + } +} + // RunDaemon runs the daemon in a separate process and returns the socket path and a channel that will be closed when // the daemon stops. func RunDaemon(ctx context.Context, t *testing.T, execPath string, args ...DaemonOption) (socketPath string, stopped chan struct{}) { diff --git a/internal/users/localentries/export_test.go b/internal/users/localentries/export_test.go index 9a6ce34e07..b7e921f7d7 100644 --- a/internal/users/localentries/export_test.go +++ b/internal/users/localentries/export_test.go @@ -3,13 +3,26 @@ package localentries // WithGroupPath overrides the default /etc/group path for tests. func WithGroupPath(p string) Option { return func(o *options) { - o.groupPath = p + o.groupInputPath = p + o.groupOutputPath = p } } -// WithGpasswdCmd overrides gpasswd call with specific commands for tests. -func WithGpasswdCmd(cmds []string) Option { +// WithGroupInputPath overrides the default /etc/group path for input in tests. +func WithGroupInputPath(p string) Option { return func(o *options) { - o.gpasswdCmd = cmds + o.groupInputPath = p } } + +// WithGroupOutputPath overrides the default /etc/group path for output in tests. +func WithGroupOutputPath(p string) Option { + return func(o *options) { + o.groupOutputPath = p + } +} + +// GroupFileBackupPath exposes the path to the group file backup for testing. +func GroupFileBackupPath(groupFilePath string) string { + return groupFileBackupPath(groupFilePath) +} diff --git a/internal/users/localentries/localgroups.go b/internal/users/localentries/localgroups.go index 811968948c..0da587029b 100644 --- a/internal/users/localentries/localgroups.go +++ b/internal/users/localentries/localgroups.go @@ -4,16 +4,18 @@ package localentries import ( "bufio" "context" + "errors" "fmt" "math" "os" - "os/exec" "slices" "strconv" "strings" "sync" + "github.com/ubuntu/authd/internal/fileutils" "github.com/ubuntu/authd/internal/sliceutils" + userslocking "github.com/ubuntu/authd/internal/users/locking" "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/authd/log" "github.com/ubuntu/decorate" @@ -23,13 +25,13 @@ import ( const GroupFile = "/etc/group" var defaultOptions = options{ - groupPath: GroupFile, - gpasswdCmd: []string{"gpasswd"}, + groupInputPath: GroupFile, + groupOutputPath: GroupFile, } type options struct { - groupPath string - gpasswdCmd []string + groupInputPath string + groupOutputPath string } // Option represents an optional function to override UpdateLocalGroups default values. @@ -47,41 +49,63 @@ func Update(username string, newGroups []string, oldGroups []string, args ...Opt arg(&opts) } - currentGroups, err := existingLocalGroups(username, opts.groupPath) - if err != nil { + if err := userslocking.WriteRecLock(); err != nil { return err } + defer func() { + if unlockErr := userslocking.WriteRecUnlock(); unlockErr != nil { + err = errors.Join(err, unlockErr) + } + }() - currentGroupsNames := sliceutils.Map(currentGroups, func(g types.GroupEntry) string { + localGroupsMu.Lock() + defer localGroupsMu.Unlock() + + allGroups, userGroups, err := existingLocalGroups(username, opts.groupInputPath) + if err != nil { + return err + } + currentGroupsNames := sliceutils.Map(userGroups, func(g types.GroupEntry) string { return g.Name }) - localGroupsMu.Lock() - defer localGroupsMu.Unlock() groupsToAdd := sliceutils.Difference(newGroups, currentGroupsNames) - log.Debugf(context.TODO(), "Adding to local groups: %v", groupsToAdd) + log.Debugf(context.TODO(), "Adding %q to local groups: %v", username, groupsToAdd) groupsToRemove := sliceutils.Difference(oldGroups, newGroups) // Only remove user from groups which they are part of groupsToRemove = sliceutils.Intersection(groupsToRemove, currentGroupsNames) - log.Debugf(context.TODO(), "Removing from local groups: %v", groupsToRemove) + log.Debugf(context.TODO(), "Removing %q from local groups: %v", username, groupsToRemove) + + if len(groupsToRemove) == 0 && len(groupsToAdd) == 0 { + return nil + } + + getGroupByName := func(name string) *types.GroupEntry { + idx := slices.IndexFunc(allGroups, func(g types.GroupEntry) bool { return g.Name == name }) + if idx == -1 { + return nil + } + return &allGroups[idx] + } - // Do all this in a goroutine as we don't want to hang. for _, g := range groupsToRemove { - args := opts.gpasswdCmd[1:] - args = append(args, "--delete", username, g) - if err := runGPasswd(opts.gpasswdCmd[0], args...); err != nil { - return err + group := getGroupByName(g) + if group == nil { + continue } + group.Users = slices.DeleteFunc(group.Users, func(u string) bool { + return u == username + }) } for _, g := range groupsToAdd { - args := opts.gpasswdCmd[1:] - args = append(args, "--add", username, g) - if err := runGPasswd(opts.gpasswdCmd[0], args...); err != nil { - return err + group := getGroupByName(g) + if group == nil { + continue } + group.Users = append(group.Users, username) } - return nil + return saveLocalGroups(opts.groupInputPath, opts.groupOutputPath, allGroups) } func parseLocalGroups(groupPath string) (groups []types.GroupEntry, err error) { @@ -139,32 +163,72 @@ func parseLocalGroups(groupPath string) (groups []types.GroupEntry, err error) { return groups, nil } -// existingLocalGroups returns which groups from groupPath the user is part of. -func existingLocalGroups(user, groupPath string) (groups []types.GroupEntry, err error) { +func groupFileTemporaryPath(groupPath string) string { + return fmt.Sprintf("%s+", groupPath) +} + +func groupFileBackupPath(groupPath string) string { + return fmt.Sprintf("%s-", groupPath) +} + +func formatGroupEntries(groups []types.GroupEntry) string { + groupLines := sliceutils.Map(groups, func(group types.GroupEntry) string { + return group.String() + }) + + // Add final new line to the group file. + groupLines = append(groupLines, "") + + return strings.Join(groupLines, "\n") +} + +func saveLocalGroups(inputPath, groupPath string, groups []types.GroupEntry) (err error) { + defer decorate.OnError(&err, "could not write local groups to %q", groupPath) + + if err := types.ValidateGroupEntries(groups); err != nil { + return err + } + + backupPath := groupFileBackupPath(groupPath) + groupsEntries := formatGroupEntries(groups) + + log.Debugf(context.TODO(), "Saving group entries %#v to %q", groups, groupPath) + if len(groupsEntries) > 0 { + log.Debugf(context.TODO(), "Group file content:\n%s", groupsEntries) + } + + if err := os.Remove(backupPath); err != nil && !errors.Is(err, os.ErrNotExist) { + log.Warningf(context.Background(), "Failed to remove group file backup: %v", err) + } + + log.Debugf(context.Background(), "Backing up %q to %q", inputPath, backupPath) + if err := fileutils.CopyFile(inputPath, backupPath); err != nil { + log.Warningf(context.Background(), "Failed make a backup for the group file: %v", err) + } + + tempPath := groupFileTemporaryPath(groupPath) + //nolint:gosec // G306 /etc/group should indeed have 0644 permissions + if err := os.WriteFile(tempPath, []byte(groupsEntries), 0644); err != nil { + return fmt.Errorf("error writing %s: %w", tempPath, err) + } + + if err := fileutils.Lrename(tempPath, groupPath); err != nil { + return fmt.Errorf("error renaming %s to %s: %w", tempPath, groupPath, err) + } + + return nil +} + +// existingLocalGroups returns all the available groups and which groups from groupPath the user is part of. +func existingLocalGroups(user, groupPath string) (groups, userGroups []types.GroupEntry, err error) { defer decorate.OnError(&err, "could not fetch existing local group for user %q", user) groups, err = parseLocalGroups(groupPath) if err != nil { - return nil, err + return nil, nil, err } - return slices.DeleteFunc(groups, func(g types.GroupEntry) bool { + return groups, slices.DeleteFunc(slices.Clone(groups), func(g types.GroupEntry) bool { return !slices.Contains(g.Users, user) }), nil } - -// runGPasswd is a wrapper to cmdName ignoring exit code 3, meaning that the group doesn't exist. -// Note: it’s the same return code for user not existing, but it’s something we are in control of as we -// are responsible for the user itself and parsing the output is not desired. -func runGPasswd(cmdName string, args ...string) error { - cmd := exec.Command(cmdName, args...) - out, err := cmd.CombinedOutput() - if err != nil { - if cmd.ProcessState.ExitCode() == 3 { - log.Noticef(context.TODO(), "gpasswd exited with code 3 (group or user does not exist); ignoring: %s", out) - return nil - } - return fmt.Errorf("%q returned: %v\nOutput: %s", strings.Join(cmd.Args, " "), err, out) - } - return nil -} diff --git a/internal/users/localentries/localgroups_test.go b/internal/users/localentries/localgroups_test.go index 563446a552..48d02c00fa 100644 --- a/internal/users/localentries/localgroups_test.go +++ b/internal/users/localentries/localgroups_test.go @@ -1,13 +1,16 @@ package localentries_test import ( - "os" "path/filepath" "testing" "github.com/stretchr/testify/require" + "github.com/ubuntu/authd/internal/fileutils" + "github.com/ubuntu/authd/internal/testutils/golden" "github.com/ubuntu/authd/internal/users/localentries" localentriestestutils "github.com/ubuntu/authd/internal/users/localentries/testutils" + userslocking "github.com/ubuntu/authd/internal/users/locking" + "github.com/ubuntu/authd/log" ) func TestUpdatelocalentries(t *testing.T) { @@ -51,6 +54,11 @@ func TestUpdatelocalentries(t *testing.T) { // Error cases "Error_on_missing_groups_file": {groupFilePath: "does_not_exists.group", wantErr: true}, + "Error_on_invalid_user_name": { + groupFilePath: "no_users_in_our_groups.group", + username: "no,commas,please", + wantErr: true, + }, "Error_when_groups_file_has_missing_fields": { groupFilePath: "malformed_file_missing_field.group", wantErr: true, @@ -83,25 +91,35 @@ func TestUpdatelocalentries(t *testing.T) { tc.username = "" } - destCmdsFile := filepath.Join(t.TempDir(), "gpasswd.output") + inputGroupFilePath := filepath.Join("testdata", tc.groupFilePath) + outputGroupFilePath := filepath.Join(t.TempDir(), "group") - groupFilePath := filepath.Join("testdata", tc.groupFilePath) - cmdArgs := []string{"env", "GO_WANT_HELPER_PROCESS=1", - os.Args[0], "-test.run=TestMockgpasswd", "--", - groupFilePath, destCmdsFile, + if exists, _ := fileutils.FileExists(inputGroupFilePath); exists { + tempGroupFile := filepath.Join(t.TempDir(), "group") + err := fileutils.CopyFile(inputGroupFilePath, tempGroupFile) + require.NoError(t, err, "failed to copy group file for testing") + inputGroupFilePath = tempGroupFile } - err := localentries.Update(tc.username, tc.newGroups, tc.oldGroups, localentries.WithGroupPath(groupFilePath), localentries.WithGpasswdCmd(cmdArgs)) + err := localentries.Update(tc.username, tc.newGroups, tc.oldGroups, + localentries.WithGroupInputPath(inputGroupFilePath), + localentries.WithGroupOutputPath(outputGroupFilePath)) if tc.wantErr { require.Error(t, err, "Updatelocalentries should have failed") } else { require.NoError(t, err, "Updatelocalentries should not have failed") } + localentriestestutils.RequireGroupFile(t, outputGroupFilePath, golden.Path(t)) }) } } -func TestMockgpasswd(t *testing.T) { - localentriestestutils.Mockgpasswd(t) +func TestMain(m *testing.M) { + log.SetLevel(log.DebugLevel) + + userslocking.Z_ForTests_OverrideLocking() + defer userslocking.Z_ForTests_RestoreLocking() + + m.Run() } diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Add_and_remove_user_from_multiple_groups_with_one_remaining b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Add_and_remove_user_from_multiple_groups_with_one_remaining deleted file mode 100644 index 80dd17cde2..0000000000 --- a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Add_and_remove_user_from_multiple_groups_with_one_remaining +++ /dev/null @@ -1 +0,0 @@ ---add myuser localgroup3 \ No newline at end of file diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Add_and_remove_user_from_multiple_groups_with_one_remaining.group b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Add_and_remove_user_from_multiple_groups_with_one_remaining.group new file mode 100644 index 0000000000..38db364051 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Add_and_remove_user_from_multiple_groups_with_one_remaining.group @@ -0,0 +1,6 @@ +localgroup1:x:41:otheruser,myuser,otheruser2 +localgroup2:x:42:myuser +localgroup3:x:43:otheruser2,myuser +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Add_and_remove_user_from_multiple_groups_with_one_remaining.group.backup b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Add_and_remove_user_from_multiple_groups_with_one_remaining.group.backup new file mode 100644 index 0000000000..a0ad6fba24 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Add_and_remove_user_from_multiple_groups_with_one_remaining.group.backup @@ -0,0 +1,6 @@ +localgroup1:x:41:otheruser,myuser,otheruser2 +localgroup2:x:42:myuser +localgroup3:x:43:otheruser2 +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Group_file_with_empty_line_is_ignored b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Group_file_with_empty_line_is_ignored deleted file mode 100644 index 48073e4afb..0000000000 --- a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Group_file_with_empty_line_is_ignored +++ /dev/null @@ -1,2 +0,0 @@ ---add myuser localgroup1 ---add myuser localgroup3 \ No newline at end of file diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Group_file_with_empty_line_is_ignored.group b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Group_file_with_empty_line_is_ignored.group new file mode 100644 index 0000000000..fc7dc2c253 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Group_file_with_empty_line_is_ignored.group @@ -0,0 +1,6 @@ +localgroup1:x:41:myuser +localgroup2:x:42: +localgroup3:x:43:myuser +localgroup4:x:44: +cloudgroup1:x:9998: +cloudgroup2:x:9999: diff --git a/internal/users/localentries/testdata/gpasswdfail_in_deleted_group.group b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Group_file_with_empty_line_is_ignored.group.backup similarity index 76% rename from internal/users/localentries/testdata/gpasswdfail_in_deleted_group.group rename to internal/users/localentries/testdata/golden/TestUpdatelocalentries/Group_file_with_empty_line_is_ignored.group.backup index e0daafe3db..672fe3e741 100644 --- a/internal/users/localentries/testdata/gpasswdfail_in_deleted_group.group +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Group_file_with_empty_line_is_ignored.group.backup @@ -1,5 +1,6 @@ localgroup1:x:41: -localgroup2:x:42:gpasswdfail +localgroup2:x:42: + localgroup3:x:43: localgroup4:x:44: cloudgroup1:x:9998: diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_multiple_other_users_in_our_group b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_multiple_other_users_in_our_group deleted file mode 100644 index 48073e4afb..0000000000 --- a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_multiple_other_users_in_our_group +++ /dev/null @@ -1,2 +0,0 @@ ---add myuser localgroup1 ---add myuser localgroup3 \ No newline at end of file diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_multiple_other_users_in_our_group.group b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_multiple_other_users_in_our_group.group new file mode 100644 index 0000000000..8ac662bee9 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_multiple_other_users_in_our_group.group @@ -0,0 +1,6 @@ +localgroup1:x:41:otheruser,otheruser2,myuser +localgroup2:x:42:otheruser2 +localgroup3:x:43:otheruser,myuser +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_multiple_other_users_in_our_group.group.backup b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_multiple_other_users_in_our_group.group.backup new file mode 100644 index 0000000000..57701809de --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_multiple_other_users_in_our_group.group.backup @@ -0,0 +1,6 @@ +localgroup1:x:41:otheruser,otheruser2 +localgroup2:x:42:otheruser2 +localgroup3:x:43:otheruser +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_no_users_in_our_group b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_no_users_in_our_group deleted file mode 100644 index 48073e4afb..0000000000 --- a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_no_users_in_our_group +++ /dev/null @@ -1,2 +0,0 @@ ---add myuser localgroup1 ---add myuser localgroup3 \ No newline at end of file diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_no_users_in_our_group.group b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_no_users_in_our_group.group new file mode 100644 index 0000000000..3e52a350bf --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_no_users_in_our_group.group @@ -0,0 +1,6 @@ +localgroup1:x:41:myuser +localgroup2:x:42:otheruser +localgroup3:x:43:myuser +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_no_users_in_our_group.group.backup b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_no_users_in_our_group.group.backup new file mode 100644 index 0000000000..f298aac70d --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_no_users_in_our_group.group.backup @@ -0,0 +1,6 @@ +localgroup1:x:41: +localgroup2:x:42:otheruser +localgroup3:x:43: +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_other_users_in_our_group b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_other_users_in_our_group deleted file mode 100644 index 48073e4afb..0000000000 --- a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_other_users_in_our_group +++ /dev/null @@ -1,2 +0,0 @@ ---add myuser localgroup1 ---add myuser localgroup3 \ No newline at end of file diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_other_users_in_our_group.group b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_other_users_in_our_group.group new file mode 100644 index 0000000000..41ec5168d2 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_other_users_in_our_group.group @@ -0,0 +1,6 @@ +localgroup1:x:41:otheruser,myuser +localgroup2:x:42:otheruser2 +localgroup3:x:43:otheruser,myuser +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_other_users_in_our_group.group.backup b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_other_users_in_our_group.group.backup new file mode 100644 index 0000000000..821230a903 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_in_existing_files_with_other_users_in_our_group.group.backup @@ -0,0 +1,6 @@ +localgroup1:x:41:otheruser +localgroup2:x:42:otheruser2 +localgroup3:x:43:otheruser +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_when_no_users_in_any_group b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_when_no_users_in_any_group deleted file mode 100644 index 48073e4afb..0000000000 --- a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_when_no_users_in_any_group +++ /dev/null @@ -1,2 +0,0 @@ ---add myuser localgroup1 ---add myuser localgroup3 \ No newline at end of file diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_when_no_users_in_any_group.group b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_when_no_users_in_any_group.group new file mode 100644 index 0000000000..fc7dc2c253 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_when_no_users_in_any_group.group @@ -0,0 +1,6 @@ +localgroup1:x:41:myuser +localgroup2:x:42: +localgroup3:x:43:myuser +localgroup4:x:44: +cloudgroup1:x:9998: +cloudgroup2:x:9999: diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_when_no_users_in_any_group.group.backup b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_when_no_users_in_any_group.group.backup new file mode 100644 index 0000000000..d6667e9e66 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_new_user_when_no_users_in_any_group.group.backup @@ -0,0 +1,6 @@ +localgroup1:x:41: +localgroup2:x:42: +localgroup3:x:43: +localgroup4:x:44: +cloudgroup1:x:9998: +cloudgroup2:x:9999: diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_user_in_the_only_local_group_when_not_present b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_user_in_the_only_local_group_when_not_present deleted file mode 100644 index 80dd17cde2..0000000000 --- a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_user_in_the_only_local_group_when_not_present +++ /dev/null @@ -1 +0,0 @@ ---add myuser localgroup3 \ No newline at end of file diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_user_in_the_only_local_group_when_not_present.group b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_user_in_the_only_local_group_when_not_present.group new file mode 100644 index 0000000000..3e52a350bf --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_user_in_the_only_local_group_when_not_present.group @@ -0,0 +1,6 @@ +localgroup1:x:41:myuser +localgroup2:x:42:otheruser +localgroup3:x:43:myuser +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_user_in_the_only_local_group_when_not_present.group.backup b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_user_in_the_only_local_group_when_not_present.group.backup new file mode 100644 index 0000000000..b0f6ea9cf0 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_user_in_the_only_local_group_when_not_present.group.backup @@ -0,0 +1,6 @@ +localgroup1:x:41:myuser +localgroup2:x:42:otheruser +localgroup3:x:43: +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_user_in_the_only_local_group_when_not_present_even_with_multiple b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_user_in_the_only_local_group_when_not_present_even_with_multiple deleted file mode 100644 index 80dd17cde2..0000000000 --- a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_user_in_the_only_local_group_when_not_present_even_with_multiple +++ /dev/null @@ -1 +0,0 @@ ---add myuser localgroup3 \ No newline at end of file diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_user_in_the_only_local_group_when_not_present_even_with_multiple.group b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_user_in_the_only_local_group_when_not_present_even_with_multiple.group new file mode 100644 index 0000000000..b69e305ea3 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_user_in_the_only_local_group_when_not_present_even_with_multiple.group @@ -0,0 +1,6 @@ +localgroup1:x:41:otheruser,myuser,otheruser2 +localgroup2:x:42:otheruser2 +localgroup3:x:43:otheruser,myuser +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_user_in_the_only_local_group_when_not_present_even_with_multiple.group.backup b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_user_in_the_only_local_group_when_not_present_even_with_multiple.group.backup new file mode 100644 index 0000000000..800c919baa --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Insert_user_in_the_only_local_group_when_not_present_even_with_multiple.group.backup @@ -0,0 +1,6 @@ +localgroup1:x:41:otheruser,myuser,otheruser2 +localgroup2:x:42:otheruser2 +localgroup3:x:43:otheruser +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Missing_group_is_ignored b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Missing_group_is_ignored deleted file mode 100644 index 0dd812dc84..0000000000 --- a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Missing_group_is_ignored +++ /dev/null @@ -1 +0,0 @@ ---add myuser localgroup1 \ No newline at end of file diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Missing_group_is_ignored.group b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Missing_group_is_ignored.group new file mode 100644 index 0000000000..02e66bd3bd --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Missing_group_is_ignored.group @@ -0,0 +1,5 @@ +localgroup1:x:41:myuser +localgroup2:x:42: +localgroup4:x:44: +cloudgroup1:x:9998: +cloudgroup2:x:9999: diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Missing_group_is_ignored.group.backup b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Missing_group_is_ignored.group.backup new file mode 100644 index 0000000000..4c1e2343c5 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Missing_group_is_ignored.group.backup @@ -0,0 +1,5 @@ +localgroup1:x:41: +localgroup2:x:42: +localgroup4:x:44: +cloudgroup1:x:9998: +cloudgroup2:x:9999: diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/User_is_added_to_group_they_were_added_to_before b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/User_is_added_to_group_they_were_added_to_before deleted file mode 100644 index 0dd812dc84..0000000000 --- a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/User_is_added_to_group_they_were_added_to_before +++ /dev/null @@ -1 +0,0 @@ ---add myuser localgroup1 \ No newline at end of file diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/User_is_added_to_group_they_were_added_to_before.group b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/User_is_added_to_group_they_were_added_to_before.group new file mode 100644 index 0000000000..fd572da90f --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/User_is_added_to_group_they_were_added_to_before.group @@ -0,0 +1,6 @@ +localgroup1:x:41:myuser +localgroup2:x:42: +localgroup3:x:43: +localgroup4:x:44: +cloudgroup1:x:9998: +cloudgroup2:x:9999: diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/User_is_added_to_group_they_were_added_to_before.group.backup b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/User_is_added_to_group_they_were_added_to_before.group.backup new file mode 100644 index 0000000000..d6667e9e66 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/User_is_added_to_group_they_were_added_to_before.group.backup @@ -0,0 +1,6 @@ +localgroup1:x:41: +localgroup2:x:42: +localgroup3:x:43: +localgroup4:x:44: +cloudgroup1:x:9998: +cloudgroup2:x:9999: diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/User_is_removed_from_old_groups_but_not_from_other_groups b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/User_is_removed_from_old_groups_but_not_from_other_groups deleted file mode 100644 index e49d0784fd..0000000000 --- a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/User_is_removed_from_old_groups_but_not_from_other_groups +++ /dev/null @@ -1 +0,0 @@ ---delete myuser localgroup3 \ No newline at end of file diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/User_is_removed_from_old_groups_but_not_from_other_groups.group b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/User_is_removed_from_old_groups_but_not_from_other_groups.group new file mode 100644 index 0000000000..b0f6ea9cf0 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/User_is_removed_from_old_groups_but_not_from_other_groups.group @@ -0,0 +1,6 @@ +localgroup1:x:41:myuser +localgroup2:x:42:otheruser +localgroup3:x:43: +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/User_is_removed_from_old_groups_but_not_from_other_groups.group.backup b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/User_is_removed_from_old_groups_but_not_from_other_groups.group.backup new file mode 100644 index 0000000000..3e52a350bf --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/User_is_removed_from_old_groups_but_not_from_other_groups.group.backup @@ -0,0 +1,6 @@ +localgroup1:x:41:myuser +localgroup2:x:42:otheruser +localgroup3:x:43:myuser +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testutils.go b/internal/users/localentries/testutils.go index 0ef7edee09..2f575bb08c 100644 --- a/internal/users/localentries/testutils.go +++ b/internal/users/localentries/testutils.go @@ -1,9 +1,23 @@ package localentries -import "github.com/ubuntu/authd/internal/testsdetection" +import ( + "github.com/ubuntu/authd/internal/testsdetection" +) var originalDefaultOptions = defaultOptions +const ( + // Z_ForTests_GroupFilePathEnv is the env variable to set the group file path during + // integration tests. + // nolint:revive,nolintlint // We want to use underscores in the function name here. + Z_ForTests_GroupFilePathEnv = "AUTHD_INTEGRATIONTESTS_GROUP_FILE_PATH" + + // Z_ForTests_GroupFileOutputPathEnv is the env variable to set the group file output + // path during integration tests. + // nolint:revive,nolintlint // We want to use underscores in the function name here. + Z_ForTests_GroupFileOutputPathEnv = "AUTHD_INTEGRATIONTESTS_GROUP_OUTPUT_FILE_PATH" +) + // Z_ForTests_RestoreDefaultOptions restores the defaultOptions to their original values. // // nolint:revive,nolintlint // We want to use underscores in the function name here. @@ -18,19 +32,9 @@ func Z_ForTests_RestoreDefaultOptions() { // Call Z_ForTests_RestoreDefaultOptions to restore the original value. // // nolint:revive,nolintlint // We want to use underscores in the function name here. -func Z_ForTests_SetGroupPath(groupPath string) { - testsdetection.MustBeTesting() - - defaultOptions.groupPath = groupPath -} - -// Z_ForTests_SetGpasswdCmd sets the gpasswdCmd for the defaultOptions. -// Tests using this can't be run in parallel. -// Call Z_ForTests_RestoreDefaultOptions to restore the original value. -// -// nolint:revive,nolintlint // We want to use underscores in the function name here. -func Z_ForTests_SetGpasswdCmd(gpasswdCmd []string) { +func Z_ForTests_SetGroupPath(inputGroupPath, outputGroupPath string) { testsdetection.MustBeTesting() - defaultOptions.gpasswdCmd = gpasswdCmd + defaultOptions.groupInputPath = inputGroupPath + defaultOptions.groupOutputPath = outputGroupPath } diff --git a/internal/users/localentries/testutils/gpasswd.go b/internal/users/localentries/testutils/gpasswd.go deleted file mode 100644 index 292ebba35d..0000000000 --- a/internal/users/localentries/testutils/gpasswd.go +++ /dev/null @@ -1,147 +0,0 @@ -// Package localgrouptestutils export users test functionalities used by other packages to change cmdline and group file. -package localgrouptestutils - -import ( - "fmt" - "os" - "path/filepath" - "slices" - "strings" - "testing" - - "github.com/stretchr/testify/require" - "github.com/ubuntu/authd/internal/testutils/golden" - "github.com/ubuntu/authd/internal/users/localentries" -) - -// Mockgpasswd is the gpasswd mock. -func Mockgpasswd(_ *testing.T) { - if os.Getenv("GO_WANT_HELPER_PROCESS") == "" { - return - } - - args := os.Args - for len(args) > 0 { - if args[0] != "--" { - args = args[1:] - continue - } - args = args[1:] - break - } - groupsFilePath, outputFilePath := args[0], args[1] - - // args are now the real args passed by authd. - args = args[2:] - - d, err := os.ReadFile(groupsFilePath) - if err != nil { - fmt.Fprintf(os.Stderr, "Mock: error reading group file: %v", err) - os.Exit(1) - } - - // Error if the group is not in the groupfile (we don’t handle substrings in the mock) - group := args[len(args)-1] - if !strings.Contains(string(d), group+":") { - fmt.Fprintf(os.Stderr, "Error: %s in not in the group file", group) - os.Exit(3) - } - - // Other error - if slices.Contains(args, "gpasswdfail") { - fmt.Fprint(os.Stderr, "Error requested in mock") - os.Exit(1) - } - - f, err := os.OpenFile(outputFilePath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600) - if err != nil { - fmt.Fprintf(os.Stderr, "Mock: error opening file in append mode: %v", err) - os.Exit(1) - } - defer f.Close() - - if _, err := f.Write([]byte(strings.Join(args, " ") + "\n")); err != nil { - fmt.Fprintf(os.Stderr, "Mock: error while writing in file: %v", err) - f.Close() - os.Exit(1) - } -} - -// SetupGPasswdMock setup the gpasswd mock and return the path to the file where the commands will be written. -// -// Tests that require this can not be run in parallel. -func SetupGPasswdMock(t *testing.T, groupsFilePath string) string { - t.Helper() - - t.Cleanup(localentries.Z_ForTests_RestoreDefaultOptions) - - localentries.Z_ForTests_SetGroupPath(groupsFilePath) - - destCmdsFile := filepath.Join(t.TempDir(), "gpasswd.output") - localentries.Z_ForTests_SetGpasswdCmd([]string{"env", "GO_WANT_HELPER_PROCESS=1", - os.Args[0], "-test.run=TestMockgpasswd", "--", - groupsFilePath, destCmdsFile, - }) - - return destCmdsFile -} - -// AuthdIntegrationTestsEnvWithGpasswdMock returns the environment to pass to the authd daemon to use the gpasswd -// mock. In order to enable it, the authd binary must be built with the tag integrationtests. -// You need to install a TestMockgpasswd (generally calling Mockgpasswd) in your integration tests files. -func AuthdIntegrationTestsEnvWithGpasswdMock(t *testing.T, outputFilePath, groupsFilePath string) []string { - t.Helper() - - gpasswdArgs := append([]string{ - "env", "GO_WANT_HELPER_PROCESS=1"}, - os.Args...) - gpasswdArgs = append(gpasswdArgs, - "-test.run=TestMockgpasswd", "--", - groupsFilePath, outputFilePath, - ) - - return []string{ - "AUTHD_INTEGRATIONTESTS_GPASSWD_ARGS=" + strings.Join(gpasswdArgs, " "), - "AUTHD_INTEGRATIONTESTS_GPASSWD_GRP_FILE_PATH=" + groupsFilePath, - } -} - -// RequireGPasswdOutput compare the output of gpasswd with the golden file. -func RequireGPasswdOutput(t *testing.T, destCmdsFile, goldenGpasswdPath string) { - t.Helper() - - // TODO: this should be extracted in testutils, but still allow post-treatement of file like sorting. - referenceFilePath := goldenGpasswdPath - if golden.UpdateEnabled() { - // The file may already not exists. - _ = os.Remove(goldenGpasswdPath) - referenceFilePath = destCmdsFile - } - - var shouldExists bool - if _, err := os.Stat(referenceFilePath); err == nil { - shouldExists = true - } - if !shouldExists { - require.NoFileExists(t, destCmdsFile, "UpdateLocalGroups should not call gpasswd by did") - return - } - - gotGPasswd := idempotentGPasswdOutput(t, destCmdsFile) - golden.CheckOrUpdate(t, gotGPasswd, golden.WithPath(goldenGpasswdPath)) -} - -// idempotentGPasswdOutput sort and trim spaces around mock gpasswd output. -func idempotentGPasswdOutput(t *testing.T, cmdsFilePath string) string { - t.Helper() - - d, err := os.ReadFile(cmdsFilePath) - require.NoError(t, err, "Teardown: could not read dest trace file") - - // need to sort out all operations - ops := strings.Split(string(d), "\n") - slices.Sort(ops) - content := strings.TrimSpace(strings.Join(ops, "\n")) - - return content -} diff --git a/internal/users/localentries/testutils/localgroups.go b/internal/users/localentries/testutils/localgroups.go index 9160a928a5..2a5a102490 100644 --- a/internal/users/localentries/testutils/localgroups.go +++ b/internal/users/localentries/testutils/localgroups.go @@ -2,7 +2,15 @@ package localgrouptestutils import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + "github.com/ubuntu/authd/internal/fileutils" "github.com/ubuntu/authd/internal/testsdetection" + "github.com/ubuntu/authd/internal/testutils/golden" + "github.com/ubuntu/authd/internal/users/localentries" ) func init() { @@ -12,8 +20,7 @@ func init() { var ( defaultOptions struct { - groupPath string - gpasswdCmd []string + groupPath string } ) @@ -23,8 +30,66 @@ func SetGroupPath(groupPath string) { defaultOptions.groupPath = groupPath } -// SetGpasswdCmd sets the gpasswdCmd for the defaultOptions. -// Tests using this can't be run in parallel. -func SetGpasswdCmd(gpasswdCmd []string) { - defaultOptions.gpasswdCmd = gpasswdCmd +// RequireGroupFile compare the output of the generated group file with the +// golden file. +func RequireGroupFile(t *testing.T, destGroupFile, goldenGroupPath string) { + t.Helper() + + destGroupBackupFile := destGroupFile + "-" + groupSuffix := ".group" + backupSuffix := groupSuffix + ".backup" + + // TODO: this should be extracted in testutils, but still allow post-treatement of file like sorting. + referenceFilePath := goldenGroupPath + groupSuffix + if golden.UpdateEnabled() { + // The file may already not exists. + _ = os.Remove(goldenGroupPath + groupSuffix) + _ = os.Remove(goldenGroupPath + backupSuffix) + referenceFilePath = destGroupFile + } + + var shouldExists bool + if _, err := os.Stat(referenceFilePath); err == nil { + shouldExists = true + } + if !shouldExists { + require.NoFileExists(t, destGroupFile, "UpdateLocalGroups should not update the group files, but it did") + require.NoFileExists(t, destGroupBackupFile, "UpdateLocalGroups should not update the group file, but it did") + return + } + + gotGroups, err := os.ReadFile(destGroupFile) + require.NoError(t, err, "Teardown: could not read dest group file") + + golden.CheckOrUpdate(t, string(gotGroups), golden.WithPath(goldenGroupPath), + golden.WithSuffix(groupSuffix)) + + gotGroupsBackup, err := os.ReadFile(destGroupBackupFile) + require.NoError(t, err, "Teardown: could not read dest group backup file") + golden.CheckOrUpdate(t, string(gotGroupsBackup), golden.WithPath(goldenGroupPath), + golden.WithSuffix(backupSuffix)) +} + +// SetupGroupMock setup the group mock and return the path to the file where the +// output will be written. +// +// Tests that require this can not be run in parallel. +func SetupGroupMock(t *testing.T, groupsFilePath string) string { + t.Helper() + + testsdetection.MustBeTesting() + + t.Cleanup(localentries.Z_ForTests_RestoreDefaultOptions) + + var tempGroupFile string + if exists, _ := fileutils.FileExists(groupsFilePath); exists { + tempGroupFile = filepath.Join(t.TempDir(), filepath.Base(groupsFilePath)) + err := fileutils.CopyFile(groupsFilePath, tempGroupFile) + require.NoError(t, err, "failed to copy group file for testing") + } + + destGroupFile := filepath.Join(t.TempDir(), "group") + localentries.Z_ForTests_SetGroupPath(tempGroupFile, destGroupFile) + + return destGroupFile } diff --git a/internal/users/manager_test.go b/internal/users/manager_test.go index a7452193c8..9cfe50fe3e 100644 --- a/internal/users/manager_test.go +++ b/internal/users/manager_test.go @@ -13,6 +13,7 @@ import ( "github.com/ubuntu/authd/internal/users/db" "github.com/ubuntu/authd/internal/users/idgenerator" localgroupstestutils "github.com/ubuntu/authd/internal/users/localentries/testutils" + userslocking "github.com/ubuntu/authd/internal/users/locking" userstestutils "github.com/ubuntu/authd/internal/users/testutils" "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/authd/log" @@ -41,7 +42,8 @@ func TestNewManager(t *testing.T) { } for name, tc := range tests { t.Run(name, func(t *testing.T) { - destCmdsFile := localgroupstestutils.SetupGPasswdMock(t, filepath.Join("testdata", "groups", "users_in_groups.group")) + destGroupFile := localgroupstestutils.SetupGroupMock(t, + filepath.Join("testdata", "groups", "users_in_groups.group")) dbDir := t.TempDir() if tc.dbFile == "" { @@ -85,7 +87,7 @@ func TestNewManager(t *testing.T) { golden.CheckOrUpdate(t, got) - localgroupstestutils.RequireGPasswdOutput(t, destCmdsFile, golden.Path(t)+".gpasswd.output") + localgroupstestutils.RequireGroupFile(t, destGroupFile, golden.Path(t)) }) } } @@ -134,10 +136,6 @@ func TestUpdateUser(t *testing.T) { {GroupInfo: types.GroupInfo{Name: "localgroup1", UGID: ""}}, {GroupInfo: types.GroupInfo{Name: "group1", UGID: "1"}, GID: 11111}, }, - "mixed-groups-gpasswd-fail": { - {GroupInfo: types.GroupInfo{Name: "group1", UGID: "1"}, GID: 11111}, - {GroupInfo: types.GroupInfo{Name: "gpasswdfail", UGID: ""}}, - }, "nameless-group": {{GroupInfo: types.GroupInfo{Name: "", UGID: "1"}, GID: 11111}}, "different-name-same-gid": {{GroupInfo: types.GroupInfo{Name: "newgroup1", UGID: "1"}, GID: 11111}}, "group-exists-on-system": {{GroupInfo: types.GroupInfo{Name: "root", UGID: "1"}, GID: 11111}}, @@ -159,6 +157,7 @@ func TestUpdateUser(t *testing.T) { }{ "Successfully_update_user": {groupsCase: "authd-group"}, "Successfully_update_user_updating_local_groups": {groupsCase: "mixed-groups-authd-first", localGroupsFile: "users_in_groups.group"}, + "Successfully_update_user_updating_local_groups_with_changes": {groupsCase: "mixed-groups-authd-first", localGroupsFile: "user_mismatching_groups.group"}, "UID_does_not_change_if_user_already_exists": {userCase: "same-name-different-uid", dbFile: "one_user_and_group", wantSameUID: true}, "Successfully update user with different capitalization": {userCase: "different-capitalization-same-uid", dbFile: "one_user_and_group"}, "GID_does_not_change_if_group_with_same_UGID_exists": {groupsCase: "different-name-same-ugid", dbFile: "one_user_and_group"}, @@ -179,9 +178,10 @@ func TestUpdateUser(t *testing.T) { t.Parallel() } - var destCmdsFile string + var destGroupFile string if tc.localGroupsFile != "" { - destCmdsFile = localgroupstestutils.SetupGPasswdMock(t, filepath.Join("testdata", "groups", tc.localGroupsFile)) + destGroupFile = localgroupstestutils.SetupGroupMock(t, + filepath.Join("testdata", "groups", tc.localGroupsFile)) } if tc.userCase == "" { @@ -243,12 +243,14 @@ func TestUpdateUser(t *testing.T) { golden.CheckOrUpdate(t, got) - localgroupstestutils.RequireGPasswdOutput(t, destCmdsFile, golden.Path(t)+".gpasswd.output") + localgroupstestutils.RequireGroupFile(t, destGroupFile, golden.Path(t)) }) } } func TestBrokerForUser(t *testing.T) { + t.Parallel() + tests := map[string]struct { username string dbFile string @@ -264,8 +266,7 @@ func TestBrokerForUser(t *testing.T) { } for name, tc := range tests { t.Run(name, func(t *testing.T) { - // We don't care about the output of gpasswd in this test, but we still need to mock it. - _ = localgroupstestutils.SetupGPasswdMock(t, "empty.group") + t.Parallel() dbDir := t.TempDir() err := db.Z_ForTests_CreateDBFromYAML(filepath.Join("testdata", "db", tc.dbFile+".db.yaml"), dbDir) @@ -285,6 +286,8 @@ func TestBrokerForUser(t *testing.T) { } func TestUpdateBrokerForUser(t *testing.T) { + t.Parallel() + tests := map[string]struct { username string @@ -299,8 +302,7 @@ func TestUpdateBrokerForUser(t *testing.T) { } for name, tc := range tests { t.Run(name, func(t *testing.T) { - // We don't care about the output of gpasswd in this test, but we still need to mock it. - _ = localgroupstestutils.SetupGPasswdMock(t, "empty.group") + t.Parallel() if tc.username == "" { tc.username = "user1" @@ -330,6 +332,8 @@ func TestUpdateBrokerForUser(t *testing.T) { } func TestUserByIDAndName(t *testing.T) { + t.Parallel() + tests := map[string]struct { uid uint32 username string @@ -349,8 +353,7 @@ func TestUserByIDAndName(t *testing.T) { } for name, tc := range tests { t.Run(name, func(t *testing.T) { - // We don't care about the output of gpasswd in this test, but we still need to mock it. - _ = localgroupstestutils.SetupGPasswdMock(t, "empty.group") + t.Parallel() dbDir := t.TempDir() err := db.Z_ForTests_CreateDBFromYAML(filepath.Join("testdata", "db", tc.dbFile+".db.yaml"), dbDir) @@ -392,6 +395,8 @@ func TestUserByIDAndName(t *testing.T) { } func TestAllUsers(t *testing.T) { + t.Parallel() + tests := map[string]struct { dbFile string @@ -402,8 +407,7 @@ func TestAllUsers(t *testing.T) { } for name, tc := range tests { t.Run(name, func(t *testing.T) { - // We don't care about the output of gpasswd in this test, but we still need to mock it. - _ = localgroupstestutils.SetupGPasswdMock(t, "empty.group") + t.Parallel() dbDir := t.TempDir() err := db.Z_ForTests_CreateDBFromYAML(filepath.Join("testdata", "db", tc.dbFile+".db.yaml"), dbDir) @@ -423,6 +427,8 @@ func TestAllUsers(t *testing.T) { } func TestGroupByIDAndName(t *testing.T) { + t.Parallel() + tests := map[string]struct { gid uint32 groupname string @@ -442,8 +448,7 @@ func TestGroupByIDAndName(t *testing.T) { } for name, tc := range tests { t.Run(name, func(t *testing.T) { - // We don't care about the output of gpasswd in this test, but we still need to mock it. - _ = localgroupstestutils.SetupGPasswdMock(t, "empty.group") + t.Parallel() dbDir := t.TempDir() err := db.Z_ForTests_CreateDBFromYAML(filepath.Join("testdata", "db", tc.dbFile+".db.yaml"), dbDir) @@ -482,6 +487,8 @@ func TestGroupByIDAndName(t *testing.T) { } func TestAllGroups(t *testing.T) { + t.Parallel() + tests := map[string]struct { dbFile string @@ -492,8 +499,7 @@ func TestAllGroups(t *testing.T) { } for name, tc := range tests { t.Run(name, func(t *testing.T) { - // We don't care about the output of gpasswd in this test, but we still need to mock it. - _ = localgroupstestutils.SetupGPasswdMock(t, "empty.group") + t.Parallel() dbDir := t.TempDir() err := db.Z_ForTests_CreateDBFromYAML(filepath.Join("testdata", "db", tc.dbFile+".db.yaml"), dbDir) @@ -514,6 +520,8 @@ func TestAllGroups(t *testing.T) { } func TestShadowByName(t *testing.T) { + t.Parallel() + tests := map[string]struct { username string dbFile string @@ -527,8 +535,7 @@ func TestShadowByName(t *testing.T) { } for name, tc := range tests { t.Run(name, func(t *testing.T) { - // We don't care about the output of gpasswd in this test, but we still need to mock it. - _ = localgroupstestutils.SetupGPasswdMock(t, "empty.group") + t.Parallel() dbDir := t.TempDir() err := db.Z_ForTests_CreateDBFromYAML(filepath.Join("testdata", "db", tc.dbFile+".db.yaml"), dbDir) @@ -549,6 +556,8 @@ func TestShadowByName(t *testing.T) { } func TestAllShadows(t *testing.T) { + t.Parallel() + tests := map[string]struct { dbFile string @@ -558,8 +567,7 @@ func TestAllShadows(t *testing.T) { } for name, tc := range tests { t.Run(name, func(t *testing.T) { - // We don't care about the output of gpasswd in this test, but we still need to mock it. - _ = localgroupstestutils.SetupGPasswdMock(t, "empty.group") + t.Parallel() dbDir := t.TempDir() err := db.Z_ForTests_CreateDBFromYAML(filepath.Join("testdata", "db", tc.dbFile+".db.yaml"), dbDir) @@ -579,10 +587,6 @@ func TestAllShadows(t *testing.T) { } } -func TestMockgpasswd(t *testing.T) { - localgroupstestutils.Mockgpasswd(t) -} - func requireErrorAssertions(t *testing.T, gotErr, wantErrType error, wantErr bool) { t.Helper() @@ -608,5 +612,9 @@ func newManagerForTests(t *testing.T, dbDir string, opts ...users.Option) *users func TestMain(m *testing.M) { log.SetLevel(log.DebugLevel) + + userslocking.Z_ForTests_OverrideLocking() + defer userslocking.Z_ForTests_RestoreLocking() + m.Run() } diff --git a/internal/users/testdata/golden/TestUpdateUser/Successfully_update_user_updating_local_groups_with_changes b/internal/users/testdata/golden/TestUpdateUser/Successfully_update_user_updating_local_groups_with_changes new file mode 100644 index 0000000000..11a33baa3a --- /dev/null +++ b/internal/users/testdata/golden/TestUpdateUser/Successfully_update_user_updating_local_groups_with_changes @@ -0,0 +1,20 @@ +users: + - name: user1 + uid: 1111 + gid: 1111 + gecos: gecos for user1 + dir: /home/user1 + shell: /bin/bash +groups: + - name: user1 + gid: 1111 + ugid: user1 + - name: group1 + gid: 11111 + ugid: "1" +users_to_groups: + - uid: 1111 + gid: 1111 + - uid: 1111 + gid: 11111 +schema_version: 1 diff --git a/internal/users/testdata/golden/TestUpdateUser/Successfully_update_user_updating_local_groups_with_changes.group b/internal/users/testdata/golden/TestUpdateUser/Successfully_update_user_updating_local_groups_with_changes.group new file mode 100644 index 0000000000..d373a1f2a7 --- /dev/null +++ b/internal/users/testdata/golden/TestUpdateUser/Successfully_update_user_updating_local_groups_with_changes.group @@ -0,0 +1,3 @@ +localgroup1:x:41:user1 +localgroup2:x:44:user2 +localgroup3:x:45:user3 diff --git a/internal/users/testdata/golden/TestUpdateUser/Successfully_update_user_updating_local_groups_with_changes.group.backup b/internal/users/testdata/golden/TestUpdateUser/Successfully_update_user_updating_local_groups_with_changes.group.backup new file mode 100644 index 0000000000..5138335d0e --- /dev/null +++ b/internal/users/testdata/golden/TestUpdateUser/Successfully_update_user_updating_local_groups_with_changes.group.backup @@ -0,0 +1,3 @@ +localgroup1:x:41: +localgroup2:x:44:user2 +localgroup3:x:45:user3 diff --git a/internal/users/testdata/groups/gpasswdfail_in_deleted_group.group b/internal/users/testdata/groups/gpasswdfail_in_deleted_group.group deleted file mode 100644 index 3ba19de956..0000000000 --- a/internal/users/testdata/groups/gpasswdfail_in_deleted_group.group +++ /dev/null @@ -1,2 +0,0 @@ -gpasswdfail:x:42:anotheruser -localgroup1:x:43:otheruser \ No newline at end of file diff --git a/internal/users/testdata/groups/user_mismatching_groups.group b/internal/users/testdata/groups/user_mismatching_groups.group new file mode 100644 index 0000000000..5138335d0e --- /dev/null +++ b/internal/users/testdata/groups/user_mismatching_groups.group @@ -0,0 +1,3 @@ +localgroup1:x:41: +localgroup2:x:44:user2 +localgroup3:x:45:user3 diff --git a/internal/users/types/groupentry.go b/internal/users/types/groupentry.go index 9437691671..040f2f1355 100644 --- a/internal/users/types/groupentry.go +++ b/internal/users/types/groupentry.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "slices" + "strconv" "strings" "github.com/ubuntu/authd/internal/sliceutils" @@ -49,6 +50,15 @@ func (g GroupEntry) DeepCopy() GroupEntry { return g } +func (g GroupEntry) String() string { + return strings.Join([]string{ + g.Name, + g.Passwd, + strconv.FormatUint(uint64(g.GID), 10), + strings.Join(g.Users, ","), + }, ":") +} + // DeepCopyGroupEntries makes a deep copy of group entries. func DeepCopyGroupEntries(groups []GroupEntry) []GroupEntry { return sliceutils.Map(groups, func(g GroupEntry) GroupEntry { diff --git a/internal/users/types/groupentry_test.go b/internal/users/types/groupentry_test.go index 0b75daf71a..094a22d446 100644 --- a/internal/users/types/groupentry_test.go +++ b/internal/users/types/groupentry_test.go @@ -159,6 +159,55 @@ func TestGroupEntryEquals(t *testing.T) { } } +func TestGroupEntryString(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + group GroupEntry + expected string + }{ + { + name: "All_fields_set_with_users", + group: GroupEntry{Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1", "user2"}}, + expected: "group1:x:1000:user1,user2", + }, + { + name: "No_users", + group: GroupEntry{Name: "group2", GID: 1001, Passwd: "y", Users: nil}, + expected: "group2:y:1001:", + }, + { + name: "Empty_users_slice", + group: GroupEntry{Name: "group3", GID: 1002, Passwd: "z", Users: []string{}}, + expected: "group3:z:1002:", + }, + { + name: "Empty_passwd", + group: GroupEntry{Name: "group4", GID: 1003, Passwd: "", Users: []string{"user3"}}, + expected: "group4::1003:user3", + }, + { + name: "Empty_group", + group: GroupEntry{}, + expected: "::0:", + }, + { + name: "Multiple_users", + group: GroupEntry{Name: "admins", GID: 42, Passwd: "pw", Users: []string{"alice", "bob", "carol"}}, + expected: "admins:pw:42:alice,bob,carol", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + got := tc.group.String() + require.Equal(t, tc.expected, got, "String output mismatch") + }) + } +} + func TestValidateGroupEntries(t *testing.T) { t.Parallel() diff --git a/nss/integration-tests/integration_test.go b/nss/integration-tests/integration_test.go index 79caf306bc..57deb16feb 100644 --- a/nss/integration-tests/integration_test.go +++ b/nss/integration-tests/integration_test.go @@ -12,7 +12,6 @@ import ( "github.com/ubuntu/authd/examplebroker" "github.com/ubuntu/authd/internal/testutils" "github.com/ubuntu/authd/internal/testutils/golden" - localgroupstestutils "github.com/ubuntu/authd/internal/users/localentries/testutils" ) var daemonPath string @@ -28,15 +27,14 @@ func TestIntegration(t *testing.T) { // Create a default daemon to use for most test cases. defaultSocket := filepath.Join(os.TempDir(), "nss-integration-tests.sock") defaultDbState := "multiple_users_and_groups" - defaultOutputPath := filepath.Join(filepath.Dir(daemonPath), "gpasswd.output") - defaultGroupsFilePath := filepath.Join(testutils.TestFamilyPath(t), "gpasswd.group") + defaultGroupsFilePath := filepath.Join(filepath.Join("testdata", "empty.group")) - env := append(localgroupstestutils.AuthdIntegrationTestsEnvWithGpasswdMock(t, defaultOutputPath, defaultGroupsFilePath), "AUTHD_INTEGRATIONTESTS_CURRENT_USER_AS_ROOT=1") ctx, cancel := context.WithCancel(context.Background()) _, stopped := testutils.RunDaemon(ctx, t, daemonPath, testutils.WithSocketPath(defaultSocket), testutils.WithPreviousDBState(defaultDbState), - testutils.WithEnvironment(env...), + testutils.WithGroupFile(defaultGroupsFilePath), + testutils.WithEnvironment("AUTHD_INTEGRATIONTESTS_CURRENT_USER_AS_ROOT=1"), ) t.Cleanup(func() { @@ -115,15 +113,11 @@ func TestIntegration(t *testing.T) { if useAlternativeDaemon { // Run a specific new daemon for special test cases. - outPath := filepath.Join(t.TempDir(), "gpasswd.output") - groupsFilePath := filepath.Join("testdata", "empty.group") - var daemonStopped chan struct{} ctx, cancel := context.WithCancel(context.Background()) - env := localgroupstestutils.AuthdIntegrationTestsEnvWithGpasswdMock(t, outPath, groupsFilePath) socketPath, daemonStopped = testutils.RunDaemon(ctx, t, daemonPath, testutils.WithPreviousDBState(tc.dbState), - testutils.WithEnvironment(env...), + testutils.WithGroupFile(defaultGroupsFilePath), ) t.Cleanup(func() { cancel() @@ -172,16 +166,7 @@ func TestIntegration(t *testing.T) { } } -func TestMockgpasswd(t *testing.T) { - localgroupstestutils.Mockgpasswd(t) -} - func TestMain(m *testing.M) { - // Needed to skip the test setup when running the gpasswd mock. - if os.Getenv("GO_WANT_HELPER_PROCESS") != "" { - os.Exit(m.Run()) - } - execPath, cleanup, err := testutils.BuildDaemon("-tags=withexamplebroker,integrationtests") if err != nil { log.Printf("Setup: failed to build daemon: %v", err) diff --git a/pam/integration-tests/cli_test.go b/pam/integration-tests/cli_test.go index 214130a73c..fc12537937 100644 --- a/pam/integration-tests/cli_test.go +++ b/pam/integration-tests/cli_test.go @@ -243,21 +243,25 @@ func TestCLIAuthenticate(t *testing.T) { filepath.Join(outDir, "pam_authd")) require.NoError(t, err, "Setup: symlinking the pam client") - var socketPath, gpasswdOutput, groupsFile, pidFile string + var socketPath, groupFileOutput, pidFile string if tc.wantLocalGroups || tc.currentUserNotRoot || tc.stopDaemonAfter > 0 || tc.oldDB != "" { // For the local groups tests we need to run authd again so that it has - // special environment that generates a fake gpasswd output for us to test. + // special environment that saves the updated group file to a writable + // location for us to test. // Similarly for the not-root tests authd has to run in a more restricted way. // In the other cases this is not needed, so we can just use a shared authd. - gpasswdOutput, groupsFile = prepareGPasswdFiles(t) + var groupFile string + groupFileOutput, groupFile = prepareGroupFiles(t) pidFile = filepath.Join(outDir, "authd.pid") - socketPath = runAuthd(t, gpasswdOutput, groupsFile, !tc.currentUserNotRoot, + socketPath = runAuthd(t, !tc.currentUserNotRoot, + testutils.WithGroupFile(groupFile), + testutils.WithGroupFileOutput(groupFileOutput), testutils.WithPidFile(pidFile), testutils.WithEnvironment(useOldDatabaseEnv(t, tc.oldDB)...)) } else { - socketPath, gpasswdOutput = sharedAuthd(t) + socketPath, groupFileOutput = sharedAuthd(t) } if tc.socketPath != "" { socketPath = tc.socketPath @@ -273,13 +277,7 @@ func TestCLIAuthenticate(t *testing.T) { got := td.ExpectedOutput(t, outDir) golden.CheckOrUpdate(t, got) - if tc.wantLocalGroups || tc.oldDB != "" { - actualGroups, err := os.ReadFile(groupsFile) - require.NoError(t, err, "Failed to read the groups file") - golden.CheckOrUpdate(t, string(actualGroups), golden.WithSuffix(".groups")) - } - - localgroupstestutils.RequireGPasswdOutput(t, gpasswdOutput, golden.Path(t)+".gpasswd_out") + localgroupstestutils.RequireGroupFile(t, groupFileOutput, golden.Path(t)) requireRunnerResultForUser(t, authd.SessionMode_LOGIN, tc.clientOptions.PamUser, got) }) @@ -376,7 +374,8 @@ func TestCLIChangeAuthTok(t *testing.T) { if tc.currentUserNotRoot { // For the not-root tests authd has to run in a more restricted way. // In the other cases this is not needed, so we can just use a shared authd. - socketPath = runAuthd(t, os.DevNull, os.DevNull, false) + socketPath = runAuthd(t, false, + testutils.WithGroupFile(filepath.Join(t.TempDir(), "group"))) } else { socketPath, _ = sharedAuthd(t) } @@ -438,7 +437,3 @@ func TestPamCLIRunStandalone(t *testing.T) { require.Contains(t, outStr, pam.ErrAuthinfoUnavail.Error()) require.Contains(t, outStr, pam.ErrIgnore.Error()) } - -func TestMockgpasswd(t *testing.T) { - localgroupstestutils.Mockgpasswd(t) -} diff --git a/pam/integration-tests/helpers_test.go b/pam/integration-tests/helpers_test.go index ad7386b17f..2f22577f6b 100644 --- a/pam/integration-tests/helpers_test.go +++ b/pam/integration-tests/helpers_test.go @@ -24,7 +24,6 @@ import ( "github.com/ubuntu/authd/internal/testutils" "github.com/ubuntu/authd/internal/testutils/golden" "github.com/ubuntu/authd/internal/users/db/bbolt" - localgroupstestutils "github.com/ubuntu/authd/internal/users/localentries/testutils" "github.com/ubuntu/authd/pam/internal/pam_test" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -39,25 +38,25 @@ var ( ) type authdInstance struct { - mu sync.Mutex - refCount uint64 - socketPath string - gPasswdOutputPath string - groupsFile string - cleanup func() + mu sync.Mutex + refCount uint64 + socketPath string + groupsOutputPath string + groupsFile string + cleanup func() } var ( sharedAuthdInstance = authdInstance{} ) -func runAuthdForTesting(t *testing.T, gpasswdOutput, groupsFile string, currentUserAsRoot bool, isSharedDaemon bool, args ...testutils.DaemonOption) ( +func runAuthdForTesting(t *testing.T, currentUserAsRoot bool, isSharedDaemon bool, args ...testutils.DaemonOption) ( socketPath string, waitFunc func()) { t.Helper() ctx, cancel := context.WithCancel(context.Background()) - env := localgroupstestutils.AuthdIntegrationTestsEnvWithGpasswdMock(t, gpasswdOutput, groupsFile) + var env []string if currentUserAsRoot { env = append(env, authdCurrentUserRootEnvVariableContent) } @@ -89,15 +88,15 @@ func runAuthdForTesting(t *testing.T, gpasswdOutput, groupsFile string, currentU } } -func runAuthd(t *testing.T, gpasswdOutput, groupsFile string, currentUserAsRoot bool, args ...testutils.DaemonOption) string { +func runAuthd(t *testing.T, currentUserAsRoot bool, args ...testutils.DaemonOption) string { t.Helper() - socketPath, waitFunc := runAuthdForTesting(t, gpasswdOutput, groupsFile, currentUserAsRoot, false, args...) + socketPath, waitFunc := runAuthdForTesting(t, currentUserAsRoot, false, args...) t.Cleanup(waitFunc) return socketPath } -func sharedAuthd(t *testing.T, args ...testutils.DaemonOption) (socketPath string, gpasswdFile string) { +func sharedAuthd(t *testing.T, args ...testutils.DaemonOption) (socketPath string, groupFile string) { t.Helper() useSharedInstance := testutils.IsRace() @@ -106,11 +105,14 @@ func sharedAuthd(t *testing.T, args ...testutils.DaemonOption) (socketPath strin } if !useSharedInstance { - gPasswd := filepath.Join(t.TempDir(), "gpasswd.output") - groups := filepath.Join(testutils.TestFamilyPath(t), "gpasswd.group") - socket, cleanup := runAuthdForTesting(t, gPasswd, groups, true, useSharedInstance, args...) + groupOutput := filepath.Join(t.TempDir(), "groups") + groups := filepath.Join(testutils.TestFamilyPath(t), "groups") + args = append(args, + testutils.WithGroupFile(groups), + testutils.WithGroupFileOutput(groupOutput)) + socket, cleanup := runAuthdForTesting(t, true, useSharedInstance, args...) t.Cleanup(cleanup) - return socket, gPasswd + return socket, groupOutput } sa := &sharedAuthdInstance @@ -128,7 +130,7 @@ func sharedAuthd(t *testing.T, args ...testutils.DaemonOption) (socketPath strin require.NotNil(t, sa.cleanup) cleanup := sa.cleanup sa.socketPath = "" - sa.gPasswdOutputPath = "" + sa.groupsOutputPath = "" sa.groupsFile = "" sa.cleanup = nil cleanup() @@ -142,15 +144,16 @@ func sharedAuthd(t *testing.T, args ...testutils.DaemonOption) (socketPath strin t.Logf("Authd shared instances increased: %v", sa.refCount) } if sa.refCount != 1 { - return sa.socketPath, sa.gPasswdOutputPath + return sa.socketPath, sa.groupsOutputPath } args = append(slices.Clone(args), testutils.WithSharedDaemon(true)) - sa.gPasswdOutputPath = filepath.Join(t.TempDir(), "gpasswd.output") - sa.groupsFile = filepath.Join(testutils.TestFamilyPath(t), "gpasswd.group") - sa.socketPath, sa.cleanup = runAuthdForTesting(t, sa.gPasswdOutputPath, - sa.groupsFile, true, useSharedInstance, args...) - return sa.socketPath, sa.gPasswdOutputPath + sa.groupsFile = filepath.Join(testutils.TestFamilyPath(t), "groups") + args = append(args, testutils.WithGroupFile(sa.groupsFile)) + sa.groupsOutputPath = filepath.Join(t.TempDir(), "groups") + args = append(args, testutils.WithGroupFileOutput(sa.groupsOutputPath)) + sa.socketPath, sa.cleanup = runAuthdForTesting(t, true, useSharedInstance, args...) + return sa.socketPath, sa.groupsOutputPath } func preparePamRunnerTest(t *testing.T, clientPath string) []string { @@ -343,29 +346,29 @@ func prependBinToPath(t *testing.T) string { return "PATH=" + strings.Join([]string{filepath.Join(strings.TrimSpace(string(out)), "bin"), env}, ":") } -func prepareGPasswdFiles(t *testing.T) (string, string) { +func prepareGroupFiles(t *testing.T) (string, string) { t.Helper() cwd, err := os.Getwd() require.NoError(t, err, "Cannot get current working directory") - const groupFileName = "gpasswd.group" - gpasswdOutput := filepath.Join(t.TempDir(), "gpasswd.output") + const groupFileName = "groups" + groupOutputFile := filepath.Join(t.TempDir(), "groups") groupsFile := filepath.Join(cwd, "testdata", t.Name(), groupFileName) if ok, _ := fileutils.FileExists(groupsFile); !ok { groupsFile = filepath.Join(cwd, testutils.TestFamilyPath(t), groupFileName) } - // Do a copy of the original group file, since it may be migrated by authd. + // Do a copy of the original group file, since it may be changed by authd. tmpCopy := filepath.Join(t.TempDir(), filepath.Base(groupsFile)) err = fileutils.CopyFile(groupsFile, tmpCopy) require.NoError(t, err, "Cannot copy the group file %q", groupsFile) groupsFile = tmpCopy - saveArtifactsForDebugOnCleanup(t, []string{gpasswdOutput, groupsFile}) + saveArtifactsForDebugOnCleanup(t, []string{groupOutputFile, groupsFile}) - return gpasswdOutput, groupsFile + return groupOutputFile, groupsFile } func getUbuntuVersion(t *testing.T) int { diff --git a/pam/integration-tests/integration_test.go b/pam/integration-tests/integration_test.go index c4c85078b3..7b0fd25e92 100644 --- a/pam/integration-tests/integration_test.go +++ b/pam/integration-tests/integration_test.go @@ -13,11 +13,6 @@ const authdCurrentUserRootEnvVariableContent = "AUTHD_INTEGRATIONTESTS_CURRENT_U var daemonPath string func TestMain(m *testing.M) { - // Needed to skip the test setup when running the gpasswd mock. - if os.Getenv("GO_WANT_HELPER_PROCESS") != "" { - os.Exit(m.Run()) - } - execPath, daemonCleanup, err := testutils.BuildDaemon("-tags=withexamplebroker,integrationtests") if err != nil { log.Printf("Setup: Failed to build authd daemon: %v", err) diff --git a/pam/integration-tests/native_test.go b/pam/integration-tests/native_test.go index 3193bd29a6..66ef865981 100644 --- a/pam/integration-tests/native_test.go +++ b/pam/integration-tests/native_test.go @@ -401,22 +401,26 @@ func TestNativeAuthenticate(t *testing.T) { filepath.Join(outDir, "pam_authd")) require.NoError(t, err, "Setup: symlinking the pam client") - var socketPath, gpasswdOutput, groupsFile, pidFile string + var socketPath, groupFileOutput, pidFile string if tc.wantLocalGroups || tc.currentUserNotRoot || tc.wantSeparateDaemon || tc.oldDB != "" { // For the local groups tests we need to run authd again so that it has - // special environment that generates a fake gpasswd output for us to test. + // special environment that saves the updated group file to a writable + // location for us to test. // Similarly for the not-root tests authd has to run in a more restricted way. // In the other cases this is not needed, so we can just use a shared authd. - gpasswdOutput, groupsFile = prepareGPasswdFiles(t) + var groupFile string + groupFileOutput, groupFile = prepareGroupFiles(t) pidFile = filepath.Join(outDir, "authd.pid") - socketPath = runAuthd(t, gpasswdOutput, groupsFile, !tc.currentUserNotRoot, + socketPath = runAuthd(t, !tc.currentUserNotRoot, + testutils.WithGroupFile(groupFile), + testutils.WithGroupFileOutput(groupFileOutput), testutils.WithPidFile(pidFile), testutils.WithEnvironment(useOldDatabaseEnv(t, tc.oldDB)...)) } else { - socketPath, gpasswdOutput = sharedAuthd(t) + socketPath, groupFileOutput = sharedAuthd(t) } if tc.socketPath != "" { socketPath = tc.socketPath @@ -445,13 +449,7 @@ func TestNativeAuthenticate(t *testing.T) { got := td.ExpectedOutput(t, outDir) golden.CheckOrUpdate(t, got) - if tc.wantLocalGroups || tc.oldDB != "" { - actualGroups, err := os.ReadFile(groupsFile) - require.NoError(t, err, "Failed to read the groups file") - golden.CheckOrUpdate(t, string(actualGroups), golden.WithSuffix(".groups")) - } - - localgroupstestutils.RequireGPasswdOutput(t, gpasswdOutput, golden.Path(t)+".gpasswd_out") + localgroupstestutils.RequireGroupFile(t, groupFileOutput, golden.Path(t)) if !tc.skipRunnerCheck { requireRunnerResultForUser(t, authd.SessionMode_LOGIN, tc.clientOptions.PamUser, got) @@ -566,7 +564,8 @@ func TestNativeChangeAuthTok(t *testing.T) { if tc.currentUserNotRoot { // For the not-root tests authd has to run in a more restricted way. // In the other cases this is not needed, so we can just use a shared authd. - socketPath = runAuthd(t, os.DevNull, os.DevNull, false) + socketPath = runAuthd(t, false, + testutils.WithGroupFile(filepath.Join(t.TempDir(), "group"))) } else { socketPath, _ = sharedAuthd(t) } diff --git a/pam/integration-tests/ssh_test.go b/pam/integration-tests/ssh_test.go index 480b361329..f112893dff 100644 --- a/pam/integration-tests/ssh_test.go +++ b/pam/integration-tests/ssh_test.go @@ -121,9 +121,9 @@ func testSSHAuthenticate(t *testing.T, sharedSSHd bool) { defaultTapeSettings := []tapeSetting{{vhsHeight, 1000}, {vhsWidth, 1500}} var sshdEnv []string - var defaultSSHDPort, defaultUserHome, defaultSocketPath, defaultGPasswdOutput string + var defaultSSHDPort, defaultUserHome, defaultSocketPath, defaultGroupOutput string if sharedSSHd { - defaultSocketPath, defaultGPasswdOutput = sharedAuthd(t) + defaultSocketPath, defaultGroupOutput = sharedAuthd(t) serviceFile := createSshdServiceFile(t, execModule, execChild, pamMkHomeDirModule, defaultSocketPath) sshdEnv = append(sshdEnv, nssEnv...) sshdEnv = append(sshdEnv, fmt.Sprintf("AUTHD_NSS_SOCKET=%s", defaultSocketPath)) @@ -356,7 +356,7 @@ Wait@%dms`, sshDefaultFinalWaitTimeout), t.Parallel() socketPath := defaultSocketPath - gpasswdOutput := defaultGPasswdOutput + groupOutput := defaultGroupOutput var authdEnv []string var authdSocketLink string @@ -375,19 +375,22 @@ Wait@%dms`, sshDefaultFinalWaitTimeout), authdEnv = append(authdEnv, nssTestEnv(t, nssLibrary, authdSocketLink)...) } - var groupsFile string if tc.wantLocalGroups || tc.oldDB != "" { // For the local groups tests we need to run authd again so that it has - // special environment that generates a fake gpasswd output for us to test. - // In the other cases this is not needed, so we can just use a shared authd. - gpasswdOutput, groupsFile = prepareGPasswdFiles(t) + // special environment that saves the updated group file to a writable + // location for us to test. + var groupsFile string + groupOutput, groupsFile = prepareGroupFiles(t) authdEnv = append(authdEnv, useOldDatabaseEnv(t, tc.oldDB)...) - socketPath = runAuthd(t, gpasswdOutput, groupsFile, true, + socketPath = runAuthd(t, true, + testutils.WithGroupFile(groupsFile), + testutils.WithGroupFileOutput(groupOutput), testutils.WithEnvironment(authdEnv...)) } else if !sharedSSHd { - socketPath, gpasswdOutput = sharedAuthd(t, + socketPath, groupOutput = sharedAuthd(t, + testutils.WithGroupFileOutput(defaultGroupOutput), testutils.WithEnvironment(authdEnv...)) } if tc.socketPath != "" { @@ -511,13 +514,7 @@ Wait@%dms`, sshDefaultFinalWaitTimeout), } } - if tc.wantLocalGroups || tc.oldDB != "" { - actualGroups, err := os.ReadFile(groupsFile) - require.NoError(t, err, "Failed to read the groups file") - golden.CheckOrUpdate(t, string(actualGroups), golden.WithSuffix(".groups")) - } - - localgroupstestutils.RequireGPasswdOutput(t, gpasswdOutput, golden.Path(t)+".gpasswd_out") + localgroupstestutils.RequireGroupFile(t, groupOutput, golden.Path(t)) }) } } diff --git a/pam/integration-tests/testdata/TestCLIAuthenticate/Authenticate_user_successfully_after_db_migration/gpasswd.group b/pam/integration-tests/testdata/TestCLIAuthenticate/Authenticate_user_successfully_after_db_migration/groups similarity index 100% rename from pam/integration-tests/testdata/TestCLIAuthenticate/Authenticate_user_successfully_after_db_migration/gpasswd.group rename to pam/integration-tests/testdata/TestCLIAuthenticate/Authenticate_user_successfully_after_db_migration/groups diff --git a/pam/integration-tests/testdata/TestCLIAuthenticate/Authenticate_user_with_mixed_case_after_db_migration/gpasswd.group b/pam/integration-tests/testdata/TestCLIAuthenticate/Authenticate_user_with_mixed_case_after_db_migration/groups similarity index 100% rename from pam/integration-tests/testdata/TestCLIAuthenticate/Authenticate_user_with_mixed_case_after_db_migration/gpasswd.group rename to pam/integration-tests/testdata/TestCLIAuthenticate/Authenticate_user_with_mixed_case_after_db_migration/groups diff --git a/pam/integration-tests/testdata/TestCLIAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration/gpasswd.group b/pam/integration-tests/testdata/TestCLIAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration/groups similarity index 100% rename from pam/integration-tests/testdata/TestCLIAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration/gpasswd.group rename to pam/integration-tests/testdata/TestCLIAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration/groups diff --git a/pam/integration-tests/testdata/TestCLIAuthenticate/gpasswd.group b/pam/integration-tests/testdata/TestCLIAuthenticate/groups similarity index 100% rename from pam/integration-tests/testdata/TestCLIAuthenticate/gpasswd.group rename to pam/integration-tests/testdata/TestCLIAuthenticate/groups diff --git a/pam/integration-tests/testdata/TestCLIChangeAuthTok/gpasswd.group b/pam/integration-tests/testdata/TestCLIChangeAuthTok/groups similarity index 100% rename from pam/integration-tests/testdata/TestCLIChangeAuthTok/gpasswd.group rename to pam/integration-tests/testdata/TestCLIChangeAuthTok/groups diff --git a/pam/integration-tests/testdata/TestGdmModule/gpasswd.group b/pam/integration-tests/testdata/TestGdmModule/groups similarity index 100% rename from pam/integration-tests/testdata/TestGdmModule/gpasswd.group rename to pam/integration-tests/testdata/TestGdmModule/groups diff --git a/pam/integration-tests/testdata/TestNativeAuthenticate/Authenticate_user_successfully_after_db_migration/gpasswd.group b/pam/integration-tests/testdata/TestNativeAuthenticate/Authenticate_user_successfully_after_db_migration/groups similarity index 100% rename from pam/integration-tests/testdata/TestNativeAuthenticate/Authenticate_user_successfully_after_db_migration/gpasswd.group rename to pam/integration-tests/testdata/TestNativeAuthenticate/Authenticate_user_successfully_after_db_migration/groups diff --git a/pam/integration-tests/testdata/TestNativeAuthenticate/Authenticate_user_with_mixed_case_after_db_migration/gpasswd.group b/pam/integration-tests/testdata/TestNativeAuthenticate/Authenticate_user_with_mixed_case_after_db_migration/groups similarity index 100% rename from pam/integration-tests/testdata/TestNativeAuthenticate/Authenticate_user_with_mixed_case_after_db_migration/gpasswd.group rename to pam/integration-tests/testdata/TestNativeAuthenticate/Authenticate_user_with_mixed_case_after_db_migration/groups diff --git a/pam/integration-tests/testdata/TestNativeAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration/gpasswd.group b/pam/integration-tests/testdata/TestNativeAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration/groups similarity index 100% rename from pam/integration-tests/testdata/TestNativeAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration/gpasswd.group rename to pam/integration-tests/testdata/TestNativeAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration/groups diff --git a/pam/integration-tests/testdata/TestNativeAuthenticate/gpasswd.group b/pam/integration-tests/testdata/TestNativeAuthenticate/groups similarity index 100% rename from pam/integration-tests/testdata/TestNativeAuthenticate/gpasswd.group rename to pam/integration-tests/testdata/TestNativeAuthenticate/groups diff --git a/pam/integration-tests/testdata/TestNativeChangeAuthTok/gpasswd.group b/pam/integration-tests/testdata/TestNativeChangeAuthTok/groups similarity index 100% rename from pam/integration-tests/testdata/TestNativeChangeAuthTok/gpasswd.group rename to pam/integration-tests/testdata/TestNativeChangeAuthTok/groups diff --git a/pam/integration-tests/testdata/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration/gpasswd.group b/pam/integration-tests/testdata/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration/groups similarity index 100% rename from pam/integration-tests/testdata/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration/gpasswd.group rename to pam/integration-tests/testdata/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration/groups diff --git a/pam/integration-tests/testdata/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration/gpasswd.group b/pam/integration-tests/testdata/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration/groups similarity index 100% rename from pam/integration-tests/testdata/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration/gpasswd.group rename to pam/integration-tests/testdata/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration/groups diff --git a/pam/integration-tests/testdata/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration/gpasswd.group b/pam/integration-tests/testdata/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration/groups similarity index 100% rename from pam/integration-tests/testdata/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration/gpasswd.group rename to pam/integration-tests/testdata/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration/groups diff --git a/pam/integration-tests/testdata/TestSSHAuthenticate/gpasswd.group b/pam/integration-tests/testdata/TestSSHAuthenticate/groups similarity index 100% rename from pam/integration-tests/testdata/TestSSHAuthenticate/gpasswd.group rename to pam/integration-tests/testdata/TestSSHAuthenticate/groups diff --git a/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_and_add_it_to_local_group b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_and_add_it_to_local_group index 6a23d5b739..48284881c8 100644 --- a/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_and_add_it_to_local_group +++ b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_and_add_it_to_local_group @@ -2,7 +2,7 @@ Username: user name ──────────────────────────────────────────────────────────────────────────────── > ./pam_authd login socket=${AUTHD_TEST_TAPE_SOCKET} -Username: user-local-groups-integration-auth-native +Username: user-local-groups-integration-auth-cli ──────────────────────────────────────────────────────────────────────────────── > ./pam_authd login socket=${AUTHD_TEST_TAPE_SOCKET} Select your provider @@ -26,10 +26,10 @@ Gimme your password: ──────────────────────────────────────────────────────────────────────────────── > ./pam_authd login socket=${AUTHD_TEST_TAPE_SOCKET} PAM Authenticate() - User: "user-local-groups-integration-auth-native" + User: "user-local-groups-integration-auth-cli" Result: success PAM AcctMgmt() - User: "user-local-groups-integration-auth-native" + User: "user-local-groups-integration-auth-cli" Result: success > ──────────────────────────────────────────────────────────────────────────────── diff --git a/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_and_add_it_to_local_group.gpasswd_out b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_and_add_it_to_local_group.gpasswd_out deleted file mode 100644 index 4f5e85619d..0000000000 --- a/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_and_add_it_to_local_group.gpasswd_out +++ /dev/null @@ -1 +0,0 @@ ---add user-local-groups-integration-auth-native localgroup \ No newline at end of file diff --git a/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_and_add_it_to_local_group.group b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_and_add_it_to_local_group.group new file mode 100644 index 0000000000..d2dc4c7614 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_and_add_it_to_local_group.group @@ -0,0 +1 @@ +localgroup:x:41:user-local-groups-integration-auth-cli diff --git a/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_and_add_it_to_local_group.groups b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_and_add_it_to_local_group.group.backup similarity index 100% rename from pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_and_add_it_to_local_group.groups rename to pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_and_add_it_to_local_group.group.backup diff --git a/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_successfully_after_db_migration.groups b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_successfully_after_db_migration.groups deleted file mode 100644 index f31a69be33..0000000000 --- a/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_successfully_after_db_migration.groups +++ /dev/null @@ -1,6 +0,0 @@ -localgroup:x:41:user-integration-with-mixed-case,user-integration-cached,user-integration-pre-check-with-upper-case,user-integration-upper-case,user-pre-check -some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-with-mixed-case -some-group-for-user-integration-cached:x:102:user-integration-cached -some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-with-upper-case -some-group-for-user-integration-UPPER-CASE:x:104:user-integration-upper-case -some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.groups b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.groups deleted file mode 100644 index f31a69be33..0000000000 --- a/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.groups +++ /dev/null @@ -1,6 +0,0 @@ -localgroup:x:41:user-integration-with-mixed-case,user-integration-cached,user-integration-pre-check-with-upper-case,user-integration-upper-case,user-pre-check -some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-with-mixed-case -some-group-for-user-integration-cached:x:102:user-integration-cached -some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-with-upper-case -some-group-for-user-integration-UPPER-CASE:x:104:user-integration-upper-case -some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.groups b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.groups deleted file mode 100644 index f31a69be33..0000000000 --- a/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.groups +++ /dev/null @@ -1,6 +0,0 @@ -localgroup:x:41:user-integration-with-mixed-case,user-integration-cached,user-integration-pre-check-with-upper-case,user-integration-upper-case,user-pre-check -some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-with-mixed-case -some-group-for-user-integration-cached:x:102:user-integration-cached -some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-with-upper-case -some-group-for-user-integration-UPPER-CASE:x:104:user-integration-upper-case -some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_and_add_it_to_local_group.gpasswd_out b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_and_add_it_to_local_group.gpasswd_out deleted file mode 100644 index 4f5e85619d..0000000000 --- a/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_and_add_it_to_local_group.gpasswd_out +++ /dev/null @@ -1 +0,0 @@ ---add user-local-groups-integration-auth-native localgroup \ No newline at end of file diff --git a/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_and_add_it_to_local_group.group b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_and_add_it_to_local_group.group new file mode 100644 index 0000000000..71e6393e37 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_and_add_it_to_local_group.group @@ -0,0 +1 @@ +localgroup:x:41:user-local-groups-integration-auth-native diff --git a/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_and_add_it_to_local_group.groups b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_and_add_it_to_local_group.group.backup similarity index 100% rename from pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_and_add_it_to_local_group.groups rename to pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_and_add_it_to_local_group.group.backup diff --git a/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_successfully_after_db_migration.groups b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_successfully_after_db_migration.groups deleted file mode 100644 index f31a69be33..0000000000 --- a/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_successfully_after_db_migration.groups +++ /dev/null @@ -1,6 +0,0 @@ -localgroup:x:41:user-integration-with-mixed-case,user-integration-cached,user-integration-pre-check-with-upper-case,user-integration-upper-case,user-pre-check -some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-with-mixed-case -some-group-for-user-integration-cached:x:102:user-integration-cached -some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-with-upper-case -some-group-for-user-integration-UPPER-CASE:x:104:user-integration-upper-case -some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.groups b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.groups deleted file mode 100644 index f31a69be33..0000000000 --- a/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.groups +++ /dev/null @@ -1,6 +0,0 @@ -localgroup:x:41:user-integration-with-mixed-case,user-integration-cached,user-integration-pre-check-with-upper-case,user-integration-upper-case,user-pre-check -some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-with-mixed-case -some-group-for-user-integration-cached:x:102:user-integration-cached -some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-with-upper-case -some-group-for-user-integration-UPPER-CASE:x:104:user-integration-upper-case -some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.groups b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.groups deleted file mode 100644 index f31a69be33..0000000000 --- a/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.groups +++ /dev/null @@ -1,6 +0,0 @@ -localgroup:x:41:user-integration-with-mixed-case,user-integration-cached,user-integration-pre-check-with-upper-case,user-integration-upper-case,user-pre-check -some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-with-mixed-case -some-group-for-user-integration-cached:x:102:user-integration-cached -some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-with-upper-case -some-group-for-user-integration-UPPER-CASE:x:104:user-integration-upper-case -some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_and_add_it_to_local_group.gpasswd_out b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_and_add_it_to_local_group.gpasswd_out deleted file mode 100644 index a8528cc444..0000000000 --- a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_and_add_it_to_local_group.gpasswd_out +++ /dev/null @@ -1 +0,0 @@ ---add user-local-groups-integration-pre-check-ssh-authenticate-user-and-add-it-to-local-group localgroup \ No newline at end of file diff --git a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_and_add_it_to_local_group.group b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_and_add_it_to_local_group.group new file mode 100644 index 0000000000..e084191014 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_and_add_it_to_local_group.group @@ -0,0 +1 @@ +localgroup:x:41:user-local-groups-integration-pre-check-ssh-authenticate-user-and-add-it-to-local-group diff --git a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_and_add_it_to_local_group.groups b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_and_add_it_to_local_group.group.backup similarity index 100% rename from pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_and_add_it_to_local_group.groups rename to pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_and_add_it_to_local_group.group.backup diff --git a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_and_add_it_to_local_group_on_shared_SSHd.gpasswd_out b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_and_add_it_to_local_group_on_shared_SSHd.gpasswd_out deleted file mode 100644 index ffd6a5fda6..0000000000 --- a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_and_add_it_to_local_group_on_shared_SSHd.gpasswd_out +++ /dev/null @@ -1 +0,0 @@ ---add user-local-groups-integration-pre-check-ssh-authenticate-user-and-add-it-to-local-group-on-shared-sshd localgroup \ No newline at end of file diff --git a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_and_add_it_to_local_group_on_shared_SSHd.group b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_and_add_it_to_local_group_on_shared_SSHd.group new file mode 100644 index 0000000000..5222b1a7d6 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_and_add_it_to_local_group_on_shared_SSHd.group @@ -0,0 +1 @@ +localgroup:x:41:user-local-groups-integration-pre-check-ssh-authenticate-user-and-add-it-to-local-group-on-shared-sshd diff --git a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_and_add_it_to_local_group_on_shared_SSHd.groups b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_and_add_it_to_local_group_on_shared_SSHd.group.backup similarity index 100% rename from pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_and_add_it_to_local_group_on_shared_SSHd.groups rename to pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_and_add_it_to_local_group_on_shared_SSHd.group.backup diff --git a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration.groups b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration.groups deleted file mode 100644 index f31a69be33..0000000000 --- a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration.groups +++ /dev/null @@ -1,6 +0,0 @@ -localgroup:x:41:user-integration-with-mixed-case,user-integration-cached,user-integration-pre-check-with-upper-case,user-integration-upper-case,user-pre-check -some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-with-mixed-case -some-group-for-user-integration-cached:x:102:user-integration-cached -some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-with-upper-case -some-group-for-user-integration-UPPER-CASE:x:104:user-integration-upper-case -some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration_on_shared_SSHd.groups b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration_on_shared_SSHd.groups deleted file mode 100644 index f31a69be33..0000000000 --- a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration_on_shared_SSHd.groups +++ /dev/null @@ -1,6 +0,0 @@ -localgroup:x:41:user-integration-with-mixed-case,user-integration-cached,user-integration-pre-check-with-upper-case,user-integration-upper-case,user-pre-check -some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-with-mixed-case -some-group-for-user-integration-cached:x:102:user-integration-cached -some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-with-upper-case -some-group-for-user-integration-UPPER-CASE:x:104:user-integration-upper-case -some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.groups b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.groups deleted file mode 100644 index f31a69be33..0000000000 --- a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.groups +++ /dev/null @@ -1,6 +0,0 @@ -localgroup:x:41:user-integration-with-mixed-case,user-integration-cached,user-integration-pre-check-with-upper-case,user-integration-upper-case,user-pre-check -some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-with-mixed-case -some-group-for-user-integration-cached:x:102:user-integration-cached -some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-with-upper-case -some-group-for-user-integration-UPPER-CASE:x:104:user-integration-upper-case -some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration_on_shared_SSHd.groups b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration_on_shared_SSHd.groups deleted file mode 100644 index f31a69be33..0000000000 --- a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration_on_shared_SSHd.groups +++ /dev/null @@ -1,6 +0,0 @@ -localgroup:x:41:user-integration-with-mixed-case,user-integration-cached,user-integration-pre-check-with-upper-case,user-integration-upper-case,user-pre-check -some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-with-mixed-case -some-group-for-user-integration-cached:x:102:user-integration-cached -some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-with-upper-case -some-group-for-user-integration-UPPER-CASE:x:104:user-integration-upper-case -some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.groups b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.groups deleted file mode 100644 index f31a69be33..0000000000 --- a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.groups +++ /dev/null @@ -1,6 +0,0 @@ -localgroup:x:41:user-integration-with-mixed-case,user-integration-cached,user-integration-pre-check-with-upper-case,user-integration-upper-case,user-pre-check -some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-with-mixed-case -some-group-for-user-integration-cached:x:102:user-integration-cached -some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-with-upper-case -some-group-for-user-integration-UPPER-CASE:x:104:user-integration-upper-case -some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration_on_shared_SSHd.groups b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration_on_shared_SSHd.groups deleted file mode 100644 index f31a69be33..0000000000 --- a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration_on_shared_SSHd.groups +++ /dev/null @@ -1,6 +0,0 @@ -localgroup:x:41:user-integration-with-mixed-case,user-integration-cached,user-integration-pre-check-with-upper-case,user-integration-upper-case,user-pre-check -some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-with-mixed-case -some-group-for-user-integration-cached:x:102:user-integration-cached -some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-with-upper-case -some-group-for-user-integration-UPPER-CASE:x:104:user-integration-upper-case -some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/tapes/cli/local_group.tape b/pam/integration-tests/testdata/tapes/cli/local_group.tape index 97b25c1027..73f78d71b8 100644 --- a/pam/integration-tests/testdata/tapes/cli/local_group.tape +++ b/pam/integration-tests/testdata/tapes/cli/local_group.tape @@ -5,7 +5,7 @@ Wait /Username: user name\n/ Show Hide -TypeUsername "user-local-groups-integration-auth-native" +TypeUsername "user-local-groups-integration-auth-cli" Show Hide From 15db4c17c09325bbfa668040b6250d0888084f03 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 26 Jun 2025 13:51:16 +0200 Subject: [PATCH 0528/1670] users/localentries: Improve Update documentation --- internal/users/localentries/localgroups.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/users/localentries/localgroups.go b/internal/users/localentries/localgroups.go index 0da587029b..f7780c98f7 100644 --- a/internal/users/localentries/localgroups.go +++ b/internal/users/localentries/localgroups.go @@ -39,7 +39,9 @@ type Option func(*options) var localGroupsMu = &sync.RWMutex{} -// Update synchronizes for the given user the local group list with the current group list from UserInfo. +// Update updates the local groups for a user, adding them to the groups in +// newGroups which they are not already part of, and removing them from the +// groups in oldGroups which are not in newGroups. func Update(username string, newGroups []string, oldGroups []string, args ...Option) (err error) { log.Debugf(context.TODO(), "Updating local groups for user %q, new groups: %v, old groups: %v", username, newGroups, oldGroups) defer decorate.OnError(&err, "could not update local groups for user %q", username) From 6e331094eee30ac9bd08d146f9d11ae593b5dc5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 23 Jun 2025 21:55:15 +0200 Subject: [PATCH 0529/1670] users/localentries/localgroups: Protect access to write group operations Add a structure to ensure that the operations on groups that require locking are performed in such state, and that the lock is released. The lock is still referenced to allow concurrent requests (at least for reading). --- .../IsAuthenticated | 2 +- internal/users/localentries/localgroups.go | 132 +++++++++++++----- .../users/localentries/localgroups_test.go | 111 +++++++++++++-- internal/users/manager.go | 8 +- 4 files changed, 208 insertions(+), 45 deletions(-) diff --git a/internal/services/pam/testdata/golden/TestIsAuthenticated/Error_on_updating_local_groups_with_unexisting_file/IsAuthenticated b/internal/services/pam/testdata/golden/TestIsAuthenticated/Error_on_updating_local_groups_with_unexisting_file/IsAuthenticated index a5a3f884b8..8bf4930833 100644 --- a/internal/services/pam/testdata/golden/TestIsAuthenticated/Error_on_updating_local_groups_with_unexisting_file/IsAuthenticated +++ b/internal/services/pam/testdata/golden/TestIsAuthenticated/Error_on_updating_local_groups_with_unexisting_file/IsAuthenticated @@ -1,4 +1,4 @@ FIRST CALL: access: msg: - err: can't check authentication: failed to update user "testisauthenticated/error_on_updating_local_groups_with_unexisting_file_separator_success_with_local_groups": could not update local groups for user "testisauthenticated/error_on_updating_local_groups_with_unexisting_file_separator_success_with_local_groups": could not fetch existing local group for user "testisauthenticated/error_on_updating_local_groups_with_unexisting_file_separator_success_with_local_groups": could not fetch existing local group: open : no such file or directory + err: can't check authentication: failed to update user "testisauthenticated/error_on_updating_local_groups_with_unexisting_file_separator_success_with_local_groups": could not lock local groups: could not fetch existing local group: open : no such file or directory diff --git a/internal/users/localentries/localgroups.go b/internal/users/localentries/localgroups.go index f7780c98f7..6d6a1d08f4 100644 --- a/internal/users/localentries/localgroups.go +++ b/internal/users/localentries/localgroups.go @@ -15,6 +15,7 @@ import ( "github.com/ubuntu/authd/internal/fileutils" "github.com/ubuntu/authd/internal/sliceutils" + "github.com/ubuntu/authd/internal/testsdetection" userslocking "github.com/ubuntu/authd/internal/users/locking" "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/authd/log" @@ -37,36 +38,106 @@ type options struct { // Option represents an optional function to override UpdateLocalGroups default values. type Option func(*options) -var localGroupsMu = &sync.RWMutex{} +// GroupsWithLock is a struct that holds the current user groups and provides methods to +// retrieve and update them while ensuring that the system's user database is locked +// to prevent concurrent modifications. +type GroupsWithLock struct { + // mu is a mutex that protects the refCount and entries fields. + mu sync.RWMutex + // refCount is used to track how many times the GroupsWithLock instance has been returned by GetGroupsWithLock. + refCount uint64 + // entries holds the current group entries. + entries []types.GroupEntry + // options for testing purposes. + options options +} -// Update updates the local groups for a user, adding them to the groups in -// newGroups which they are not already part of, and removing them from the -// groups in oldGroups which are not in newGroups. -func Update(username string, newGroups []string, oldGroups []string, args ...Option) (err error) { - log.Debugf(context.TODO(), "Updating local groups for user %q, new groups: %v, old groups: %v", username, newGroups, oldGroups) - defer decorate.OnError(&err, "could not update local groups for user %q", username) +// defaultGroupsWithLock is used as the instance for locked groups when +// no test options are provided. +var defaultGroupsWithLock = &GroupsWithLock{} + +// GetGroupsWithLock gets a GroupsWithLock instance with a lock on the system's user database. +// It returns a cleanup function that should be called to unlock system's user database. +func GetGroupsWithLock(args ...Option) (groups *GroupsWithLock, cleanup func() error, err error) { + defer decorate.OnError(&err, "could not lock local groups") + + if err := userslocking.WriteRecLock(); err != nil { + return nil, nil, err + } + + cleanupUnlocked := func() error { + if groups.refCount == 0 { + return fmt.Errorf("groups were already unlocked") + } + + groups.refCount-- + if groups.refCount == 0 { + groups.entries = nil + } + return userslocking.WriteRecUnlock() + } + + cleanup = func() error { + groups.mu.Lock() + defer groups.mu.Unlock() + + return cleanupUnlocked() + } + + groups = defaultGroupsWithLock + testingMode := len(args) != 0 + + if testingMode { + testsdetection.MustBeTesting() + groups = &GroupsWithLock{} + } + + groups.mu.Lock() + defer groups.mu.Unlock() + + groups.refCount++ + if groups.refCount > 1 { + return groups, cleanup, nil + } opts := defaultOptions for _, arg := range args { arg(&opts) } - if err := userslocking.WriteRecLock(); err != nil { - return err + groups.options = opts + groups.entries, err = parseLocalGroups(opts.groupInputPath) + if err != nil { + return nil, nil, errors.Join(err, cleanupUnlocked()) } - defer func() { - if unlockErr := userslocking.WriteRecUnlock(); unlockErr != nil { - err = errors.Join(err, unlockErr) - } - }() - localGroupsMu.Lock() - defer localGroupsMu.Unlock() + return groups, cleanup, nil +} - allGroups, userGroups, err := existingLocalGroups(username, opts.groupInputPath) - if err != nil { - return err +func (g *GroupsWithLock) mustLock() (cleanup func()) { + g.mu.Lock() + cleanup = g.mu.Unlock + + if g.refCount == 0 { + defer cleanup() + panic("locked groups are not locked!") } + + return cleanup +} + +// Update updates the local groups for a user, adding them to the groups in +// newGroups which they are not already part of, and removing them from the +// groups in oldGroups which are not in newGroups. +func (g *GroupsWithLock) Update(username string, newGroups []string, oldGroups []string) (err error) { + log.Debugf(context.TODO(), "Updating local groups for user %q, new groups: %v, old groups: %v", username, newGroups, oldGroups) + defer decorate.OnError(&err, "could not update local groups for user %q", username) + + unlock := g.mustLock() + defer unlock() + + allGroups := types.DeepCopyGroupEntries(g.entries) + userGroups := g.userLocalGroups(username) currentGroupsNames := sliceutils.Map(userGroups, func(g types.GroupEntry) string { return g.Name }) @@ -107,7 +178,7 @@ func Update(username string, newGroups []string, oldGroups []string, args ...Opt group.Users = append(group.Users, username) } - return saveLocalGroups(opts.groupInputPath, opts.groupOutputPath, allGroups) + return g.saveLocalGroups(allGroups) } func parseLocalGroups(groupPath string) (groups []types.GroupEntry, err error) { @@ -184,7 +255,10 @@ func formatGroupEntries(groups []types.GroupEntry) string { return strings.Join(groupLines, "\n") } -func saveLocalGroups(inputPath, groupPath string, groups []types.GroupEntry) (err error) { +func (g *GroupsWithLock) saveLocalGroups(groups []types.GroupEntry) (err error) { + inputPath := g.options.groupInputPath + groupPath := g.options.groupOutputPath + defer decorate.OnError(&err, "could not write local groups to %q", groupPath) if err := types.ValidateGroupEntries(groups); err != nil { @@ -218,19 +292,13 @@ func saveLocalGroups(inputPath, groupPath string, groups []types.GroupEntry) (er return fmt.Errorf("error renaming %s to %s: %w", tempPath, groupPath, err) } + g.entries = types.DeepCopyGroupEntries(groups) return nil } -// existingLocalGroups returns all the available groups and which groups from groupPath the user is part of. -func existingLocalGroups(user, groupPath string) (groups, userGroups []types.GroupEntry, err error) { - defer decorate.OnError(&err, "could not fetch existing local group for user %q", user) - - groups, err = parseLocalGroups(groupPath) - if err != nil { - return nil, nil, err - } - - return groups, slices.DeleteFunc(slices.Clone(groups), func(g types.GroupEntry) bool { +// userLocalGroups returns all groups the user is part of. +func (g *GroupsWithLock) userLocalGroups(user string) (userGroups []types.GroupEntry) { + return slices.DeleteFunc(slices.Clone(g.entries), func(g types.GroupEntry) bool { return !slices.Contains(g.Users, user) - }), nil + }) } diff --git a/internal/users/localentries/localgroups_test.go b/internal/users/localentries/localgroups_test.go index 48d02c00fa..cc366f837c 100644 --- a/internal/users/localentries/localgroups_test.go +++ b/internal/users/localentries/localgroups_test.go @@ -1,7 +1,9 @@ package localentries_test import ( + "fmt" "path/filepath" + "sync" "testing" "github.com/stretchr/testify/require" @@ -23,7 +25,8 @@ func TestUpdatelocalentries(t *testing.T) { oldGroups []string groupFilePath string - wantErr bool + wantLockErr bool + wantUpdateErr bool }{ // First insertion cases "Insert_new_user_in_existing_files_with_no_users_in_our_group": {groupFilePath: "no_users_in_our_groups.group"}, @@ -53,27 +56,30 @@ func TestUpdatelocalentries(t *testing.T) { "User_is_not_removed_from_groups_they_are_not_part_of": {newGroups: []string{}, oldGroups: []string{"localgroup2"}, groupFilePath: "user_in_one_group.group"}, // Error cases - "Error_on_missing_groups_file": {groupFilePath: "does_not_exists.group", wantErr: true}, + "Error_on_missing_groups_file": { + groupFilePath: "does_not_exists.group", + wantLockErr: true, + }, "Error_on_invalid_user_name": { groupFilePath: "no_users_in_our_groups.group", username: "no,commas,please", - wantErr: true, + wantUpdateErr: true, }, "Error_when_groups_file_has_missing_fields": { groupFilePath: "malformed_file_missing_field.group", - wantErr: true, + wantLockErr: true, }, "Error_when_groups_file_has_invalid_gid": { groupFilePath: "malformed_file_invalid_gid.group", - wantErr: true, + wantLockErr: true, }, "Error_when_groups_file_has_no_group_name": { groupFilePath: "malformed_file_no_group_name.group", - wantErr: true, + wantLockErr: true, }, "Error_when_groups_file_has_a_duplicated_group": { groupFilePath: "malformed_file_duplicated.group", - wantErr: true, + wantLockErr: true, }, } for name, tc := range tests { @@ -101,18 +107,101 @@ func TestUpdatelocalentries(t *testing.T) { inputGroupFilePath = tempGroupFile } - err := localentries.Update(tc.username, tc.newGroups, tc.oldGroups, + defer localentriestestutils.RequireGroupFile(t, outputGroupFilePath, golden.Path(t)) + + lg, cleanup, err := localentries.GetGroupsWithLock( localentries.WithGroupInputPath(inputGroupFilePath), - localentries.WithGroupOutputPath(outputGroupFilePath)) - if tc.wantErr { + localentries.WithGroupOutputPath(outputGroupFilePath), + ) + if tc.wantLockErr { + require.Error(t, err, "GetGroupsWithLock should have failed") + return + } + require.NoError(t, err, "Setup: failed to lock the users group") + t.Cleanup(func() { require.NoError(t, cleanup(), "Releasing unlocked groups") }) + + err = lg.Update(tc.username, tc.newGroups, tc.oldGroups) + if tc.wantUpdateErr { require.Error(t, err, "Updatelocalentries should have failed") } else { require.NoError(t, err, "Updatelocalentries should not have failed") } + }) + } +} + +//nolint:tparallel // This can't be parallel, but subtests can. +func TestRacingLockingActions(t *testing.T) { + const nIterations = 50 + + testFilePath := filepath.Join("testdata", "no_users_in_our_groups.group") + + wg := sync.WaitGroup{} + wg.Add(nIterations) + + // Lock and get the values in parallel. + for idx := range nIterations { + t.Run(fmt.Sprintf("iteration_%d", idx), func(t *testing.T) { + t.Parallel() + + t.Cleanup(wg.Done) + + var opts []localentries.Option + useTestGroupFile := idx%3 == 0 - localentriestestutils.RequireGroupFile(t, outputGroupFilePath, golden.Path(t)) + if useTestGroupFile { + // Mix the requests with test-only code paths... + opts = append(opts, localentries.WithGroupPath(testFilePath)) + } + + lg, unlock, err := localentries.GetGroupsWithLock(opts...) + require.NoError(t, err, "Failed to lock the users group (test groups: %v)", useTestGroupFile) + err = lg.Update("", nil, nil) + require.NoError(t, err, "Update should not fail (test groups: %v)", useTestGroupFile) + err = unlock() + require.NoError(t, err, "Unlock should not fail to lock the users group (test groups: %v)", useTestGroupFile) }) } + + t.Run("final_check", func(t *testing.T) { + t.Parallel() + wg.Wait() + + // Get a last unlock function, to see if we're all good... + lg, unlock, err := localentries.GetGroupsWithLock() + require.NoError(t, err, "Failed to lock the users group") + err = unlock() + require.NoError(t, err, "Unlock should not fail to lock the users group") + + // Ensure that we had cleaned up all the locks correctly! + require.Panics(t, func() { _ = lg.Update("", nil, nil) }) + }) +} + +func TestLockedInvalidActions(t *testing.T) { + // This cannot be parallel + + lg, unlock, err := localentries.GetGroupsWithLock() + + require.NoError(t, err, "Setup: failed to lock the users group") + err = unlock() + require.NoError(t, err, "Unlock should not fail to lock the users group") + + err = unlock() + require.Error(t, err, "Unlocking twice should fail") + + require.Panics(t, func() { _ = lg.Update("", nil, nil) }, + "Update should panic but did not") + + // This is to ensure that we're in a good state, despite the actions above + for range 10 { + lg, unlock, err = localentries.GetGroupsWithLock() + require.NoError(t, err, "Failed to lock the users group") + defer func() { + err := unlock() + require.NoError(t, err, "Unlock should not fail to lock the users group") + }() + } } func TestMain(m *testing.M) { diff --git a/internal/users/manager.go b/internal/users/manager.go index b700e77d0d..4901f41c65 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -225,8 +225,14 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { return err } + lockedGroups, cleanup, err := localentries.GetGroupsWithLock() + if err != nil { + return err + } + defer func() { err = errors.Join(err, cleanup()) }() + // Update local groups. - if err := localentries.Update(u.Name, localGroups, oldLocalGroups); err != nil { + if err := lockedGroups.Update(u.Name, localGroups, oldLocalGroups); err != nil { return err } From 1fc45443a188959f8a268ada7da32c35e4c84097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 25 Jun 2025 04:43:40 +0200 Subject: [PATCH 0530/1670] users/localentries/localgroups: Add ability to get and save groups We may need to manually manipulate groups in some cases, so do add methods that allow to do this and that must be operating when we're locked --- internal/users/localentries/localgroups.go | 35 +++ .../users/localentries/localgroups_test.go | 232 +++++++++++++++++- .../users/localentries/testdata/empty.group | 0 .../Empty_group_adding_one_group.group | 1 + .../Empty_group_adding_one_group.group.backup | 0 ...group_adding_one_group_with_one_user.group | 1 + ...dding_one_group_with_one_user.group.backup | 0 ...oup_adding_two_groups_with_two_users.group | 2 + ...ing_two_groups_with_two_users.group.backup | 0 ...licated_equal_group_to_existing_file.group | 7 + ..._equal_group_to_existing_file.group.backup | 6 + ...nores_adding_duplicated_equal_groups.group | 2 + ...dding_duplicated_equal_groups.group.backup | 0 ...th_multiple_other_users_in_our_group.group | 6 + ...iple_other_users_in_our_group.group.backup | 6 + ...ing_files_with_no_users_in_our_group.group | 6 + ...es_with_no_users_in_our_group.group.backup | 6 + ..._files_with_other_users_in_our_group.group | 6 + ...with_other_users_in_our_group.group.backup | 6 + ..._new_user_when_no_users_in_any_group.group | 6 + ...er_when_no_users_in_any_group.group.backup | 6 + .../Removes_group_correctly.group | 4 + .../Removes_group_correctly.group.backup | 6 + 23 files changed, 341 insertions(+), 3 deletions(-) create mode 100644 internal/users/localentries/testdata/empty.group create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Empty_group_adding_one_group.group create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Empty_group_adding_one_group.group.backup create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Empty_group_adding_one_group_with_one_user.group create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Empty_group_adding_one_group_with_one_user.group.backup create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Empty_group_adding_two_groups_with_two_users.group create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Empty_group_adding_two_groups_with_two_users.group.backup create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Ignores_adding_duplicated_equal_group_to_existing_file.group create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Ignores_adding_duplicated_equal_group_to_existing_file.group.backup create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Ignores_adding_duplicated_equal_groups.group create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Ignores_adding_duplicated_equal_groups.group.backup create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_in_existing_files_with_multiple_other_users_in_our_group.group create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_in_existing_files_with_multiple_other_users_in_our_group.group.backup create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_in_existing_files_with_no_users_in_our_group.group create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_in_existing_files_with_no_users_in_our_group.group.backup create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_in_existing_files_with_other_users_in_our_group.group create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_in_existing_files_with_other_users_in_our_group.group.backup create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_when_no_users_in_any_group.group create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_when_no_users_in_any_group.group.backup create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Removes_group_correctly.group create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Removes_group_correctly.group.backup diff --git a/internal/users/localentries/localgroups.go b/internal/users/localentries/localgroups.go index 6d6a1d08f4..4f2d982c6b 100644 --- a/internal/users/localentries/localgroups.go +++ b/internal/users/localentries/localgroups.go @@ -126,6 +126,36 @@ func (g *GroupsWithLock) mustLock() (cleanup func()) { return cleanup } +func (g *GroupsWithLock) mustRLock() (cleanup func()) { + g.mu.RLock() + cleanup = g.mu.RUnlock + + if g.refCount == 0 { + defer cleanup() + panic("locked groups are not locked!") + } + + return cleanup +} + +// GetEntries returns a copy of the current group entries. +func (g *GroupsWithLock) GetEntries() (entries []types.GroupEntry) { + unlock := g.mustRLock() + defer unlock() + + return types.DeepCopyGroupEntries(g.entries) +} + +// SaveEntries saves the provided group entries to the local group file. +func (g *GroupsWithLock) SaveEntries(entries []types.GroupEntry) (err error) { + defer decorate.OnError(&err, "could not save groups") + + unlock := g.mustLock() + defer unlock() + + return g.saveLocalGroups(entries) +} + // Update updates the local groups for a user, adding them to the groups in // newGroups which they are not already part of, and removing them from the // groups in oldGroups which are not in newGroups. @@ -261,6 +291,11 @@ func (g *GroupsWithLock) saveLocalGroups(groups []types.GroupEntry) (err error) defer decorate.OnError(&err, "could not write local groups to %q", groupPath) + if slices.EqualFunc(g.entries, groups, types.GroupEntry.Equals) { + log.Debugf(context.TODO(), "Nothing to do, groups are equal") + return nil + } + if err := types.ValidateGroupEntries(groups); err != nil { return err } diff --git a/internal/users/localentries/localgroups_test.go b/internal/users/localentries/localgroups_test.go index cc366f837c..6f59f2b09e 100644 --- a/internal/users/localentries/localgroups_test.go +++ b/internal/users/localentries/localgroups_test.go @@ -2,16 +2,20 @@ package localentries_test import ( "fmt" + "os" "path/filepath" + "slices" "sync" "testing" "github.com/stretchr/testify/require" "github.com/ubuntu/authd/internal/fileutils" + "github.com/ubuntu/authd/internal/testutils" "github.com/ubuntu/authd/internal/testutils/golden" "github.com/ubuntu/authd/internal/users/localentries" localentriestestutils "github.com/ubuntu/authd/internal/users/localentries/testutils" userslocking "github.com/ubuntu/authd/internal/users/locking" + "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/authd/log" ) @@ -130,6 +134,221 @@ func TestUpdatelocalentries(t *testing.T) { } } +func TestGetAndSaveLocalGroups(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + groupFilePath string + addGroups []types.GroupEntry + removeGroups []string + addUsers map[string][]string + + wantGetErr bool + wantSetErr bool + wantNoOp bool + }{ + "Empty_group_is_kept_empty_on_no_op": { + groupFilePath: "empty.group", + }, + "Empty_group_adding_one_group": { + groupFilePath: "empty.group", + addGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1}, + }, + }, + "Empty_group_adding_one_group_with_one_user": { + groupFilePath: "empty.group", + addGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1}, + }, + addUsers: map[string][]string{"group1": {"user1"}}, + }, + "Empty_group_adding_two_groups_with_two_users": { + groupFilePath: "empty.group", + addGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1}, + {Name: "group2", Passwd: "x", GID: 2}, + }, + addUsers: map[string][]string{ + "group1": {"user1.1", "user1.2"}, + "group2": {"user2.2", "user2.2"}, + }, + }, + "Empty_group_adding_one_group_and_removing_it_afterwards": { + groupFilePath: "empty.group", + addGroups: []types.GroupEntry{{Name: "group1", GID: 1}}, + removeGroups: []string{"group1"}, + wantNoOp: true, + }, + "Insert_new_user_in_existing_files_with_no_users_in_our_group": { + groupFilePath: "no_users_in_our_groups.group", + addUsers: map[string][]string{ + "localgroup1": {"user1.1", "user1.2"}, + "localgroup2": {"user2.2", "user2.2"}, + }, + }, + "Insert_new_user_when_no_users_in_any_group": { + groupFilePath: "no_users.group", + addUsers: map[string][]string{ + "localgroup1": {"user1.1", "user1.2"}, + "localgroup2": {"user2.2", "user2.2"}, + }, + }, + "Insert_new_user_in_existing_files_with_other_users_in_our_group": { + groupFilePath: "users_in_our_groups.group", + addUsers: map[string][]string{ + "localgroup1": {"user1.1", "user1.2"}, + "localgroup2": {"user2.2", "user2.2"}, + }, + }, + "Insert_new_user_in_existing_files_with_multiple_other_users_in_our_group": { + groupFilePath: "multiple_users_in_our_groups.group", + addUsers: map[string][]string{ + "localgroup1": {"user1.1", "user1.2"}, + "localgroup2": {"user2.2", "user2.2"}, + }, + }, + "Ignores_adding_duplicated_equal_groups": { + groupFilePath: "empty.group", + addGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 12345, Users: []string{"user1", "user2"}}, + {Name: "group1", Passwd: "x", GID: 12345, Users: []string{"user1", "user2"}}, + }, + }, + "Ignores_adding_duplicated_equal_group_to_existing_file": { + groupFilePath: "no_users_in_our_groups.group", + addGroups: []types.GroupEntry{ + {Name: "localgroup3", Passwd: "x", GID: 43}, + }, + }, + "Removes_group_correctly": { + groupFilePath: "multiple_users_in_our_groups.group", + removeGroups: []string{"localgroup1", "localgroup4"}, + }, + + // Error cases + "Error_on_missing_groups_file": { + groupFilePath: "does_not_exists.group", + wantGetErr: true, + }, + "Error_when_groups_file_has_missing_fields": { + groupFilePath: "malformed_file_missing_field.group", + wantGetErr: true, + }, + "Error_when_groups_file_has_invalid_gid": { + groupFilePath: "malformed_file_invalid_gid.group", + wantGetErr: true, + }, + "Error_when_groups_file_has_no_group_name": { + groupFilePath: "malformed_file_no_group_name.group", + wantGetErr: true, + }, + "Error_when_groups_file_has_a_duplicated_group": { + groupFilePath: "malformed_file_duplicated.group", + wantGetErr: true, + }, + "Error_adding_duplicated_groups": { + groupFilePath: "empty.group", + addGroups: []types.GroupEntry{ + {Name: "group1", GID: 12345}, + {Name: "group1", GID: 12345, Users: []string{"user1"}}, + }, + wantSetErr: true, + }, + "Error_adding_duplicated_group_to_existing_file": { + groupFilePath: "no_users_in_our_groups.group", + addGroups: []types.GroupEntry{{Name: "localgroup3", GID: 12345}}, + wantSetErr: true, + }, + "Error_adding_duplicated_groups_GIDs": { + groupFilePath: "empty.group", + addGroups: []types.GroupEntry{ + {Name: "group1", GID: 43}, + {Name: "group2", GID: 43, Users: []string{"user1"}}, + }, + wantSetErr: true, + }, + "Error_adding_duplicated_group_GID_to_existing_file": { + groupFilePath: "no_users_in_our_groups.group", + addGroups: []types.GroupEntry{{Name: "test-group3", GID: 43}}, + wantSetErr: true, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + inputGroupFilePath := filepath.Join(testutils.CurrentDir(), + "testdata", tc.groupFilePath) + outputGroupFilePath := filepath.Join(t.TempDir(), "group") + + if !tc.wantNoOp && tc.addGroups == nil && tc.removeGroups == nil && tc.addUsers == nil { + tc.wantNoOp = true + } + + if exists, _ := fileutils.FileExists(inputGroupFilePath); exists { + tempGroupFile := filepath.Join(t.TempDir(), "group") + err := os.Symlink(inputGroupFilePath, tempGroupFile) + require.NoError(t, err, "failed to symlink group file for testing") + inputGroupFilePath = tempGroupFile + } + + defer localentriestestutils.RequireGroupFile(t, outputGroupFilePath, golden.Path(t)) + + lg, cleanup, err := localentries.GetGroupsWithLock( + localentries.WithGroupInputPath(inputGroupFilePath), + localentries.WithGroupOutputPath(outputGroupFilePath), + ) + if tc.wantGetErr { + require.Error(t, err, "Locking should have failed, but it did not") + return + } + + require.NoError(t, err, "Setup: failed to lock the users group") + defer func() { require.NoError(t, cleanup(), "Releasing unlocked groups") }() + + groups := lg.GetEntries() + initialGroups := slices.Clone(groups) + + groups = append(groups, tc.addGroups...) + groups = slices.DeleteFunc(groups, func(g types.GroupEntry) bool { + return slices.Contains(tc.removeGroups, g.Name) + }) + + for groupName, userNames := range tc.addUsers { + idx := slices.IndexFunc(groups, func(g types.GroupEntry) bool { return g.Name == groupName }) + require.GreaterOrEqual(t, idx, 0, "Setup: %q is not in groups %v", groupName, groups) + groups[idx].Users = append(groups[idx].Users, userNames...) + } + + err = lg.SaveEntries(groups) + if tc.wantSetErr { + require.Error(t, err, "SaveEntries should have failed") + updatedGroups := lg.GetEntries() + require.Equal(t, initialGroups, updatedGroups, "Cached groups have been changed") + return + } + + if len(groups) == 0 { + groups = nil + } + + require.NoError(t, err, "SaveEntries should not have failed") + // Ensure we also saved the cached version of the groups... + updatedGroups := lg.GetEntries() + require.Equal(t, groups, updatedGroups, "Cached groups are not saved") + + if tc.wantNoOp { + // The output group file should not have been created + require.NoFileExists(t, outputGroupFilePath, "Output group file should not have been created") + return + } + + require.FileExists(t, outputGroupFilePath, "Output file should have been created") + }) + } +} + //nolint:tparallel // This can't be parallel, but subtests can. func TestRacingLockingActions(t *testing.T) { const nIterations = 50 @@ -147,17 +366,20 @@ func TestRacingLockingActions(t *testing.T) { t.Cleanup(wg.Done) var opts []localentries.Option + wantGroup := types.GroupEntry{Name: "root", GID: 0, Passwd: "x"} useTestGroupFile := idx%3 == 0 if useTestGroupFile { // Mix the requests with test-only code paths... opts = append(opts, localentries.WithGroupPath(testFilePath)) + wantGroup = types.GroupEntry{Name: "localgroup1", GID: 41, Passwd: "x"} } lg, unlock, err := localentries.GetGroupsWithLock(opts...) require.NoError(t, err, "Failed to lock the users group (test groups: %v)", useTestGroupFile) - err = lg.Update("", nil, nil) - require.NoError(t, err, "Update should not fail (test groups: %v)", useTestGroupFile) + groups := lg.GetEntries() + require.NotEmpty(t, groups, "Got empty groups (test groups: %v)", useTestGroupFile) + require.Contains(t, groups, wantGroup, "Expected group was not found (test groups: %v)", useTestGroupFile) err = unlock() require.NoError(t, err, "Unlock should not fail to lock the users group (test groups: %v)", useTestGroupFile) }) @@ -174,7 +396,7 @@ func TestRacingLockingActions(t *testing.T) { require.NoError(t, err, "Unlock should not fail to lock the users group") // Ensure that we had cleaned up all the locks correctly! - require.Panics(t, func() { _ = lg.Update("", nil, nil) }) + require.Panics(t, func() { _ = lg.GetEntries() }) }) } @@ -192,6 +414,10 @@ func TestLockedInvalidActions(t *testing.T) { require.Panics(t, func() { _ = lg.Update("", nil, nil) }, "Update should panic but did not") + require.Panics(t, func() { _ = lg.GetEntries() }, + "GetEntries should panic but did not") + require.Panics(t, func() { _ = lg.SaveEntries(nil) }, + "SaveEntries should panic but did not") // This is to ensure that we're in a good state, despite the actions above for range 10 { diff --git a/internal/users/localentries/testdata/empty.group b/internal/users/localentries/testdata/empty.group new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Empty_group_adding_one_group.group b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Empty_group_adding_one_group.group new file mode 100644 index 0000000000..88e03b5ac7 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Empty_group_adding_one_group.group @@ -0,0 +1 @@ +group1:x:1: diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Empty_group_adding_one_group.group.backup b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Empty_group_adding_one_group.group.backup new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Empty_group_adding_one_group_with_one_user.group b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Empty_group_adding_one_group_with_one_user.group new file mode 100644 index 0000000000..92a82f3c60 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Empty_group_adding_one_group_with_one_user.group @@ -0,0 +1 @@ +group1:x:1:user1 diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Empty_group_adding_one_group_with_one_user.group.backup b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Empty_group_adding_one_group_with_one_user.group.backup new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Empty_group_adding_two_groups_with_two_users.group b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Empty_group_adding_two_groups_with_two_users.group new file mode 100644 index 0000000000..4be9c763c6 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Empty_group_adding_two_groups_with_two_users.group @@ -0,0 +1,2 @@ +group1:x:1:user1.1,user1.2 +group2:x:2:user2.2,user2.2 diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Empty_group_adding_two_groups_with_two_users.group.backup b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Empty_group_adding_two_groups_with_two_users.group.backup new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Ignores_adding_duplicated_equal_group_to_existing_file.group b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Ignores_adding_duplicated_equal_group_to_existing_file.group new file mode 100644 index 0000000000..e4d1ad40b8 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Ignores_adding_duplicated_equal_group_to_existing_file.group @@ -0,0 +1,7 @@ +localgroup1:x:41: +localgroup2:x:42:otheruser +localgroup3:x:43: +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 +localgroup3:x:43: diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Ignores_adding_duplicated_equal_group_to_existing_file.group.backup b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Ignores_adding_duplicated_equal_group_to_existing_file.group.backup new file mode 100644 index 0000000000..f298aac70d --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Ignores_adding_duplicated_equal_group_to_existing_file.group.backup @@ -0,0 +1,6 @@ +localgroup1:x:41: +localgroup2:x:42:otheruser +localgroup3:x:43: +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Ignores_adding_duplicated_equal_groups.group b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Ignores_adding_duplicated_equal_groups.group new file mode 100644 index 0000000000..cf642aec25 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Ignores_adding_duplicated_equal_groups.group @@ -0,0 +1,2 @@ +group1:x:12345:user1,user2 +group1:x:12345:user1,user2 diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Ignores_adding_duplicated_equal_groups.group.backup b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Ignores_adding_duplicated_equal_groups.group.backup new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_in_existing_files_with_multiple_other_users_in_our_group.group b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_in_existing_files_with_multiple_other_users_in_our_group.group new file mode 100644 index 0000000000..1547ad060e --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_in_existing_files_with_multiple_other_users_in_our_group.group @@ -0,0 +1,6 @@ +localgroup1:x:41:otheruser,otheruser2,user1.1,user1.2 +localgroup2:x:42:otheruser2,user2.2,user2.2 +localgroup3:x:43:otheruser +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_in_existing_files_with_multiple_other_users_in_our_group.group.backup b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_in_existing_files_with_multiple_other_users_in_our_group.group.backup new file mode 100644 index 0000000000..57701809de --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_in_existing_files_with_multiple_other_users_in_our_group.group.backup @@ -0,0 +1,6 @@ +localgroup1:x:41:otheruser,otheruser2 +localgroup2:x:42:otheruser2 +localgroup3:x:43:otheruser +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_in_existing_files_with_no_users_in_our_group.group b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_in_existing_files_with_no_users_in_our_group.group new file mode 100644 index 0000000000..e8d60b1114 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_in_existing_files_with_no_users_in_our_group.group @@ -0,0 +1,6 @@ +localgroup1:x:41:user1.1,user1.2 +localgroup2:x:42:otheruser,user2.2,user2.2 +localgroup3:x:43: +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_in_existing_files_with_no_users_in_our_group.group.backup b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_in_existing_files_with_no_users_in_our_group.group.backup new file mode 100644 index 0000000000..f298aac70d --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_in_existing_files_with_no_users_in_our_group.group.backup @@ -0,0 +1,6 @@ +localgroup1:x:41: +localgroup2:x:42:otheruser +localgroup3:x:43: +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_in_existing_files_with_other_users_in_our_group.group b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_in_existing_files_with_other_users_in_our_group.group new file mode 100644 index 0000000000..aee6a2be46 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_in_existing_files_with_other_users_in_our_group.group @@ -0,0 +1,6 @@ +localgroup1:x:41:otheruser,user1.1,user1.2 +localgroup2:x:42:otheruser2,user2.2,user2.2 +localgroup3:x:43:otheruser +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_in_existing_files_with_other_users_in_our_group.group.backup b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_in_existing_files_with_other_users_in_our_group.group.backup new file mode 100644 index 0000000000..821230a903 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_in_existing_files_with_other_users_in_our_group.group.backup @@ -0,0 +1,6 @@ +localgroup1:x:41:otheruser +localgroup2:x:42:otheruser2 +localgroup3:x:43:otheruser +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_when_no_users_in_any_group.group b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_when_no_users_in_any_group.group new file mode 100644 index 0000000000..fedc59334a --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_when_no_users_in_any_group.group @@ -0,0 +1,6 @@ +localgroup1:x:41:user1.1,user1.2 +localgroup2:x:42:user2.2,user2.2 +localgroup3:x:43: +localgroup4:x:44: +cloudgroup1:x:9998: +cloudgroup2:x:9999: diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_when_no_users_in_any_group.group.backup b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_when_no_users_in_any_group.group.backup new file mode 100644 index 0000000000..d6667e9e66 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Insert_new_user_when_no_users_in_any_group.group.backup @@ -0,0 +1,6 @@ +localgroup1:x:41: +localgroup2:x:42: +localgroup3:x:43: +localgroup4:x:44: +cloudgroup1:x:9998: +cloudgroup2:x:9999: diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Removes_group_correctly.group b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Removes_group_correctly.group new file mode 100644 index 0000000000..88de603498 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Removes_group_correctly.group @@ -0,0 +1,4 @@ +localgroup2:x:42:otheruser2 +localgroup3:x:43:otheruser +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Removes_group_correctly.group.backup b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Removes_group_correctly.group.backup new file mode 100644 index 0000000000..57701809de --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Removes_group_correctly.group.backup @@ -0,0 +1,6 @@ +localgroup1:x:41:otheruser,otheruser2 +localgroup2:x:42:otheruser2 +localgroup3:x:43:otheruser +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 From c657d35e976bdad268ea6525aad74355b165ade9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 25 Jun 2025 07:00:40 +0200 Subject: [PATCH 0531/1670] users/db/migration: Use locked group entries to migrate the the local groups We can reuse now the locking and writing code of localentries in the migration phase too, thus let's simplify things a bit and reuse the same code everywhere --- cmd/authd/daemon/daemon_test.go | 4 + cmd/authd/daemon/migration_test.go | 7 +- .../group | 2 +- cmd/authd/daemon/testdata/group | 2 +- cmd/authd/integrationtests.go | 2 - internal/users/db/db_test.go | 115 +++++++--------- internal/users/db/export_test.go | 15 --- internal/users/db/migration.go | 124 +++--------------- internal/users/db/testutils.go | 16 --- pam/integration-tests/cli_test.go | 5 + pam/integration-tests/native_test.go | 5 + pam/integration-tests/ssh_test.go | 6 +- ...user_successfully_after_db_migration.group | 6 + ...ccessfully_after_db_migration.group.backup | 6 + ...r_with_mixed_case_after_db_migration.group | 6 + ...mixed_case_after_db_migration.group.backup | 6 + ..._using_lower_case_after_db_migration.group | 6 + ...lower_case_after_db_migration.group.backup | 6 + ...user_successfully_after_db_migration.group | 6 + ...ccessfully_after_db_migration.group.backup | 6 + ...r_with_mixed_case_after_db_migration.group | 6 + ...mixed_case_after_db_migration.group.backup | 6 + ..._using_lower_case_after_db_migration.group | 6 + ...lower_case_after_db_migration.group.backup | 6 + ...user_successfully_after_db_migration.group | 6 + ...ccessfully_after_db_migration.group.backup | 6 + ...ly_after_db_migration_on_shared_SSHd.group | 6 + ...r_db_migration_on_shared_SSHd.group.backup | 6 + ...r_with_mixed_case_after_db_migration.group | 6 + ...mixed_case_after_db_migration.group.backup | 6 + ...se_after_db_migration_on_shared_SSHd.group | 6 + ...r_db_migration_on_shared_SSHd.group.backup | 6 + ..._using_lower_case_after_db_migration.group | 6 + ...lower_case_after_db_migration.group.backup | 6 + ...se_after_db_migration_on_shared_SSHd.group | 6 + ...r_db_migration_on_shared_SSHd.group.backup | 6 + 36 files changed, 225 insertions(+), 222 deletions(-) create mode 100644 pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_successfully_after_db_migration.group create mode 100644 pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_successfully_after_db_migration.group.backup create mode 100644 pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.group create mode 100644 pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.group.backup create mode 100644 pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.group create mode 100644 pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.group.backup create mode 100644 pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_successfully_after_db_migration.group create mode 100644 pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_successfully_after_db_migration.group.backup create mode 100644 pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.group create mode 100644 pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.group.backup create mode 100644 pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.group create mode 100644 pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.group.backup create mode 100644 pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration.group create mode 100644 pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration.group.backup create mode 100644 pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration_on_shared_SSHd.group create mode 100644 pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration_on_shared_SSHd.group.backup create mode 100644 pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.group create mode 100644 pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.group.backup create mode 100644 pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration_on_shared_SSHd.group create mode 100644 pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration_on_shared_SSHd.group.backup create mode 100644 pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.group create mode 100644 pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.group.backup create mode 100644 pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration_on_shared_SSHd.group create mode 100644 pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration_on_shared_SSHd.group.backup diff --git a/cmd/authd/daemon/daemon_test.go b/cmd/authd/daemon/daemon_test.go index cc26310d06..4774757fe5 100644 --- a/cmd/authd/daemon/daemon_test.go +++ b/cmd/authd/daemon/daemon_test.go @@ -17,6 +17,7 @@ import ( "github.com/ubuntu/authd/internal/fileutils" "github.com/ubuntu/authd/internal/testutils" "github.com/ubuntu/authd/internal/users" + userslocking "github.com/ubuntu/authd/internal/users/locking" "github.com/ubuntu/authd/log" ) @@ -410,5 +411,8 @@ func TestMain(m *testing.M) { } defer cleanup() + userslocking.Z_ForTests_OverrideLocking() + defer userslocking.Z_ForTests_RestoreLocking() + m.Run() } diff --git a/cmd/authd/daemon/migration_test.go b/cmd/authd/daemon/migration_test.go index 3dff5d6e21..474b612069 100644 --- a/cmd/authd/daemon/migration_test.go +++ b/cmd/authd/daemon/migration_test.go @@ -11,6 +11,7 @@ import ( "github.com/ubuntu/authd/internal/testutils/golden" "github.com/ubuntu/authd/internal/users/db" "github.com/ubuntu/authd/internal/users/db/bbolt" + localgrouptestutils "github.com/ubuntu/authd/internal/users/localentries/testutils" userslocking "github.com/ubuntu/authd/internal/users/locking" ) @@ -186,10 +187,8 @@ func TestMaybeMigrateBBoltToSQLite(t *testing.T) { err := fileutils.CopyFile(groupFile, tempGroupFile) require.NoError(t, err, "failed to copy group file for testing") - // Make the db package use the temporary group file - origGroupFile := db.Z_ForTests_GetGroupFile() - db.Z_ForTests_SetGroupFile(tempGroupFile) - t.Cleanup(func() { db.Z_ForTests_SetGroupFile(origGroupFile) }) + // Make the localentries package use the test group file. + tempGroupFile = localgrouptestutils.SetupGroupMock(t, tempGroupFile) migrated, err := maybeMigrateBBoltToSQLite(dbDir) if tc.wantError { diff --git a/cmd/authd/daemon/testdata/golden/TestMaybeMigrateBBoltToSQLite/Migration_if_bbolt_exists_and_sqlite_does_not_exist/group b/cmd/authd/daemon/testdata/golden/TestMaybeMigrateBBoltToSQLite/Migration_if_bbolt_exists_and_sqlite_does_not_exist/group index d284122dc2..86d94268b6 100644 --- a/cmd/authd/daemon/testdata/golden/TestMaybeMigrateBBoltToSQLite/Migration_if_bbolt_exists_and_sqlite_does_not_exist/group +++ b/cmd/authd/daemon/testdata/golden/TestMaybeMigrateBBoltToSQLite/Migration_if_bbolt_exists_and_sqlite_does_not_exist/group @@ -2,4 +2,4 @@ root:x:0: other-local-group:x:1234: other-local-group-with-users:x:4321:user-foo,user-bar group1:x:11111:user1 -UpperCaseGroup:x:11111:user1,user3 +UpperCaseGroup:x:11112:user1,user3 diff --git a/cmd/authd/daemon/testdata/group b/cmd/authd/daemon/testdata/group index 121f89c7e0..3659368e2e 100644 --- a/cmd/authd/daemon/testdata/group +++ b/cmd/authd/daemon/testdata/group @@ -2,4 +2,4 @@ root:x:0: other-local-group:x:1234: other-local-group-with-users:x:4321:user-foo,user-bar group1:x:11111:user1 -UpperCaseGroup:x:11111:user1,User3 +UpperCaseGroup:x:11112:user1,User3 diff --git a/cmd/authd/integrationtests.go b/cmd/authd/integrationtests.go index 63edada1d2..4133451d25 100644 --- a/cmd/authd/integrationtests.go +++ b/cmd/authd/integrationtests.go @@ -10,7 +10,6 @@ import ( "github.com/ubuntu/authd/internal/services/permissions" "github.com/ubuntu/authd/internal/testsdetection" - "github.com/ubuntu/authd/internal/users/db" "github.com/ubuntu/authd/internal/users/localentries" userslocking "github.com/ubuntu/authd/internal/users/locking" ) @@ -32,7 +31,6 @@ func init() { grpFileOutputPath = grpFilePath } localentries.Z_ForTests_SetGroupPath(grpFilePath, grpFileOutputPath) - db.Z_ForTests_SetGroupFile(grpFilePath) userslocking.Z_ForTests_OverrideLocking() } diff --git a/internal/users/db/db_test.go b/internal/users/db/db_test.go index bc4b2a5bea..d6061cf5c3 100644 --- a/internal/users/db/db_test.go +++ b/internal/users/db/db_test.go @@ -13,6 +13,8 @@ import ( "github.com/ubuntu/authd/internal/fileutils" "github.com/ubuntu/authd/internal/testutils/golden" "github.com/ubuntu/authd/internal/users/db" + "github.com/ubuntu/authd/internal/users/localentries" + localgrouptestutils "github.com/ubuntu/authd/internal/users/localentries/testutils" userslocking "github.com/ubuntu/authd/internal/users/locking" "github.com/ubuntu/authd/log" ) @@ -129,10 +131,9 @@ LocalTestGroup:x:12345:TestUser `), 0600) require.NoError(t, err, "Setup: could not create group file") - // Make the db package use the temporary group file - origGroupFile := db.GroupFile() - db.SetGroupFile(groupsFilePath) - t.Cleanup(func() { db.SetGroupFile(origGroupFile) }) + // Make the localentries package use the test group file. + t.Cleanup(localentries.Z_ForTests_RestoreDefaultOptions) + localentries.Z_ForTests_SetGroupPath(groupsFilePath, groupsFilePath) // Make the userutils package to use test locking for the group file userslocking.Z_ForTests_OverrideLockingWithCleanup(t) @@ -152,11 +153,6 @@ LocalTestGroup:x:12345:TestUser require.NoError(t, err) golden.CheckOrUpdate(t, string(userGroupContent), golden.WithPath("groups")) - - // Check the content of the backup group file - userGroupBackupContent, err := os.ReadFile(db.GroupFileBackupPath()) - require.NoError(t, err) - golden.CheckOrUpdate(t, string(userGroupBackupContent), golden.WithPath("groups-backup")) } func TestMigrationToLowercaseUserAndGroupNamesEmptyDB(t *testing.T) { @@ -171,10 +167,8 @@ func TestMigrationToLowercaseUserAndGroupNamesEmptyDB(t *testing.T) { err = fileutils.Touch(groupsFilePath) require.NoError(t, err, "Setup: could not create group file") - // Make the db package use the temporary group file - origGroupFile := db.GroupFile() - db.SetGroupFile(groupsFilePath) - t.Cleanup(func() { db.SetGroupFile(origGroupFile) }) + // Make the localentries package use the test group file. + groupsFilePath = localgrouptestutils.SetupGroupMock(t, groupsFilePath) // Make the userutils package to use test locking for the group file userslocking.Z_ForTests_OverrideLockingWithCleanup(t) @@ -189,15 +183,7 @@ func TestMigrationToLowercaseUserAndGroupNamesEmptyDB(t *testing.T) { golden.CheckOrUpdate(t, dbContent, golden.WithPath("db")) - // Check the content of the user group file - userGroupContent, err := os.ReadFile(groupsFilePath) - require.NoError(t, err) - - golden.CheckOrUpdate(t, string(userGroupContent), golden.WithPath("groups")) - - // Check the content of the backup group file - _, err = os.Stat(db.GroupFileBackupPath()) - require.ErrorIs(t, err, os.ErrNotExist, "No backup should exist") + require.NoFileExists(t, groupsFilePath, "No new group file should have been created") } func TestMigrationToLowercaseUserAndGroupNamesAlreadyUpdated(t *testing.T) { @@ -212,10 +198,8 @@ func TestMigrationToLowercaseUserAndGroupNamesAlreadyUpdated(t *testing.T) { err = os.WriteFile(groupsFilePath, []byte("LocalTestGroup:x:12345:testuser\n"), 0600) require.NoError(t, err, "Setup: could not create group file") - // Make the db package use the temporary group file - origGroupFile := db.GroupFile() - db.SetGroupFile(groupsFilePath) - t.Cleanup(func() { db.SetGroupFile(origGroupFile) }) + // Make the localentries package use the test group file. + groupsFilePath = localgrouptestutils.SetupGroupMock(t, groupsFilePath) // Make the userutils package to use test locking for the group file userslocking.Z_ForTests_OverrideLockingWithCleanup(t) @@ -230,15 +214,7 @@ func TestMigrationToLowercaseUserAndGroupNamesAlreadyUpdated(t *testing.T) { golden.CheckOrUpdate(t, dbContent, golden.WithPath("db")) - // Check the content of the user group file - userGroupContent, err := os.ReadFile(groupsFilePath) - require.NoError(t, err) - - golden.CheckOrUpdate(t, string(userGroupContent), golden.WithPath("groups")) - - // Check the content of the backup group file - _, err = os.Stat(db.GroupFileBackupPath()) - require.ErrorIs(t, err, os.ErrNotExist, "No backup should exist") + require.NoFileExists(t, groupsFilePath, "No new group file should have been created") } func TestMigrationToLowercaseUserAndGroupNamesWithSymlinkedGroupFile(t *testing.T) { @@ -257,10 +233,9 @@ func TestMigrationToLowercaseUserAndGroupNamesWithSymlinkedGroupFile(t *testing. err = os.Symlink(realGroupsPath, groupsFilePath) require.NoError(t, err, "Setup: could not symlink group file") - // Make the db package use the temporary group file - origGroupFile := db.GroupFile() - db.SetGroupFile(groupsFilePath) - t.Cleanup(func() { db.SetGroupFile(origGroupFile) }) + // Make the localentries package use the test group file. + t.Cleanup(localentries.Z_ForTests_RestoreDefaultOptions) + localentries.Z_ForTests_SetGroupPath(groupsFilePath, groupsFilePath) // Make the userutils package to use test locking for the group file userslocking.Z_ForTests_OverrideLockingWithCleanup(t) @@ -287,7 +262,8 @@ func TestMigrationToLowercaseUserAndGroupNamesWithSymlinkedGroupFile(t *testing. golden.CheckOrUpdate(t, string(userGroupContent), golden.WithPath("groups")) // Check the content of the backup group file - usersGroupBackupContent, err := os.ReadFile(db.GroupFileBackupPath()) + backupFilePath := groupsFilePath + "-" + usersGroupBackupContent, err := os.ReadFile(backupFilePath) require.NoError(t, err) golden.CheckOrUpdate(t, string(usersGroupBackupContent), golden.WithPath("groups-backup")) } @@ -305,13 +281,13 @@ func TestMigrationToLowercaseUserAndGroupNamesWithPreviousBackup(t *testing.T) { err = os.WriteFile(groupsFilePath, originalGroupFileContents, 0600) require.NoError(t, err, "Setup: could not create group file") - // Make the db package use the temporary group file - origGroupFile := db.GroupFile() - db.SetGroupFile(groupsFilePath) - t.Cleanup(func() { db.SetGroupFile(origGroupFile) }) + // Make the localentries package use the test group file. + t.Cleanup(localentries.Z_ForTests_RestoreDefaultOptions) + localentries.Z_ForTests_SetGroupPath(groupsFilePath, groupsFilePath) + backupFilePath := groupsFilePath + "-" // Create a temporary user group file backup for testing - err = os.WriteFile(db.GroupFileBackupPath(), []byte("LocalTestGroup:x:12345:TestUserBackup\n"), 0600) + err = os.WriteFile(backupFilePath, []byte("LocalTestGroup:x:12345:TestUserBackup\n"), 0600) require.NoError(t, err, "Setup: could not create group file") // Make the userutils package to use test locking for the group file @@ -328,13 +304,13 @@ func TestMigrationToLowercaseUserAndGroupNamesWithPreviousBackup(t *testing.T) { golden.CheckOrUpdate(t, dbContent, golden.WithPath("db")) // Check the content of the user group file - userGroupContent, err := os.ReadFile(db.GroupFile()) + userGroupContent, err := os.ReadFile(groupsFilePath) require.NoError(t, err) golden.CheckOrUpdate(t, string(userGroupContent), golden.WithPath("groups")) // Check the content of the backup group file - userGroupBackupContent, err := os.ReadFile(db.GroupFileBackupPath()) + userGroupBackupContent, err := os.ReadFile(backupFilePath) require.NoError(t, err) golden.CheckOrUpdate(t, string(userGroupBackupContent), golden.WithPath("groups-backup")) } @@ -352,10 +328,9 @@ func TestMigrationToLowercaseUserAndGroupNamesWithSymlinkedPreviousBackup(t *tes err = os.WriteFile(groupsFilePath, originalGroupFileContents, 0600) require.NoError(t, err, "Setup: could not create group file") - // Make the db package use the temporary group file - origGroupFile := db.GroupFile() - db.SetGroupFile(groupsFilePath) - t.Cleanup(func() { db.SetGroupFile(origGroupFile) }) + // Make the localentries package use the test group file. + t.Cleanup(localentries.Z_ForTests_RestoreDefaultOptions) + localentries.Z_ForTests_SetGroupPath(groupsFilePath, groupsFilePath) // Create a temporary user group file backup for testing realGroupsBackup := filepath.Join(t.TempDir(), "groups-backup") @@ -363,7 +338,8 @@ func TestMigrationToLowercaseUserAndGroupNamesWithSymlinkedPreviousBackup(t *tes require.NoError(t, err, "Setup: could not create group file") // Symlink it to the backup path - err = os.Symlink(realGroupsBackup, db.GroupFileBackupPath()) + backupFilePath := groupsFilePath + "-" + err = os.Symlink(realGroupsBackup, backupFilePath) require.NoError(t, err, "Setup: could not create group file backup symlink") // Make the userutils package to use test locking for the group file @@ -380,18 +356,18 @@ func TestMigrationToLowercaseUserAndGroupNamesWithSymlinkedPreviousBackup(t *tes golden.CheckOrUpdate(t, dbContent, golden.WithPath("db")) // Check the content of the user group file - userGroupContent, err := os.ReadFile(db.GroupFile()) + userGroupContent, err := os.ReadFile(groupsFilePath) require.NoError(t, err) golden.CheckOrUpdate(t, string(userGroupContent), golden.WithPath("groups")) // Ensure the backup is not anymore a symlink - fi, err := os.Lstat(db.GroupFileBackupPath()) + fi, err := os.Lstat(backupFilePath) require.NoError(t, err) require.Zero(t, fi.Mode()&os.ModeSymlink, "Group file backup must not be a symlink") // Check the content of the backup group file - userGroupBackupContent, err := os.ReadFile(db.GroupFileBackupPath()) + userGroupBackupContent, err := os.ReadFile(backupFilePath) require.NoError(t, err) golden.CheckOrUpdate(t, string(userGroupBackupContent), golden.WithPath("groups-backup")) } @@ -412,10 +388,9 @@ func TestMigrationToLowercaseUserAndGroupNamesFails(t *testing.T) { err = os.Chmod(groupsFilePath, 0000) require.NoError(t, err, "Setup: setting chmod to %q", groupsFilePath) - // Make the db package use the temporary group file - origGroupFile := db.GroupFile() - db.SetGroupFile(groupsFilePath) - t.Cleanup(func() { db.SetGroupFile(origGroupFile) }) + // Make the localentries package use the test group file. + t.Cleanup(localentries.Z_ForTests_RestoreDefaultOptions) + localentries.Z_ForTests_SetGroupPath(groupsFilePath, groupsFilePath) // Make the userutils package to use test locking for the group file userslocking.Z_ForTests_OverrideLockingWithCleanup(t) @@ -425,11 +400,11 @@ func TestMigrationToLowercaseUserAndGroupNamesFails(t *testing.T) { require.Error(t, err, "Updating db fails") require.Nil(t, m, "Db should be unset") - err = os.Chmod(db.GroupFile(), 0600) - require.NoError(t, err, "Setup: setting chmod to %q", db.GroupFile()) + err = os.Chmod(groupsFilePath, 0600) + require.NoError(t, err, "Setup: setting chmod to %q", groupsFilePath) // Check the content of the user group file - userGroupContent, err := os.ReadFile(db.GroupFile()) + userGroupContent, err := os.ReadFile(groupsFilePath) require.NoError(t, err) golden.CheckOrUpdate(t, string(userGroupContent), golden.WithPath("groups")) @@ -447,18 +422,18 @@ func TestMigrationToLowercaseUserAndGroupNamesWithBackupFailure(t *testing.T) { err = os.WriteFile(groupsFilePath, []byte("LocalTestGroup:x:12345:TestUser\n"), 0600) require.NoError(t, err, "Setup: could not create group file") - // Make the db package use the temporary group file - origGroupFile := db.GroupFile() - db.SetGroupFile(groupsFilePath) - t.Cleanup(func() { db.SetGroupFile(origGroupFile) }) + // Make the localentries package use the test group file. + t.Cleanup(localentries.Z_ForTests_RestoreDefaultOptions) + localentries.Z_ForTests_SetGroupPath(groupsFilePath, groupsFilePath) // To trigger all the errors we should handle, we create a non-empty // directory as backup file. So that we cannot copy or remove it. - err = os.Mkdir(db.GroupFileBackupPath(), 0700) - require.NoError(t, err, "Setup: creating directory %q", db.GroupFileBackupPath()) + backupFilePath := groupsFilePath + "-" + err = os.Mkdir(backupFilePath, 0700) + require.NoError(t, err, "Setup: creating directory %q", backupFilePath) - err = fileutils.Touch(filepath.Join(db.GroupFileBackupPath(), "a-file")) - require.NoError(t, err, "Setup: touching a file in %q", db.GroupFileBackupPath()) + err = fileutils.Touch(filepath.Join(backupFilePath, "a-file")) + require.NoError(t, err, "Setup: touching a file in %q", backupFilePath) // Make the userutils package to use test locking for the group file userslocking.Z_ForTests_OverrideLockingWithCleanup(t) diff --git a/internal/users/db/export_test.go b/internal/users/db/export_test.go index 6c0350243b..932ad0928f 100644 --- a/internal/users/db/export_test.go +++ b/internal/users/db/export_test.go @@ -5,21 +5,6 @@ func (m *Manager) Path() string { return m.path } -// GroupFile exposes the path to the group file for testing. -func GroupFile() string { - return groupFile -} - -// GroupFileBackupPath exposes the path to the group file backup for testing. -func GroupFileBackupPath() string { - return groupFileBackupPath() -} - -// SetGroupFile sets the path to the group file for testing. -func SetGroupFile(path string) { - groupFile = path -} - // GetCreateSchemaQuery exposes the query to create the schema for testing. func GetCreateSchemaQuery() string { return createSchemaQuery diff --git a/internal/users/db/migration.go b/internal/users/db/migration.go index 6e41ac2a1f..af3d8af73a 100644 --- a/internal/users/db/migration.go +++ b/internal/users/db/migration.go @@ -4,21 +4,14 @@ import ( "context" "errors" "fmt" - "io/fs" - "os" - "path/filepath" - "slices" "strings" - "github.com/ubuntu/authd/internal/fileutils" "github.com/ubuntu/authd/internal/users/db/bbolt" "github.com/ubuntu/authd/internal/users/localentries" - userslocking "github.com/ubuntu/authd/internal/users/locking" "github.com/ubuntu/authd/log" + "github.com/ubuntu/decorate" ) -var groupFile = localentries.GroupFile - // MigrateFromBBoltToSQLite migrates data from bbolt to SQLite. func MigrateFromBBoltToSQLite(dbDir string) error { m, err := New(dbDir) @@ -191,7 +184,7 @@ var schemaMigrations = []schemaMigration{ if err := renameUsersInGroupFile(oldNames, newNames); err != nil { return fmt.Errorf("failed to rename users in %s file: %w", - groupFile, err) + localentries.GroupFile, err) } // Delete groups that would cause unique constraint violations @@ -242,17 +235,12 @@ func (m *Manager) maybeApplyMigrations() error { return nil } -func groupFileTemporaryPath() string { - return fmt.Sprintf("%s+", groupFile) -} - -func groupFileBackupPath() string { - return fmt.Sprintf("%s-", groupFile) -} - // renameUsersInGroupFile renames users in the /etc/group file. -func renameUsersInGroupFile(oldNames, newNames []string) error { - log.Debugf(context.Background(), "Renaming users in %q: %v -> %v", groupFile, +func renameUsersInGroupFile(oldNames, newNames []string) (err error) { + decorate.OnError(&err, "failed to rename users in local groups: %v -> %v", + oldNames, newNames) + + log.Debugf(context.Background(), "Renaming users in local groups: %v -> %v", oldNames, newNames) if len(oldNames) == 0 && len(newNames) == 0 { @@ -260,106 +248,24 @@ func renameUsersInGroupFile(oldNames, newNames []string) error { return nil } - // Note that we can't use gpasswd here because `gpasswd --add` checks for the existence of the user, which causes an - // NSS request to be sent to authd, but authd is not ready yet because we are still migrating the database. - err := userslocking.WriteRecLock() - if err != nil { - return fmt.Errorf("failed to lock group file: %w", err) - } - defer func() { - if err := userslocking.WriteRecUnlock(); err != nil { - log.Warningf(context.Background(), "Failed to unlock group file: %v", err) - } - }() - - content, err := os.ReadFile(groupFile) + lockedGroups, unlock, err := localentries.GetGroupsWithLock() if err != nil { - return fmt.Errorf("error reading %s: %w", groupFile, err) + return err } + defer func() { err = errors.Join(err, unlock()) }() - oldLines := strings.Split(string(content), "\n") - var newLines []string - - for _, line := range oldLines { - if line == "" { - continue - } - - fields := strings.SplitN(line, ":", 4) - if len(fields) != 4 { - return fmt.Errorf("unexpected number of fields in %s line (expected 4, got %d): %s", - groupFile, len(fields), line) - } - - users := strings.Split(fields[3], ",") - for j, user := range users { + groups := lockedGroups.GetEntries() + for idx, group := range groups { + for j, user := range group.Users { for k, oldName := range oldNames { if user == oldName { - users[j] = newNames[k] + groups[idx].Users[j] = newNames[k] } } } - - fields[3] = strings.Join(users, ",") - newLines = append(newLines, strings.Join(fields, ":")) } - // Add final new line to the group file. - newLines = append(newLines, "") - - if slices.Compare(oldLines, newLines) == 0 { - return nil - } - - backupPath := groupFileBackupPath() - oldBackup := "" - - if tmpDir, err := os.MkdirTemp(os.TempDir(), "authd-migration-backup"); err == nil { - defer os.Remove(tmpDir) - - b := filepath.Join(tmpDir, filepath.Base(backupPath)) - err := fileutils.CopyFile(backupPath, b) - if err == nil { - oldBackup = b - defer os.Remove(oldBackup) - } - if err != nil && !errors.Is(err, os.ErrNotExist) { - log.Warningf(context.Background(), "Failed to create backup of %q: %v", - backupPath, err) - } - } - - if err := os.Remove(backupPath); err != nil && !errors.Is(err, os.ErrNotExist) { - log.Warningf(context.Background(), "Failed to remove group file backup: %v", err) - } - - backupAction := os.Rename - if fi, _ := os.Lstat(groupFile); fi != nil && fi.Mode()&fs.ModeSymlink != 0 { - backupAction = fileutils.CopyFile - } - if err := backupAction(groupFile, backupPath); err != nil { - log.Warningf(context.Background(), "Failed make a backup for the group file: %v", err) - - if oldBackup != "" { - // Backup of current group file failed, let's restore the old backup. - if err := fileutils.Lrename(oldBackup, backupPath); err != nil { - log.Warningf(context.Background(), "Failed restoring %q to %q: %v", - oldBackup, backupPath, err) - } - } - } - - tempPath := groupFileTemporaryPath() - //nolint:gosec // G306 /etc/group should indeed have 0644 permissions - if err := os.WriteFile(tempPath, []byte(strings.Join(newLines, "\n")), 0644); err != nil { - return fmt.Errorf("error writing %s: %w", tempPath, err) - } - - if err := fileutils.Lrename(tempPath, groupFile); err != nil { - return fmt.Errorf("error renaming %s to %s: %w", tempPath, groupFile, err) - } - - return nil + return lockedGroups.SaveEntries(groups) } func removeGroupsWithNameConflicts(db queryable) error { diff --git a/internal/users/db/testutils.go b/internal/users/db/testutils.go index 46af9e508c..73499182a8 100644 --- a/internal/users/db/testutils.go +++ b/internal/users/db/testutils.go @@ -200,19 +200,3 @@ func createDBFromYAMLReader(r io.Reader, destDir string) (err error) { log.Debug(context.Background(), "Database created") return nil } - -// Z_ForTests_GetGroupFile returns the path to the group file. -// -// nolint:revive,nolintlint // We want to use underscores in the function name here. -func Z_ForTests_GetGroupFile() string { - testsdetection.MustBeTesting() - - return groupFile -} - -// Z_ForTests_SetGroupFile sets the group file to the provided path. -// -// nolint:revive,nolintlint // We want to use underscores in the function name here. -func Z_ForTests_SetGroupFile(groupFilePath string) { - groupFile = groupFilePath -} diff --git a/pam/integration-tests/cli_test.go b/pam/integration-tests/cli_test.go index fc12537937..7f5c10f321 100644 --- a/pam/integration-tests/cli_test.go +++ b/pam/integration-tests/cli_test.go @@ -253,6 +253,11 @@ func TestCLIAuthenticate(t *testing.T) { var groupFile string groupFileOutput, groupFile = prepareGroupFiles(t) + if tc.wantLocalGroups || tc.oldDB != "" { + // We don't want to use separate input ant output files here. + groupFileOutput = groupFile + } + pidFile = filepath.Join(outDir, "authd.pid") socketPath = runAuthd(t, !tc.currentUserNotRoot, diff --git a/pam/integration-tests/native_test.go b/pam/integration-tests/native_test.go index 66ef865981..a0b42953a0 100644 --- a/pam/integration-tests/native_test.go +++ b/pam/integration-tests/native_test.go @@ -412,6 +412,11 @@ func TestNativeAuthenticate(t *testing.T) { var groupFile string groupFileOutput, groupFile = prepareGroupFiles(t) + if tc.wantLocalGroups || tc.oldDB != "" { + // We don't want to use separate input ant output files here. + groupFileOutput = groupFile + } + pidFile = filepath.Join(outDir, "authd.pid") socketPath = runAuthd(t, !tc.currentUserNotRoot, diff --git a/pam/integration-tests/ssh_test.go b/pam/integration-tests/ssh_test.go index f112893dff..90d99bd922 100644 --- a/pam/integration-tests/ssh_test.go +++ b/pam/integration-tests/ssh_test.go @@ -379,14 +379,12 @@ Wait@%dms`, sshDefaultFinalWaitTimeout), // For the local groups tests we need to run authd again so that it has // special environment that saves the updated group file to a writable // location for us to test. - var groupsFile string - groupOutput, groupsFile = prepareGroupFiles(t) + _, groupOutput = prepareGroupFiles(t) authdEnv = append(authdEnv, useOldDatabaseEnv(t, tc.oldDB)...) socketPath = runAuthd(t, true, - testutils.WithGroupFile(groupsFile), - testutils.WithGroupFileOutput(groupOutput), + testutils.WithGroupFile(groupOutput), testutils.WithEnvironment(authdEnv...)) } else if !sharedSSHd { socketPath, groupOutput = sharedAuthd(t, diff --git a/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_successfully_after_db_migration.group b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_successfully_after_db_migration.group new file mode 100644 index 0000000000..f31a69be33 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_successfully_after_db_migration.group @@ -0,0 +1,6 @@ +localgroup:x:41:user-integration-with-mixed-case,user-integration-cached,user-integration-pre-check-with-upper-case,user-integration-upper-case,user-pre-check +some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-with-mixed-case +some-group-for-user-integration-cached:x:102:user-integration-cached +some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-with-upper-case +some-group-for-user-integration-UPPER-CASE:x:104:user-integration-upper-case +some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_successfully_after_db_migration.group.backup b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_successfully_after_db_migration.group.backup new file mode 100644 index 0000000000..58742bc372 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_successfully_after_db_migration.group.backup @@ -0,0 +1,6 @@ +localgroup:x:41:user-integration-WITH-Mixed-CaSe,user-integration-cached,user-integration-pre-check-WITH-UPPER-CASE,user-integration-UPPER-CASE,user-pre-check +some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-WITH-Mixed-CaSe +some-group-for-user-integration-cached:x:102:user-integration-cached +some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-WITH-UPPER-CASE +some-group-for-user-integration-UPPER-CASE:x:104:user-integration-UPPER-CASE +some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.group b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.group new file mode 100644 index 0000000000..f31a69be33 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.group @@ -0,0 +1,6 @@ +localgroup:x:41:user-integration-with-mixed-case,user-integration-cached,user-integration-pre-check-with-upper-case,user-integration-upper-case,user-pre-check +some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-with-mixed-case +some-group-for-user-integration-cached:x:102:user-integration-cached +some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-with-upper-case +some-group-for-user-integration-UPPER-CASE:x:104:user-integration-upper-case +some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.group.backup b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.group.backup new file mode 100644 index 0000000000..58742bc372 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.group.backup @@ -0,0 +1,6 @@ +localgroup:x:41:user-integration-WITH-Mixed-CaSe,user-integration-cached,user-integration-pre-check-WITH-UPPER-CASE,user-integration-UPPER-CASE,user-pre-check +some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-WITH-Mixed-CaSe +some-group-for-user-integration-cached:x:102:user-integration-cached +some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-WITH-UPPER-CASE +some-group-for-user-integration-UPPER-CASE:x:104:user-integration-UPPER-CASE +some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.group b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.group new file mode 100644 index 0000000000..f31a69be33 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.group @@ -0,0 +1,6 @@ +localgroup:x:41:user-integration-with-mixed-case,user-integration-cached,user-integration-pre-check-with-upper-case,user-integration-upper-case,user-pre-check +some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-with-mixed-case +some-group-for-user-integration-cached:x:102:user-integration-cached +some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-with-upper-case +some-group-for-user-integration-UPPER-CASE:x:104:user-integration-upper-case +some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.group.backup b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.group.backup new file mode 100644 index 0000000000..58742bc372 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestCLIAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.group.backup @@ -0,0 +1,6 @@ +localgroup:x:41:user-integration-WITH-Mixed-CaSe,user-integration-cached,user-integration-pre-check-WITH-UPPER-CASE,user-integration-UPPER-CASE,user-pre-check +some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-WITH-Mixed-CaSe +some-group-for-user-integration-cached:x:102:user-integration-cached +some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-WITH-UPPER-CASE +some-group-for-user-integration-UPPER-CASE:x:104:user-integration-UPPER-CASE +some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_successfully_after_db_migration.group b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_successfully_after_db_migration.group new file mode 100644 index 0000000000..f31a69be33 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_successfully_after_db_migration.group @@ -0,0 +1,6 @@ +localgroup:x:41:user-integration-with-mixed-case,user-integration-cached,user-integration-pre-check-with-upper-case,user-integration-upper-case,user-pre-check +some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-with-mixed-case +some-group-for-user-integration-cached:x:102:user-integration-cached +some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-with-upper-case +some-group-for-user-integration-UPPER-CASE:x:104:user-integration-upper-case +some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_successfully_after_db_migration.group.backup b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_successfully_after_db_migration.group.backup new file mode 100644 index 0000000000..58742bc372 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_successfully_after_db_migration.group.backup @@ -0,0 +1,6 @@ +localgroup:x:41:user-integration-WITH-Mixed-CaSe,user-integration-cached,user-integration-pre-check-WITH-UPPER-CASE,user-integration-UPPER-CASE,user-pre-check +some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-WITH-Mixed-CaSe +some-group-for-user-integration-cached:x:102:user-integration-cached +some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-WITH-UPPER-CASE +some-group-for-user-integration-UPPER-CASE:x:104:user-integration-UPPER-CASE +some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.group b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.group new file mode 100644 index 0000000000..f31a69be33 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.group @@ -0,0 +1,6 @@ +localgroup:x:41:user-integration-with-mixed-case,user-integration-cached,user-integration-pre-check-with-upper-case,user-integration-upper-case,user-pre-check +some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-with-mixed-case +some-group-for-user-integration-cached:x:102:user-integration-cached +some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-with-upper-case +some-group-for-user-integration-UPPER-CASE:x:104:user-integration-upper-case +some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.group.backup b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.group.backup new file mode 100644 index 0000000000..58742bc372 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.group.backup @@ -0,0 +1,6 @@ +localgroup:x:41:user-integration-WITH-Mixed-CaSe,user-integration-cached,user-integration-pre-check-WITH-UPPER-CASE,user-integration-UPPER-CASE,user-pre-check +some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-WITH-Mixed-CaSe +some-group-for-user-integration-cached:x:102:user-integration-cached +some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-WITH-UPPER-CASE +some-group-for-user-integration-UPPER-CASE:x:104:user-integration-UPPER-CASE +some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.group b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.group new file mode 100644 index 0000000000..f31a69be33 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.group @@ -0,0 +1,6 @@ +localgroup:x:41:user-integration-with-mixed-case,user-integration-cached,user-integration-pre-check-with-upper-case,user-integration-upper-case,user-pre-check +some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-with-mixed-case +some-group-for-user-integration-cached:x:102:user-integration-cached +some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-with-upper-case +some-group-for-user-integration-UPPER-CASE:x:104:user-integration-upper-case +some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.group.backup b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.group.backup new file mode 100644 index 0000000000..58742bc372 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestNativeAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.group.backup @@ -0,0 +1,6 @@ +localgroup:x:41:user-integration-WITH-Mixed-CaSe,user-integration-cached,user-integration-pre-check-WITH-UPPER-CASE,user-integration-UPPER-CASE,user-pre-check +some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-WITH-Mixed-CaSe +some-group-for-user-integration-cached:x:102:user-integration-cached +some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-WITH-UPPER-CASE +some-group-for-user-integration-UPPER-CASE:x:104:user-integration-UPPER-CASE +some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration.group b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration.group new file mode 100644 index 0000000000..f31a69be33 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration.group @@ -0,0 +1,6 @@ +localgroup:x:41:user-integration-with-mixed-case,user-integration-cached,user-integration-pre-check-with-upper-case,user-integration-upper-case,user-pre-check +some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-with-mixed-case +some-group-for-user-integration-cached:x:102:user-integration-cached +some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-with-upper-case +some-group-for-user-integration-UPPER-CASE:x:104:user-integration-upper-case +some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration.group.backup b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration.group.backup new file mode 100644 index 0000000000..58742bc372 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration.group.backup @@ -0,0 +1,6 @@ +localgroup:x:41:user-integration-WITH-Mixed-CaSe,user-integration-cached,user-integration-pre-check-WITH-UPPER-CASE,user-integration-UPPER-CASE,user-pre-check +some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-WITH-Mixed-CaSe +some-group-for-user-integration-cached:x:102:user-integration-cached +some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-WITH-UPPER-CASE +some-group-for-user-integration-UPPER-CASE:x:104:user-integration-UPPER-CASE +some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration_on_shared_SSHd.group b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration_on_shared_SSHd.group new file mode 100644 index 0000000000..f31a69be33 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration_on_shared_SSHd.group @@ -0,0 +1,6 @@ +localgroup:x:41:user-integration-with-mixed-case,user-integration-cached,user-integration-pre-check-with-upper-case,user-integration-upper-case,user-pre-check +some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-with-mixed-case +some-group-for-user-integration-cached:x:102:user-integration-cached +some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-with-upper-case +some-group-for-user-integration-UPPER-CASE:x:104:user-integration-upper-case +some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration_on_shared_SSHd.group.backup b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration_on_shared_SSHd.group.backup new file mode 100644 index 0000000000..58742bc372 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_successfully_after_db_migration_on_shared_SSHd.group.backup @@ -0,0 +1,6 @@ +localgroup:x:41:user-integration-WITH-Mixed-CaSe,user-integration-cached,user-integration-pre-check-WITH-UPPER-CASE,user-integration-UPPER-CASE,user-pre-check +some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-WITH-Mixed-CaSe +some-group-for-user-integration-cached:x:102:user-integration-cached +some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-WITH-UPPER-CASE +some-group-for-user-integration-UPPER-CASE:x:104:user-integration-UPPER-CASE +some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.group b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.group new file mode 100644 index 0000000000..f31a69be33 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.group @@ -0,0 +1,6 @@ +localgroup:x:41:user-integration-with-mixed-case,user-integration-cached,user-integration-pre-check-with-upper-case,user-integration-upper-case,user-pre-check +some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-with-mixed-case +some-group-for-user-integration-cached:x:102:user-integration-cached +some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-with-upper-case +some-group-for-user-integration-UPPER-CASE:x:104:user-integration-upper-case +some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.group.backup b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.group.backup new file mode 100644 index 0000000000..58742bc372 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration.group.backup @@ -0,0 +1,6 @@ +localgroup:x:41:user-integration-WITH-Mixed-CaSe,user-integration-cached,user-integration-pre-check-WITH-UPPER-CASE,user-integration-UPPER-CASE,user-pre-check +some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-WITH-Mixed-CaSe +some-group-for-user-integration-cached:x:102:user-integration-cached +some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-WITH-UPPER-CASE +some-group-for-user-integration-UPPER-CASE:x:104:user-integration-UPPER-CASE +some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration_on_shared_SSHd.group b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration_on_shared_SSHd.group new file mode 100644 index 0000000000..f31a69be33 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration_on_shared_SSHd.group @@ -0,0 +1,6 @@ +localgroup:x:41:user-integration-with-mixed-case,user-integration-cached,user-integration-pre-check-with-upper-case,user-integration-upper-case,user-pre-check +some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-with-mixed-case +some-group-for-user-integration-cached:x:102:user-integration-cached +some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-with-upper-case +some-group-for-user-integration-UPPER-CASE:x:104:user-integration-upper-case +some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration_on_shared_SSHd.group.backup b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration_on_shared_SSHd.group.backup new file mode 100644 index 0000000000..58742bc372 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_mixed_case_after_db_migration_on_shared_SSHd.group.backup @@ -0,0 +1,6 @@ +localgroup:x:41:user-integration-WITH-Mixed-CaSe,user-integration-cached,user-integration-pre-check-WITH-UPPER-CASE,user-integration-UPPER-CASE,user-pre-check +some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-WITH-Mixed-CaSe +some-group-for-user-integration-cached:x:102:user-integration-cached +some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-WITH-UPPER-CASE +some-group-for-user-integration-UPPER-CASE:x:104:user-integration-UPPER-CASE +some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.group b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.group new file mode 100644 index 0000000000..f31a69be33 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.group @@ -0,0 +1,6 @@ +localgroup:x:41:user-integration-with-mixed-case,user-integration-cached,user-integration-pre-check-with-upper-case,user-integration-upper-case,user-pre-check +some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-with-mixed-case +some-group-for-user-integration-cached:x:102:user-integration-cached +some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-with-upper-case +some-group-for-user-integration-UPPER-CASE:x:104:user-integration-upper-case +some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.group.backup b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.group.backup new file mode 100644 index 0000000000..58742bc372 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration.group.backup @@ -0,0 +1,6 @@ +localgroup:x:41:user-integration-WITH-Mixed-CaSe,user-integration-cached,user-integration-pre-check-WITH-UPPER-CASE,user-integration-UPPER-CASE,user-pre-check +some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-WITH-Mixed-CaSe +some-group-for-user-integration-cached:x:102:user-integration-cached +some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-WITH-UPPER-CASE +some-group-for-user-integration-UPPER-CASE:x:104:user-integration-UPPER-CASE +some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration_on_shared_SSHd.group b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration_on_shared_SSHd.group new file mode 100644 index 0000000000..f31a69be33 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration_on_shared_SSHd.group @@ -0,0 +1,6 @@ +localgroup:x:41:user-integration-with-mixed-case,user-integration-cached,user-integration-pre-check-with-upper-case,user-integration-upper-case,user-pre-check +some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-with-mixed-case +some-group-for-user-integration-cached:x:102:user-integration-cached +some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-with-upper-case +some-group-for-user-integration-UPPER-CASE:x:104:user-integration-upper-case +some-group-for-user-pre-check:x:105:user-pre-check diff --git a/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration_on_shared_SSHd.group.backup b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration_on_shared_SSHd.group.backup new file mode 100644 index 0000000000..58742bc372 --- /dev/null +++ b/pam/integration-tests/testdata/golden/TestSSHAuthenticate/Authenticate_user_with_upper_case_using_lower_case_after_db_migration_on_shared_SSHd.group.backup @@ -0,0 +1,6 @@ +localgroup:x:41:user-integration-WITH-Mixed-CaSe,user-integration-cached,user-integration-pre-check-WITH-UPPER-CASE,user-integration-UPPER-CASE,user-pre-check +some-group-for-user-integration-WITH-Mixed-CaSe:x:101:user-integration-WITH-Mixed-CaSe +some-group-for-user-integration-cached:x:102:user-integration-cached +some-group-for-user-integration-pre-check-WITH-UPPER-CASE:x:103:user-integration-pre-check-WITH-UPPER-CASE +some-group-for-user-integration-UPPER-CASE:x:104:user-integration-UPPER-CASE +some-group-for-user-pre-check:x:105:user-pre-check From 45f0633d08c0d5098c41fc14cf28b62fcc35e988 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 23 Jun 2025 20:06:00 +0200 Subject: [PATCH 0532/1670] testutils/broker: Always return a cleanup to callers when failing We might fail without an error, but still taking a return path together with a cleanup nil function that may end up being called on tests cleanups by mistake --- internal/testutils/broker.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/testutils/broker.go b/internal/testutils/broker.go index 5b20f816e3..b127ebe07d 100644 --- a/internal/testutils/broker.go +++ b/internal/testutils/broker.go @@ -3,6 +3,7 @@ package testutils import ( "bytes" "context" + "errors" "fmt" "html/template" "os" @@ -95,9 +96,12 @@ func StartBusBrokerMock(cfgDir string, brokerName string) (string, func(), error } reply, err := conn.RequestName(busName, dbus.NameFlagDoNotQueue) - if err != nil || reply != dbus.RequestNameReplyPrimaryOwner { + if err != nil { conn.Close() - return "", nil, err + return "", nil, fmt.Errorf("can't get the D-Bus name %s: %w", busName, err) + } + if reply != dbus.RequestNameReplyPrimaryOwner { + return "", nil, errors.New("not a D-Bus primary name owner") } configPath, err := writeConfig(cfgDir, brokerName) From 14dc75af1a7bfa6e62c7795111f8ab1d2f6cf3e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 17 Jun 2025 22:35:39 +0200 Subject: [PATCH 0533/1670] users/manager: Lock users database while adding temporary users During this phase any other NSS request should be blocked until we're done --- internal/services/user/user_test.go | 5 +++ internal/users/manager.go | 15 ++++++++- internal/users/manager_test.go | 44 +++++++++++++++++++++++++++ internal/users/tempentries/preauth.go | 2 +- 4 files changed, 64 insertions(+), 2 deletions(-) diff --git a/internal/services/user/user_test.go b/internal/services/user/user_test.go index 01c7754287..f6f78be990 100644 --- a/internal/services/user/user_test.go +++ b/internal/services/user/user_test.go @@ -19,6 +19,7 @@ import ( "github.com/ubuntu/authd/internal/users" "github.com/ubuntu/authd/internal/users/db" "github.com/ubuntu/authd/internal/users/idgenerator" + userslocking "github.com/ubuntu/authd/internal/users/locking" "github.com/ubuntu/authd/log" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -66,6 +67,10 @@ func TestGetUserByName(t *testing.T) { } for name, tc := range tests { t.Run(name, func(t *testing.T) { + if tc.shouldPreCheck { + userslocking.Z_ForTests_OverrideLockingWithCleanup(t) + } + client := newUserServiceClient(t, tc.dbFile) got, err := client.GetUserByName(context.Background(), &authd.GetUserByNameRequest{Name: tc.username, ShouldPreCheck: tc.shouldPreCheck}) diff --git a/internal/users/manager.go b/internal/users/manager.go index 4901f41c65..57ae14ef7e 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -14,6 +14,7 @@ import ( "github.com/ubuntu/authd/internal/users/db" "github.com/ubuntu/authd/internal/users/idgenerator" "github.com/ubuntu/authd/internal/users/localentries" + userslocking "github.com/ubuntu/authd/internal/users/locking" "github.com/ubuntu/authd/internal/users/tempentries" "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/authd/log" @@ -455,6 +456,18 @@ func (m *Manager) AllShadows() ([]types.ShadowEntry, error) { // RegisterUserPreAuth registers a temporary user with a unique UID in our NSS handler (in memory, not in the database). // // The temporary user record is removed when UpdateUser is called with the same username. -func (m *Manager) RegisterUserPreAuth(name string) (uint32, error) { +func (m *Manager) RegisterUserPreAuth(name string) (uid uint32, err error) { + defer decorate.OnError(&err, "failed to register pre-auth user %q", name) + + if err := userslocking.WriteRecLock(); err != nil { + return 0, err + } + defer func() { + if unlockErr := userslocking.WriteRecUnlock(); unlockErr != nil { + uid = 0 + err = errors.Join(err, unlockErr) + } + }() + return m.temporaryRecords.RegisterPreAuthUser(name) } diff --git a/internal/users/manager_test.go b/internal/users/manager_test.go index 9cfe50fe3e..98d20f2a66 100644 --- a/internal/users/manager_test.go +++ b/internal/users/manager_test.go @@ -5,9 +5,11 @@ import ( "os" "path/filepath" "testing" + "time" "github.com/stretchr/testify/require" "github.com/ubuntu/authd/internal/consts" + "github.com/ubuntu/authd/internal/testutils" "github.com/ubuntu/authd/internal/testutils/golden" "github.com/ubuntu/authd/internal/users" "github.com/ubuntu/authd/internal/users/db" @@ -587,6 +589,48 @@ func TestAllShadows(t *testing.T) { } } +func TestRegisterUserPreAuthWhenLocked(t *testing.T) { + // This cannot be parallel + + userslocking.Z_ForTests_OverrideLockingAsLockedExternally(t, context.Background()) + userslocking.Z_ForTests_SetMaxWaitTime(t, testutils.MultipliedSleepDuration(750*time.Millisecond)) + + dbFile := "one_user_and_group" + dbDir := t.TempDir() + err := db.Z_ForTests_CreateDBFromYAML(filepath.Join("testdata", "db", dbFile+".db.yaml"), dbDir) + require.NoError(t, err, "Setup: could not create database from testdata") + + m := newManagerForTests(t, dbDir) + + uid, err := m.RegisterUserPreAuth("locked-user") + require.ErrorIs(t, err, userslocking.ErrLock) + require.Zero(t, uid, "Uid should be unset") +} + +func TestRegisterUserPreAuthAfterUnlock(t *testing.T) { + // This cannot be parallel + + waitTime := testutils.MultipliedSleepDuration(750 * time.Millisecond) + lockCtx, lockCancel := context.WithTimeout(context.Background(), waitTime/2) + t.Cleanup(lockCancel) + + userslocking.Z_ForTests_OverrideLockingAsLockedExternally(t, lockCtx) + userslocking.Z_ForTests_SetMaxWaitTime(t, waitTime) + + t.Cleanup(func() { _ = userslocking.WriteRecUnlock() }) + + dbFile := "one_user_and_group" + dbDir := t.TempDir() + err := db.Z_ForTests_CreateDBFromYAML(filepath.Join("testdata", "db", dbFile+".db.yaml"), dbDir) + require.NoError(t, err, "Setup: could not create database from testdata") + + m := newManagerForTests(t, dbDir) + + uid, err := m.RegisterUserPreAuth("locked-user") + require.NoError(t, err, "Registration should not fail") + require.NotZero(t, uid, "UID should be set") +} + func requireErrorAssertions(t *testing.T, gotErr, wantErrType error, wantErr bool) { t.Helper() diff --git a/internal/users/tempentries/preauth.go b/internal/users/tempentries/preauth.go index 11d26f6e5c..36a7781856 100644 --- a/internal/users/tempentries/preauth.go +++ b/internal/users/tempentries/preauth.go @@ -115,7 +115,7 @@ func preAuthUserEntry(user preAuthUser) types.UserEntry { // after the user authenticated successfully. // // Returns the generated UID. -func (r *preAuthUserRecords) RegisterPreAuthUser(loginName string) (uint32, error) { +func (r *preAuthUserRecords) RegisterPreAuthUser(loginName string) (uid uint32, err error) { // To mitigate DoS attacks, we limit the length of the name to 256 characters. if len(loginName) > 256 { return 0, errors.New("username is too long (max 256 characters)") From 87c77e0e3c564b6775db48a8d7a4788f571b492c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 17 Jun 2025 23:16:06 +0200 Subject: [PATCH 0534/1670] users/tempentries/preauth: Get the local entries just once Since we're locking now, we're sure that the local entries won't change, thus there's no much reason to get them at the very last time --- internal/users/tempentries/preauth.go | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/internal/users/tempentries/preauth.go b/internal/users/tempentries/preauth.go index 36a7781856..16802cf5dd 100644 --- a/internal/users/tempentries/preauth.go +++ b/internal/users/tempentries/preauth.go @@ -138,6 +138,15 @@ func (r *preAuthUserRecords) RegisterPreAuthUser(loginName string) (uid uint32, return user.UID, nil } + passwdEntries, err := localentries.GetPasswdEntries() + if err != nil { + return 0, fmt.Errorf("could not check user, failed to get passwd entries: %w", err) + } + groupEntries, err := localentries.GetGroupEntries() + if err != nil { + return 0, fmt.Errorf("could not check user, failed to get group entries: %w", err) + } + // Generate a UID until we find a unique one for { uid, err := r.idGenerator.GenerateUID() @@ -154,7 +163,7 @@ func (r *preAuthUserRecords) RegisterPreAuthUser(loginName string) (uid uint32, return 0, fmt.Errorf("could not add pre-auth user record: %w", err) } - unique, err := r.isUniqueUID(uid, tmpName) + unique, err := r.isUniqueUID(uid, passwdEntries, groupEntries, tmpName) if err != nil { cleanup() return 0, fmt.Errorf("could not check if UID %d is unique: %w", uid, err) @@ -171,21 +180,13 @@ func (r *preAuthUserRecords) RegisterPreAuthUser(loginName string) (uid uint32, // isUniqueUID returns true if the given UID is unique in the system. It returns false if the UID is already assigned to // a user by any NSS source (except the given temporary user). -func (r *preAuthUserRecords) isUniqueUID(uid uint32, tmpName string) (bool, error) { - entries, err := localentries.GetPasswdEntries() - if err != nil { - return false, err - } - for _, entry := range entries { +func (r *preAuthUserRecords) isUniqueUID(uid uint32, passwdEntries []types.UserEntry, groupEntries []types.GroupEntry, tmpName string) (bool, error) { + for _, entry := range passwdEntries { if entry.UID == uid && entry.Name != tmpName { return false, nil } } - groupEntries, err := localentries.GetGroupEntries() - if err != nil { - return false, fmt.Errorf("failed to get group entries: %w", err) - } for _, group := range groupEntries { if group.GID == uid { // A group with the same ID already exists, so we can't use that ID as the GID of the temporary user From 3cad565bbad7305e934cc6e4c7e9628f6f6bab9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 18 Jun 2025 03:39:55 +0200 Subject: [PATCH 0535/1670] users/tempentries/preauth: Add temporary users after all checks passed Now that we have a safe locking mechanism we don't have to ensure that the ordering of the operations will make the other NSS clients to avoid racing with us, but rather we can just assume that once locked the number of local users and groups are stable and that nothing else can add users meanwhile. Thus let's just add the temporary users only after that we've checked that the computed uid is unique --- internal/users/tempentries/preauth.go | 46 +++++++++---------- .../users/tempentries/tempentries_test.go | 2 +- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/internal/users/tempentries/preauth.go b/internal/users/tempentries/preauth.go index 16802cf5dd..d72757e299 100644 --- a/internal/users/tempentries/preauth.go +++ b/internal/users/tempentries/preauth.go @@ -154,35 +154,35 @@ func (r *preAuthUserRecords) RegisterPreAuthUser(loginName string) (uid uint32, return 0, err } - // To avoid races where a user with this UID is created by some NSS source after we checked, we register this - // UID in our NSS handler and then check if another user with the same UID exists in the system. This way we - // can guarantee that the UID is unique, under the assumption that other NSS sources don't add users with a UID - // that we already registered (if they do, there's nothing we can do about it). - tmpName, cleanup, err := r.addPreAuthUser(uid, loginName) + unique, err := r.isUniqueUID(uid, passwdEntries, groupEntries) if err != nil { - return 0, fmt.Errorf("could not add pre-auth user record: %w", err) + return 0, fmt.Errorf("could not check if UID %d is unique: %w", uid, err) } - unique, err := r.isUniqueUID(uid, passwdEntries, groupEntries, tmpName) - if err != nil { - cleanup() - return 0, fmt.Errorf("could not check if UID %d is unique: %w", uid, err) + if !unique { + // If the UID is not unique, generate a new one in the next iteration. + continue } - if unique { - log.Debugf(context.Background(), "Added temporary record for user %q with UID %d", loginName, uid) - return uid, nil + + // To avoid races where a user with this UID is created by some NSS source + // until the user actually logs in, we register this UID in our NSS handler. + if err := r.addPreAuthUser(uid, loginName); err != nil { + return 0, fmt.Errorf("could not add pre-auth user record: %w", err) } - // If the UID is not unique, remove the temporary user and generate a new one in the next iteration. - cleanup() + log.Debugf(context.Background(), + "Added temporary record for user %q with UID %d", loginName, uid) + + return uid, nil } } // isUniqueUID returns true if the given UID is unique in the system. It returns false if the UID is already assigned to -// a user by any NSS source (except the given temporary user). -func (r *preAuthUserRecords) isUniqueUID(uid uint32, passwdEntries []types.UserEntry, groupEntries []types.GroupEntry, tmpName string) (bool, error) { +// a user by any NSS source. +func (r *preAuthUserRecords) isUniqueUID(uid uint32, passwdEntries []types.UserEntry, groupEntries []types.GroupEntry) (bool, error) { for _, entry := range passwdEntries { - if entry.UID == uid && entry.Name != tmpName { + if entry.UID == uid { + log.Debugf(context.Background(), "ID %d already in use by user %q", uid, entry.Name) return false, nil } } @@ -202,7 +202,7 @@ func (r *preAuthUserRecords) isUniqueUID(uid uint32, passwdEntries []types.UserE // creating user records with attacker-controlled names. // // It returns the generated name and a cleanup function to remove the temporary user record. -func (r *preAuthUserRecords) addPreAuthUser(uid uint32, loginName string) (name string, cleanup func(), err error) { +func (r *preAuthUserRecords) addPreAuthUser(uid uint32, loginName string) (err error) { r.rwMu.Lock() defer r.rwMu.Unlock() @@ -210,9 +210,9 @@ func (r *preAuthUserRecords) addPreAuthUser(uid uint32, loginName string) (name // record to be able to identify it in isUniqueUID. bytes := make([]byte, 32) if _, err := rand.Read(bytes); err != nil { - return "", nil, fmt.Errorf("failed to generate random name: %w", err) + return fmt.Errorf("failed to generate random name: %w", err) } - name = fmt.Sprintf("%s-%x", UserPrefix, bytes) + name := fmt.Sprintf("%s-%x", UserPrefix, bytes) user := preAuthUser{name: name, uid: uid, loginName: loginName} r.users[uid] = user @@ -220,9 +220,7 @@ func (r *preAuthUserRecords) addPreAuthUser(uid uint32, loginName string) (name r.uidByLogin[loginName] = uid r.numUsers++ - cleanup = func() { r.deletePreAuthUser(uid) } - - return name, cleanup, nil + return nil } // deletePreAuthUser deletes the temporary user with the given UID. diff --git a/internal/users/tempentries/tempentries_test.go b/internal/users/tempentries/tempentries_test.go index f90a842bf2..c477492e9d 100644 --- a/internal/users/tempentries/tempentries_test.go +++ b/internal/users/tempentries/tempentries_test.go @@ -67,7 +67,7 @@ func TestRegisterUser(t *testing.T) { if tc.preAuthUIDAlreadyExists { preAuthUID = 0 // UID 0 (root) always exists } - _, _, err := records.preAuthUserRecords.addPreAuthUser(preAuthUID, tc.userName) + err := records.preAuthUserRecords.addPreAuthUser(preAuthUID, tc.userName) require.NoError(t, err, "addPreAuthUser should not return an error, but did") } From c4e391c75fe5ce2324584f3017e959b0a7fe2dcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 18 Jun 2025 03:50:17 +0200 Subject: [PATCH 0536/1670] users/tempentries/preauth: Use native length to compute users --- internal/users/tempentries/preauth.go | 5 +---- internal/users/tempentries/preauth_test.go | 11 +++++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/users/tempentries/preauth.go b/internal/users/tempentries/preauth.go index d72757e299..012d377399 100644 --- a/internal/users/tempentries/preauth.go +++ b/internal/users/tempentries/preauth.go @@ -41,7 +41,6 @@ type preAuthUserRecords struct { users map[uint32]preAuthUser uidByName map[string]uint32 uidByLogin map[string]uint32 - numUsers int } func newPreAuthUserRecords(idGenerator IDGenerator) *preAuthUserRecords { @@ -124,7 +123,7 @@ func (r *preAuthUserRecords) RegisterPreAuthUser(loginName string) (uid uint32, r.registerMu.Lock() defer r.registerMu.Unlock() - if r.numUsers >= MaxPreAuthUsers { + if len(r.users) >= MaxPreAuthUsers { return 0, errors.New("maximum number of pre-auth users reached, login for new users via SSH is disabled until authd is restarted") } @@ -218,7 +217,6 @@ func (r *preAuthUserRecords) addPreAuthUser(uid uint32, loginName string) (err e r.users[uid] = user r.uidByName[name] = uid r.uidByLogin[loginName] = uid - r.numUsers++ return nil } @@ -238,6 +236,5 @@ func (r *preAuthUserRecords) deletePreAuthUser(uid uint32) { delete(r.users, uid) delete(r.uidByName, user.name) delete(r.uidByLogin, user.loginName) - r.numUsers-- log.Debugf(context.Background(), "Removed temporary record for user %q with UID %d", user.name, uid) } diff --git a/internal/users/tempentries/preauth_test.go b/internal/users/tempentries/preauth_test.go index 8d3dac6677..9d882e3d64 100644 --- a/internal/users/tempentries/preauth_test.go +++ b/internal/users/tempentries/preauth_test.go @@ -44,7 +44,10 @@ func TestPreAuthUser(t *testing.T) { records := newPreAuthUserRecords(idGeneratorMock) if tc.maxUsers { - records.numUsers = MaxPreAuthUsers + records.users = make(map[uint32]preAuthUser, MaxPreAuthUsers) + for i := range uint32(MaxPreAuthUsers) { + records.users[uidToGenerate+i] = preAuthUser{uid: uidToGenerate + i} + } } uid, err := records.RegisterPreAuthUser(loginName) @@ -54,13 +57,13 @@ func TestPreAuthUser(t *testing.T) { } require.NoError(t, err, "RegisterPreAuthUser should not return an error, but did") require.Equal(t, uidToGenerate, uid, "UID should be the one generated by the IDGenerator") - require.Equal(t, records.numUsers, 1, "Number of pre-auth users should be 1") + require.Equal(t, len(records.users), 1, "Number of pre-auth users should be 1") if tc.registerTwice { uid, err = records.RegisterPreAuthUser(loginName) require.NoError(t, err, "RegisterPreAuthUser should not return an error, but did") require.Equal(t, uidToGenerate, uid, "UID should be the one generated by the IDGenerator") - require.Equal(t, records.numUsers, 1, "Number of pre-auth users should be 1") + require.Equal(t, len(records.users), 1, "Number of pre-auth users should be 1") } // Check that the user was registered @@ -70,7 +73,7 @@ func TestPreAuthUser(t *testing.T) { // Remove the user records.deletePreAuthUser(uidToGenerate) - require.Equal(t, records.numUsers, 0, "Number of pre-auth users should be 0") + require.Equal(t, len(records.users), 0, "Number of pre-auth users should be 0") // Check that the user was removed _, err = records.userByLogin(loginName) From 86898d2e50cf2194d4ee77f080ce9efde97eade5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 18 Jun 2025 14:55:57 +0200 Subject: [PATCH 0537/1670] users/tempentries/preauth: Lock to check if pre-checked user is available Closes: #986 --- internal/users/tempentries/preauth.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/internal/users/tempentries/preauth.go b/internal/users/tempentries/preauth.go index 012d377399..f8ce4ca4eb 100644 --- a/internal/users/tempentries/preauth.go +++ b/internal/users/tempentries/preauth.go @@ -123,8 +123,16 @@ func (r *preAuthUserRecords) RegisterPreAuthUser(loginName string) (uid uint32, r.registerMu.Lock() defer r.registerMu.Unlock() - if len(r.users) >= MaxPreAuthUsers { - return 0, errors.New("maximum number of pre-auth users reached, login for new users via SSH is disabled until authd is restarted") + if err = func() error { + r.rwMu.RLock() + defer r.rwMu.RUnlock() + + if len(r.users) >= MaxPreAuthUsers { + return errors.New("maximum number of pre-auth users reached, login for new users via SSH is disabled until authd is restarted") + } + return nil + }(); err != nil { + return 0, err } // Check if there is already a pre-auth user for that name From 1df78099479374297b1c397462a2538eac11db2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 18 Jun 2025 15:40:33 +0200 Subject: [PATCH 0538/1670] users/tempentries/preauth: Make pre-auth test to be more dynamic It allows now to register multiple users and not just two --- internal/users/tempentries/preauth_test.go | 114 +++++++++++++----- ..._a_pre-auth_user_with_the_same_name_test_1 | 6 + ...uccessfully_register_a_pre-auth_user_again | 6 + ...ully_register_a_pre-auth_user_again_test_1 | 6 + 4 files changed, 103 insertions(+), 29 deletions(-) create mode 100644 internal/users/tempentries/testdata/golden/TestPreAuthUser/No_error_when_registering_a_pre-auth_user_with_the_same_name_test_1 create mode 100644 internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again create mode 100644 internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again_test_1 diff --git a/internal/users/tempentries/preauth_test.go b/internal/users/tempentries/preauth_test.go index 9d882e3d64..f9e7cc6dbf 100644 --- a/internal/users/tempentries/preauth_test.go +++ b/internal/users/tempentries/preauth_test.go @@ -1,6 +1,8 @@ package tempentries import ( + "fmt" + "slices" "strings" "testing" @@ -13,21 +15,28 @@ import ( func TestPreAuthUser(t *testing.T) { t.Parallel() - loginName := "test" + defaultLoginName := "test" uidToGenerate := uint32(12345) tests := map[string]struct { + users []string maxUsers bool uidsToGenerate []uint32 - registerTwice bool - wantErr bool + wantErr bool + wantUIDs []uint32 }{ "Successfully_register_a_pre-auth_user": {}, "Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_in_use": { uidsToGenerate: []uint32{0, uidToGenerate}, // UID 0 (root) always exists + wantUIDs: []uint32{uidToGenerate}, }, - "No_error_when_registering_a_pre-auth_user_with_the_same_name": {registerTwice: true}, + "Successfully_register_a_pre-auth_user_again": { + users: []string{defaultLoginName, defaultLoginName}, + uidsToGenerate: []uint32{uidToGenerate}, + wantUIDs: []uint32{uidToGenerate, uidToGenerate}, + }, + "No_error_when_registering_a_pre-auth_user_with_the_same_name": {users: []string{defaultLoginName, defaultLoginName}}, "Error_when_maximum_number_of_pre-auth_users_is_reached": {maxUsers: true, wantErr: true}, } @@ -36,10 +45,22 @@ func TestPreAuthUser(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() + if len(tc.users) == 0 { + tc.users = append(tc.users, defaultLoginName) + } + if tc.uidsToGenerate == nil { - tc.uidsToGenerate = []uint32{uidToGenerate} + uid := uidToGenerate + for range tc.users { + tc.uidsToGenerate = append(tc.uidsToGenerate, uid) + uid++ + } + } + if tc.wantUIDs == nil { + tc.wantUIDs = tc.uidsToGenerate } + t.Log("UIDs to generate", tc.uidsToGenerate) idGeneratorMock := &idgenerator.IDGeneratorMock{UIDsToGenerate: tc.uidsToGenerate} records := newPreAuthUserRecords(idGeneratorMock) @@ -50,34 +71,69 @@ func TestPreAuthUser(t *testing.T) { } } - uid, err := records.RegisterPreAuthUser(loginName) - if tc.wantErr { - require.Error(t, err, "RegisterPreAuthUser should return an error, but did not") - return - } - require.NoError(t, err, "RegisterPreAuthUser should not return an error, but did") - require.Equal(t, uidToGenerate, uid, "UID should be the one generated by the IDGenerator") - require.Equal(t, len(records.users), 1, "Number of pre-auth users should be 1") + wantRegistered := 0 + var registeredUIDs []uint32 + + for idx, loginName := range tc.users { + t.Logf("Registering user %q", loginName) + uid, err := records.RegisterPreAuthUser(loginName) + if tc.wantErr { + require.Error(t, err, "RegisterPreAuthUser should return an error, but did not") + continue + } + + isDuplicated := slices.Contains(tc.users[0:idx], loginName) + if !isDuplicated { + wantRegistered++ + } + + wantUID := tc.wantUIDs[wantRegistered-1] - if tc.registerTwice { - uid, err = records.RegisterPreAuthUser(loginName) require.NoError(t, err, "RegisterPreAuthUser should not return an error, but did") - require.Equal(t, uidToGenerate, uid, "UID should be the one generated by the IDGenerator") - require.Equal(t, len(records.users), 1, "Number of pre-auth users should be 1") + require.Equal(t, wantUID, uid, "UID should be the one generated by the IDGenerator") + require.Equal(t, wantRegistered, len(records.users), + "Number of pre-auth registered, users should be %d", wantRegistered) + + if isDuplicated { + require.Contains(t, registeredUIDs, uid, "UID %d has been already registered!", uid) + } else { + require.NotContains(t, registeredUIDs, uid, "UID %d has not been already registered!", uid) + } + + registeredUIDs = append(registeredUIDs, uid) + + // Check that the user was registered + user, err := records.userByLogin(loginName) + require.NoError(t, err, "UserByID should not return an error, but did") + + var goldenOptions []golden.Option + if idx > 0 { + userSuffix := fmt.Sprintf("_%s_%d", loginName, idx) + goldenOptions = append(goldenOptions, golden.WithSuffix(userSuffix)) + } + checkPreAuthUser(t, user, goldenOptions...) } - // Check that the user was registered - user, err := records.userByLogin(loginName) - require.NoError(t, err, "UserByID should not return an error, but did") - checkPreAuthUser(t, user) + if wantRegistered == 0 { + return + } - // Remove the user - records.deletePreAuthUser(uidToGenerate) - require.Equal(t, len(records.users), 0, "Number of pre-auth users should be 0") + for idx, loginName := range tc.users { + isDuplicated := slices.Contains(tc.users[0:idx], loginName) + if !isDuplicated { + wantRegistered-- + } - // Check that the user was removed - _, err = records.userByLogin(loginName) - require.Error(t, err, "UserByID should return an error, but did not") + removeUID := registeredUIDs[len(registeredUIDs)-wantRegistered-1] + t.Logf("Removing user %q for UID %v", loginName, removeUID) + records.deletePreAuthUser(removeUID) + require.Equal(t, wantRegistered, len(records.users), + "Number of pre-auth users should be %d", wantRegistered) + + // Check that the user was removed + _, err := records.userByLogin(loginName) + require.Error(t, err, "UserByID should return an error, but did not") + } }) } } @@ -139,7 +195,7 @@ func TestPreAuthUserByIDAndName(t *testing.T) { } } -func checkPreAuthUser(t *testing.T, user types.UserEntry) { +func checkPreAuthUser(t *testing.T, user types.UserEntry, options ...golden.Option) { t.Helper() // The name field contains a randomly generated part, so we replace that part @@ -148,5 +204,5 @@ func checkPreAuthUser(t *testing.T, user types.UserEntry) { "Name should have %q prefix", UserPrefix) user.Name = UserPrefix + "-XXXXXXXX" - golden.CheckOrUpdateYAML(t, user) + golden.CheckOrUpdateYAML(t, user, options...) } diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUser/No_error_when_registering_a_pre-auth_user_with_the_same_name_test_1 b/internal/users/tempentries/testdata/golden/TestPreAuthUser/No_error_when_registering_a_pre-auth_user_with_the_same_name_test_1 new file mode 100644 index 0000000000..8b0eea6888 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestPreAuthUser/No_error_when_registering_a_pre-auth_user_with_the_same_name_test_1 @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: test +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again new file mode 100644 index 0000000000..8b0eea6888 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: test +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again_test_1 b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again_test_1 new file mode 100644 index 0000000000..8b0eea6888 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again_test_1 @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: test +dir: /nonexistent +shell: /usr/sbin/nologin From 285cf2eeb9459b904e1e04aae44d3e3e58594066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 18 Jun 2025 13:09:38 +0000 Subject: [PATCH 0539/1670] users/tempentries/preauth: Do not use the same UID twice We may end up generating the same UID twice and if this happens (unlikely, but random is never random!), we end up replacing the previously generated user with another one --- internal/users/tempentries/preauth.go | 7 +++++++ internal/users/tempentries/preauth_test.go | 5 +++++ ...h_user_if_the_first_generated_UID_is_already_registered | 6 ++++++ ..._first_generated_UID_is_already_registered_other-test_1 | 6 ++++++ 4 files changed, 24 insertions(+) create mode 100644 internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_registered create mode 100644 internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_registered_other-test_1 diff --git a/internal/users/tempentries/preauth.go b/internal/users/tempentries/preauth.go index f8ce4ca4eb..d59ca05242 100644 --- a/internal/users/tempentries/preauth.go +++ b/internal/users/tempentries/preauth.go @@ -187,6 +187,13 @@ func (r *preAuthUserRecords) RegisterPreAuthUser(loginName string) (uid uint32, // isUniqueUID returns true if the given UID is unique in the system. It returns false if the UID is already assigned to // a user by any NSS source. func (r *preAuthUserRecords) isUniqueUID(uid uint32, passwdEntries []types.UserEntry, groupEntries []types.GroupEntry) (bool, error) { + r.rwMu.RLock() + defer r.rwMu.RUnlock() + + if _, ok := r.users[uid]; ok { + return false, nil + } + for _, entry := range passwdEntries { if entry.UID == uid { log.Debugf(context.Background(), "ID %d already in use by user %q", uid, entry.Name) diff --git a/internal/users/tempentries/preauth_test.go b/internal/users/tempentries/preauth_test.go index f9e7cc6dbf..9491fe03d0 100644 --- a/internal/users/tempentries/preauth_test.go +++ b/internal/users/tempentries/preauth_test.go @@ -36,6 +36,11 @@ func TestPreAuthUser(t *testing.T) { uidsToGenerate: []uint32{uidToGenerate}, wantUIDs: []uint32{uidToGenerate, uidToGenerate}, }, + "Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_registered": { + users: []string{defaultLoginName, "other-test"}, + uidsToGenerate: []uint32{uidToGenerate, uidToGenerate, uidToGenerate + 1}, + wantUIDs: []uint32{uidToGenerate, uidToGenerate + 1}, + }, "No_error_when_registering_a_pre-auth_user_with_the_same_name": {users: []string{defaultLoginName, defaultLoginName}}, "Error_when_maximum_number_of_pre-auth_users_is_reached": {maxUsers: true, wantErr: true}, diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_registered b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_registered new file mode 100644 index 0000000000..8b0eea6888 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_registered @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: test +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_registered_other-test_1 b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_registered_other-test_1 new file mode 100644 index 0000000000..24dee20169 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_registered_other-test_1 @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12346 +gid: 12346 +gecos: other-test +dir: /nonexistent +shell: /usr/sbin/nologin From a548ee7f8e248c51b3d9fb240cdfe007d07b01bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 19 Jun 2025 02:25:25 +0200 Subject: [PATCH 0540/1670] users/tempentries/preauth: Do not allow to register a local user If an attempt to pre-auth a local user is done (it requires the broker to collaborate, though), then we didn't fail promptly but instead we were returning a fake user with a newly generated UID, and this would have failed only at the final registration phase. Be proactive, and block this earlier --- internal/services/user/user_test.go | 14 ++++++++++++-- internal/testutils/broker.go | 5 ++++- internal/users/tempentries/preauth.go | 8 ++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/internal/services/user/user_test.go b/internal/services/user/user_test.go index f6f78be990..55370428a0 100644 --- a/internal/services/user/user_test.go +++ b/internal/services/user/user_test.go @@ -62,8 +62,9 @@ func TestGetUserByName(t *testing.T) { "Error_with_typed_GRPC_notfound_code_on_unexisting_user": {username: "does-not-exists", wantErr: true, wantErrNotExists: true}, "Error_on_missing_name": {wantErr: true}, - "Error_if_user_not_in_db_and_precheck_is_disabled": {username: "user-pre-check", wantErr: true, wantErrNotExists: true}, - "Error_if_user_not_in_db_and_precheck_fails": {username: "does-not-exist", dbFile: "empty.db.yaml", shouldPreCheck: true, wantErr: true, wantErrNotExists: true}, + "Error_if_user_not_in_db_and_precheck_is_disabled": {username: "user-pre-check", wantErr: true, wantErrNotExists: true}, + "Error_if_user_not_in_db_and_precheck_fails": {username: "does-not-exist", dbFile: "empty.db.yaml", shouldPreCheck: true, wantErr: true, wantErrNotExists: true}, + "Error_if_user_not_in_db_and_precheck_fails_for_existing_user": {username: "local-pre-check", dbFile: "empty.db.yaml", shouldPreCheck: true, wantErr: true, wantErrNotExists: true}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { @@ -75,6 +76,13 @@ func TestGetUserByName(t *testing.T) { got, err := client.GetUserByName(context.Background(), &authd.GetUserByNameRequest{Name: tc.username, ShouldPreCheck: tc.shouldPreCheck}) requireExpectedResult(t, "GetUserByName", got, err, tc.wantErr, tc.wantErrNotExists) + + if !tc.shouldPreCheck || tc.wantErr { + return + } + + _, err = client.GetUserByName(context.Background(), &authd.GetUserByNameRequest{Name: tc.username, ShouldPreCheck: false}) + require.Error(t, err, "GetUserByName should return an error, but did not") }) } } @@ -307,7 +315,9 @@ func requireExpectedResult[T authd.User | authd.Group](t *testing.T, funcName st require.True(t, ok, "The error is always a gRPC error") if wantErrNotExists { require.Equal(t, codes.NotFound.String(), s.Code().String()) + return } + require.NotEqual(t, codes.NotFound.String(), s.Code().String()) return } require.NoError(t, err, fmt.Sprintf("%s should not return an error, but did", funcName)) diff --git a/internal/testutils/broker.go b/internal/testutils/broker.go index b127ebe07d..98e4d38504 100644 --- a/internal/testutils/broker.go +++ b/internal/testutils/broker.go @@ -335,7 +335,7 @@ func (b *BrokerBusMock) CancelIsAuthenticated(sessionID string) (dbusErr *dbus.E // UserPreCheck returns default values to be used in tests or an error if requested. func (b *BrokerBusMock) UserPreCheck(username string) (userinfo string, dbusErr *dbus.Error) { - if strings.ToLower(username) != "user-pre-check" { + if username != "user-pre-check" && username != "local-pre-check" { return "", dbus.MakeFailedError(fmt.Errorf("broker %q: UserPreCheck errored out", b.name)) } return userInfoFromName(username, nil), nil @@ -387,6 +387,9 @@ func userInfoFromName(sessionID string, extraGroups []groupJSONInfo) string { home = "this is not a homedir" case "ia_info_invalid_shell": shell = "this is not a valid shell" + case "local-pre-check": + name = "root" + home = "/root" } groups := []groupJSONInfo{{Name: group, UGID: ugid}} diff --git a/internal/users/tempentries/preauth.go b/internal/users/tempentries/preauth.go index d59ca05242..e1e6dd5591 100644 --- a/internal/users/tempentries/preauth.go +++ b/internal/users/tempentries/preauth.go @@ -5,6 +5,7 @@ import ( "crypto/rand" "errors" "fmt" + "slices" "sync" "github.com/ubuntu/authd/internal/users/db" @@ -154,6 +155,13 @@ func (r *preAuthUserRecords) RegisterPreAuthUser(loginName string) (uid uint32, return 0, fmt.Errorf("could not check user, failed to get group entries: %w", err) } + if slices.ContainsFunc(passwdEntries, func(p types.UserEntry) bool { + return p.Name == loginName + }) { + log.Errorf(context.Background(), "User already exists on the system: %+v", loginName) + return 0, fmt.Errorf("user %q already exists on the system", loginName) + } + // Generate a UID until we find a unique one for { uid, err := r.idGenerator.GenerateUID() From 8a9835dee36def170a6b585b3bf7c39567c0dd45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 20 Jun 2025 04:00:13 +0200 Subject: [PATCH 0541/1670] users/manager: Lock local entries while updating the users We are about to remove the temporary user items, so we should lock before of doing this to prevent other NSS sources to change our users --- internal/users/manager.go | 9 ++++++++ internal/users/manager_test.go | 40 ++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/internal/users/manager.go b/internal/users/manager.go index 57ae14ef7e..6c13a2e791 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -138,6 +138,15 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { } if errors.Is(err, db.NoDataFoundError{}) { // Check if the user exists on the system + if err := userslocking.WriteRecLock(); err != nil { + return err + } + defer func() { + if unlockErr := userslocking.WriteRecUnlock(); unlockErr != nil { + err = errors.Join(err, unlockErr) + } + }() + existingUser, err := user.Lookup(u.Name) var unknownUserErr user.UnknownUserError if !errors.As(err, &unknownUserErr) { diff --git a/internal/users/manager_test.go b/internal/users/manager_test.go index 98d20f2a66..84eb5f3415 100644 --- a/internal/users/manager_test.go +++ b/internal/users/manager_test.go @@ -631,6 +631,46 @@ func TestRegisterUserPreAuthAfterUnlock(t *testing.T) { require.NotZero(t, uid, "UID should be set") } +func TestUpdateUserWhenLocked(t *testing.T) { + // This cannot be parallel + + userslocking.Z_ForTests_OverrideLockingAsLockedExternally(t, context.Background()) + userslocking.Z_ForTests_SetMaxWaitTime(t, testutils.MultipliedSleepDuration(750*time.Millisecond)) + + dbFile := "one_user_and_group" + dbDir := t.TempDir() + err := db.Z_ForTests_CreateDBFromYAML(filepath.Join("testdata", "db", dbFile+".db.yaml"), dbDir) + require.NoError(t, err, "Setup: could not create database from testdata") + + m := newManagerForTests(t, dbDir) + + err = m.UpdateUser(types.UserInfo{UID: 1234, Name: "test-user"}) + require.ErrorIs(t, err, userslocking.ErrLock) +} + +func TestUpdateUserAfterUnlock(t *testing.T) { + // This cannot be parallel + + waitTime := testutils.MultipliedSleepDuration(750 * time.Millisecond) + lockCtx, lockCancel := context.WithTimeout(context.Background(), waitTime/2) + t.Cleanup(lockCancel) + + userslocking.Z_ForTests_OverrideLockingAsLockedExternally(t, lockCtx) + userslocking.Z_ForTests_SetMaxWaitTime(t, waitTime) + + t.Cleanup(func() { _ = userslocking.WriteRecUnlock() }) + + dbFile := "one_user_and_group" + dbDir := t.TempDir() + err := db.Z_ForTests_CreateDBFromYAML(filepath.Join("testdata", "db", dbFile+".db.yaml"), dbDir) + require.NoError(t, err, "Setup: could not create database from testdata") + + m := newManagerForTests(t, dbDir) + + err = m.UpdateUser(types.UserInfo{UID: 1234, Name: "some-user-test"}) + require.NoError(t, err, "UpdateUser should not fail") +} + func requireErrorAssertions(t *testing.T, gotErr, wantErrType error, wantErr bool) { t.Helper() From f5117c2ca6d4efb4e280ba40780ffc9f9f852394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 18 Jun 2025 05:50:59 +0200 Subject: [PATCH 0542/1670] users/tempentries/users: Remove temporary user records User records were used to keep track of new users while they were added to our DB, and so to NSS, to ensure that they were listed during the process and avoid that no other NSS lib could take their UID. However this is something that we don't need anymore when locking the pwd database while updating an user, in fact: - Once a free UID is found we can assume that such UID can't be stolen by anything else, since we're the ones owning the lock - During the process of updating an user, there's no much point of returning such user to NSS, since no other process can do anything with it anyways, and as per what said above there's no need to keep its UID slot occupied - Pre-checked users should be kept around as much as we can, to ensure that we don't have even a (tiny) window in which their UID disappears from NSS, but it's not needed to change their name earlier than that - During the UpdateUser() we have both a pwd lock and a logical lock, so there's no risk that two users are updated concurrently, and thus we don't have the risk of using the same UID for two newly-added users However, we still may want to avoid races with the pre-auth users IDs, since they may be registered concurrently while we're still in the process of registering an user or a group, so let's use a simpler and shared ID tracker to ensure we don't duplicate IDs across multiple elements --- internal/users/manager.go | 14 +- internal/users/manager_test.go | 13 +- internal/users/tempentries/preauth.go | 58 +++--- internal/users/tempentries/preauth_test.go | 6 +- internal/users/tempentries/tempentries.go | 171 +++++++++++------- .../users/tempentries/tempentries_test.go | 45 ++--- ...a_user_if_the_pre-auth_user_already_exists | 4 +- internal/users/tempentries/users.go | 147 ++++++--------- .../Successfully_get_temporary_user_by_ID | 2 +- 9 files changed, 215 insertions(+), 245 deletions(-) diff --git a/internal/users/manager.go b/internal/users/manager.go index 6c13a2e791..d829eb1c5a 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -125,9 +125,11 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { var uid uint32 - // Prevent a TOCTOU race condition between the check for existence in our database and the registration of the - // temporary user/group records. This does not prevent a race condition where a user is created by some other NSS - // source, but that is handled in the temporaryRecords.RegisterUser and temporaryRecords.RegisterGroup functions. + // Prevent a TOCTOU race condition between the check for existence in our + // database and the registration of the temporary user/group records. + // This does not prevent a race condition where a user is created by some + // other NSS source, but that is handled by locking the users database if + // this happens. m.updateUserMu.Lock() defer m.updateUserMu.Unlock() @@ -154,10 +156,8 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { return fmt.Errorf("user %q already exists on the system (but not in this authd instance)", u.Name) } - // The user does not exist, so we generate a unique UID for it. To avoid that a user with the same UID is - // created by some other NSS source, this also registers a temporary user in our NSS handler. We remove that - // temporary user before returning from this function, at which point the user is added to the database (so we - // don't need the temporary user anymore to keep the UID unique). + // The user does not exist, so we generate a unique UID for it or + // we reuse the one that has been already pre-registered. var cleanup func() uid, cleanup, err = m.temporaryRecords.RegisterUser(u.Name) if err != nil { diff --git a/internal/users/manager_test.go b/internal/users/manager_test.go index 84eb5f3415..1fd54588c5 100644 --- a/internal/users/manager_test.go +++ b/internal/users/manager_test.go @@ -4,6 +4,7 @@ import ( "context" "os" "path/filepath" + "strings" "testing" "time" @@ -16,6 +17,7 @@ import ( "github.com/ubuntu/authd/internal/users/idgenerator" localgroupstestutils "github.com/ubuntu/authd/internal/users/localentries/testutils" userslocking "github.com/ubuntu/authd/internal/users/locking" + "github.com/ubuntu/authd/internal/users/tempentries" userstestutils "github.com/ubuntu/authd/internal/users/testutils" "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/authd/log" @@ -345,10 +347,9 @@ func TestUserByIDAndName(t *testing.T) { wantErr bool wantErrType error }{ - "Successfully_get_user_by_ID": {uid: 1111, dbFile: "multiple_users_and_groups"}, - "Successfully_get_user_by_name": {username: "user1", dbFile: "multiple_users_and_groups"}, - "Successfully_get_temporary_user_by_ID": {dbFile: "multiple_users_and_groups", isTempUser: true}, - "Successfully_get_temporary_user_by_name": {username: "tempuser1", dbFile: "multiple_users_and_groups", isTempUser: true}, + "Successfully_get_user_by_ID": {uid: 1111, dbFile: "multiple_users_and_groups"}, + "Successfully_get_user_by_name": {username: "user1", dbFile: "multiple_users_and_groups"}, + "Successfully_get_temporary_user_by_ID": {dbFile: "multiple_users_and_groups", isTempUser: true}, "Error_if_user_does_not_exist_-_by_ID": {uid: 0, dbFile: "multiple_users_and_groups", wantErrType: db.NoDataFoundError{}}, "Error_if_user_does_not_exist_-_by_name": {username: "doesnotexist", dbFile: "multiple_users_and_groups", wantErrType: db.NoDataFoundError{}}, @@ -364,7 +365,7 @@ func TestUserByIDAndName(t *testing.T) { m := newManagerForTests(t, dbDir) if tc.isTempUser { - tc.uid, _, err = m.TemporaryRecords().RegisterUser("tempuser1") + tc.uid, err = m.TemporaryRecords().RegisterPreAuthUser("tempuser1") require.NoError(t, err, "RegisterUser should not return an error, but did") } @@ -383,6 +384,8 @@ func TestUserByIDAndName(t *testing.T) { // Registering a temporary user creates it with a random UID, GID, and gecos, so we have to make it // deterministic before comparing it with the golden file if tc.isTempUser { + require.True(t, strings.HasPrefix(user.Name, tempentries.UserPrefix)) + user.Name = tempentries.UserPrefix + "{{random-suffix}}" require.Equal(t, tc.uid, user.UID) user.UID = 0 require.Equal(t, tc.uid, user.GID) diff --git a/internal/users/tempentries/preauth.go b/internal/users/tempentries/preauth.go index e1e6dd5591..e11386bd3d 100644 --- a/internal/users/tempentries/preauth.go +++ b/internal/users/tempentries/preauth.go @@ -37,7 +37,6 @@ type preAuthUser struct { type preAuthUserRecords struct { idGenerator IDGenerator - registerMu sync.Mutex rwMu sync.RWMutex users map[uint32]preAuthUser uidByName map[string]uint32 @@ -47,8 +46,6 @@ type preAuthUserRecords struct { func newPreAuthUserRecords(idGenerator IDGenerator) *preAuthUserRecords { return &preAuthUserRecords{ idGenerator: idGenerator, - registerMu: sync.Mutex{}, - rwMu: sync.RWMutex{}, users: make(map[uint32]preAuthUser), uidByName: make(map[string]uint32), uidByLogin: make(map[string]uint32), @@ -105,25 +102,12 @@ func preAuthUserEntry(user preAuthUser) types.UserEntry { } } -// RegisterPreAuthUser registers a temporary user with a unique UID in our NSS handler (in memory, not in the database). -// -// The temporary user record is removed when UpdateUser is called with the same username. -// -// This method is called when a user logs in for the first time via SSH, in which case sshd checks if the user exists on -// the system (before authentication), and denies the login if the user does not exist. We pretend that the user exists -// by creating this temporary user record, which is converted into a permanent user record when UpdateUser is called -// after the user authenticated successfully. -// -// Returns the generated UID. -func (r *preAuthUserRecords) RegisterPreAuthUser(loginName string) (uid uint32, err error) { +func (r *preAuthUserRecords) generatePreAuthUserID(loginName string) (uid uint32, err error) { // To mitigate DoS attacks, we limit the length of the name to 256 characters. if len(loginName) > 256 { return 0, errors.New("username is too long (max 256 characters)") } - r.registerMu.Lock() - defer r.registerMu.Unlock() - if err = func() error { r.rwMu.RLock() defer r.rwMu.RUnlock() @@ -136,16 +120,6 @@ func (r *preAuthUserRecords) RegisterPreAuthUser(loginName string) (uid uint32, return 0, err } - // Check if there is already a pre-auth user for that name - user, err := r.userByLogin(loginName) - if err != nil && !errors.Is(err, NoDataFoundError{}) { - return 0, fmt.Errorf("could not check if pre-auth user %q already exists: %w", loginName, err) - } - if err == nil { - // A pre-auth user is already registered for this name, so we return the already generated UID. - return user.UID, nil - } - passwdEntries, err := localentries.GetPasswdEntries() if err != nil { return 0, fmt.Errorf("could not check user, failed to get passwd entries: %w", err) @@ -179,15 +153,6 @@ func (r *preAuthUserRecords) RegisterPreAuthUser(loginName string) (uid uint32, continue } - // To avoid races where a user with this UID is created by some NSS source - // until the user actually logs in, we register this UID in our NSS handler. - if err := r.addPreAuthUser(uid, loginName); err != nil { - return 0, fmt.Errorf("could not add pre-auth user record: %w", err) - } - - log.Debugf(context.Background(), - "Added temporary record for user %q with UID %d", loginName, uid) - return uid, nil } } @@ -228,6 +193,24 @@ func (r *preAuthUserRecords) addPreAuthUser(uid uint32, loginName string) (err e r.rwMu.Lock() defer r.rwMu.Unlock() + if currentUID, ok := r.users[uid]; ok { + if currentUID.uid == uid { + r.uidByLogin[loginName] = uid + log.Debugf(context.Background(), + "Pre-auth user %q with UID %d is already registered", loginName, uid) + return nil + } + // This is really a programmer error if we get there, so... Let's just avoid it. + return fmt.Errorf("An user with ID %d already exists, this should never happen", uid) + } + + if currentUID, ok := r.uidByLogin[loginName]; ok && currentUID != uid { + log.Warningf(context.Background(), + "An temporary user entry for %q already exists as %d, impossible to register user", + loginName, uid) + return fmt.Errorf("failed to register again %q as %d", loginName, uid) + } + // Generate a 64 character (32 bytes in hex) random ID which we store in the name field of the temporary user // record to be able to identify it in isUniqueUID. bytes := make([]byte, 32) @@ -236,6 +219,9 @@ func (r *preAuthUserRecords) addPreAuthUser(uid uint32, loginName string) (err e } name := fmt.Sprintf("%s-%x", UserPrefix, bytes) + log.Debugf(context.Background(), + "Added temporary record for user %q with UID %d as %q", loginName, uid, name) + user := preAuthUser{name: name, uid: uid, loginName: loginName} r.users[uid] = user r.uidByName[name] = uid diff --git a/internal/users/tempentries/preauth_test.go b/internal/users/tempentries/preauth_test.go index 9491fe03d0..9add6e019a 100644 --- a/internal/users/tempentries/preauth_test.go +++ b/internal/users/tempentries/preauth_test.go @@ -67,7 +67,7 @@ func TestPreAuthUser(t *testing.T) { t.Log("UIDs to generate", tc.uidsToGenerate) idGeneratorMock := &idgenerator.IDGeneratorMock{UIDsToGenerate: tc.uidsToGenerate} - records := newPreAuthUserRecords(idGeneratorMock) + records := NewTemporaryRecords(idGeneratorMock) if tc.maxUsers { records.users = make(map[uint32]preAuthUser, MaxPreAuthUsers) @@ -166,11 +166,11 @@ func TestPreAuthUserByIDAndName(t *testing.T) { t.Parallel() idGeneratorMock := &idgenerator.IDGeneratorMock{UIDsToGenerate: []uint32{uidToGenerate}} - records := newPreAuthUserRecords(idGeneratorMock) + records := NewTemporaryRecords(idGeneratorMock) if tc.registerUser { uid, err := records.RegisterPreAuthUser(loginName) - require.NoError(t, err, "RegisterPreAuthUser should not return an error, but did") + require.NoError(t, err, "generatePreAuthUserID should not return an error, but did") require.Equal(t, uidToGenerate, uid, "UID should be the one generated by the IDGenerator") } diff --git a/internal/users/tempentries/tempentries.go b/internal/users/tempentries/tempentries.go index e0e9f3e6b0..1dee0883d6 100644 --- a/internal/users/tempentries/tempentries.go +++ b/internal/users/tempentries/tempentries.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/ubuntu/authd/internal/users/db" + "github.com/ubuntu/authd/internal/users/localentries" "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/authd/log" ) @@ -22,7 +23,7 @@ type IDGenerator interface { // TemporaryRecords is the in-memory temporary user and group records. type TemporaryRecords struct { - *temporaryUserRecords + *idTracker *preAuthUserRecords *temporaryGroupRecords @@ -33,7 +34,7 @@ type TemporaryRecords struct { func NewTemporaryRecords(idGenerator IDGenerator) *TemporaryRecords { return &TemporaryRecords{ idGenerator: idGenerator, - temporaryUserRecords: newTemporaryUserRecords(idGenerator), + idTracker: newIDTracker(), preAuthUserRecords: newPreAuthUserRecords(idGenerator), temporaryGroupRecords: newTemporaryGroupRecords(idGenerator), } @@ -41,37 +42,26 @@ func NewTemporaryRecords(idGenerator IDGenerator) *TemporaryRecords { // UserByID returns the user information for the given user ID. func (r *TemporaryRecords) UserByID(uid uint32) (types.UserEntry, error) { - user, err := r.temporaryUserRecords.userByID(uid) - if errors.Is(err, NoDataFoundError{}) { - user, err = r.preAuthUserRecords.userByID(uid) - } - return user, err + return r.preAuthUserRecords.userByID(uid) } // UserByName returns the user information for the given user name. func (r *TemporaryRecords) UserByName(name string) (types.UserEntry, error) { - user, err := r.temporaryUserRecords.userByName(name) - if errors.Is(err, NoDataFoundError{}) { - user, err = r.preAuthUserRecords.userByName(name) - } - return user, err + return r.preAuthUserRecords.userByName(name) } // RegisterUser registers a temporary user with a unique UID in our NSS handler (in memory, not in the database). // -// Returns the generated UID and a cleanup function that should be called to remove the temporary user once the user was -// added to the database. +// Returns the generated UID and a cleanup function that should be called to +// remove the temporary user once the user is added to the database. func (r *TemporaryRecords) RegisterUser(name string) (uid uint32, cleanup func(), err error) { - r.temporaryUserRecords.registerMu.Lock() - defer r.temporaryUserRecords.registerMu.Unlock() - - // Check if there is a temporary user with the same login name. - _, err = r.temporaryUserRecords.userByName(name) - if err != nil && !errors.Is(err, NoDataFoundError{}) { - return 0, nil, fmt.Errorf("could not check if temporary user %q already exists: %w", name, err) + passwdEntries, err := localentries.GetPasswdEntries() + if err != nil { + return 0, nil, fmt.Errorf("could not check user, failed to get passwd entries: %w", err) } - if err == nil { - return 0, nil, fmt.Errorf("user %q already exists", name) + groupEntries, err := localentries.GetGroupEntries() + if err != nil { + return 0, nil, fmt.Errorf("could not check user, failed to get group entries: %w", err) } // Check if there is a pre-auth user with the same login name. @@ -80,9 +70,32 @@ func (r *TemporaryRecords) RegisterUser(name string) (uid uint32, cleanup func() return 0, nil, fmt.Errorf("could not check if pre-auth user %q already exists: %w", name, err) } if err == nil { - // There is a pre-auth user with the same login name. Now that the user authenticated successfully, we can - // replace the pre-auth user with a temporary user. - return r.replacePreAuthUser(user, name) + // There is a pre-auth user with the same login name. + + // Remove the pre-checked user as last thing, so that the user UID is exposed as the + // ones we manage in authd while we're updating the users, although there's no risk + // that someone else takes the UID here, since we're locked. + cleanup := func() { + r.deletePreAuthUser(user.UID) + r.forgetID(user.UID) + r.idTracker.forgetUser(name) + } + + // Now that the user authenticated successfully, we don't really need to check again + // if the UID is unique, since that's something we did while registering it, and we're + // currently locked, so nothing else can add another user with such ID, but we do to + // ensure that there is not another user with the same name. + unique, err := uniqueNameAndUID(name, user.UID, passwdEntries, groupEntries) + if err != nil { + cleanup() + return 0, nil, fmt.Errorf("checking UID and name uniqueness: %w", err) + } + if !unique { + cleanup() + return 0, nil, fmt.Errorf("UID (%d) or name (%q) from pre-auth user are not unique", user.UID, name) + } + + return user.UID, cleanup, nil } // Generate a UID until we find a unique one @@ -92,57 +105,83 @@ func (r *TemporaryRecords) RegisterUser(name string) (uid uint32, cleanup func() return 0, nil, err } - // To avoid races where a user with this UID is created by some NSS source after we checked, we register this - // UID in our NSS handler and then check if another user with the same UID exists in the system. This way we - // can guarantee that the UID is unique, under the assumption that other NSS sources don't add users with a UID - // that we already registered (if they do, there's nothing we can do about it). - var tmpID string - tmpID, cleanup, err = r.temporaryUserRecords.addTemporaryUser(uid, name) + unique, err := uniqueNameAndUID(name, uid, passwdEntries, groupEntries) if err != nil { - return 0, nil, fmt.Errorf("could not add temporary user record: %w", err) + return 0, nil, fmt.Errorf("checking UID and name uniqueness: %w", err) + } + if !unique { + // If the UID is not unique, generate a new one in the next iteration. + continue } - unique, err := r.temporaryUserRecords.uniqueNameAndUID(name, uid, tmpID) - if err != nil { - err = fmt.Errorf("checking UID and name uniqueness: %w", err) - cleanup() - return 0, nil, err + if !r.idTracker.trackID(uid) { + // If the UID is not unique, generate a new one in the next iteration. + continue } - if unique { - break + + tracked, currentUID := r.idTracker.trackUser(name, uid) + if !tracked { + // If the loginName is already set for a different UID, it means + // that another concurrent request won the race, so let's just + // use that one instead. + r.idTracker.forgetID(uid) + uid = currentUID } - // If the UID is not unique, remove the temporary user and generate a new one in the next iteration. - cleanup() + log.Debugf(context.Background(), "Generated UID %d for user UID %s", uid, name) + return uid, func() { r.idTracker.forgetID(uid); r.idTracker.forgetUser(name) }, nil } - - log.Debugf(context.Background(), "Added temporary record for user %q with UID %d", name, uid) - return uid, cleanup, nil } -// replacePreAuthUser replaces a pre-auth user with a temporary user with the same name and UID. -func (r *TemporaryRecords) replacePreAuthUser(user types.UserEntry, name string) (uid uint32, cleanup func(), err error) { - var tmpID string - tmpID, cleanup, err = r.addTemporaryUser(user.UID, name) - if err != nil { - return 0, nil, fmt.Errorf("could not add temporary user record: %w", err) +// RegisterPreAuthUser registers a temporary user with a unique UID in our NSS handler (in memory, not in the database). +// +// The temporary user record is removed when UpdateUser is called with the same username. +// +// This method is called when a user logs in for the first time via SSH, in which case sshd checks if the user exists on +// the system (before authentication), and denies the login if the user does not exist. We pretend that the user exists +// by creating this temporary user record, which is converted into a permanent user record when UpdateUser is called +// after the user authenticated successfully. +// +// Returns the generated UID. +func (r *TemporaryRecords) RegisterPreAuthUser(loginName string) (uid uint32, err error) { + // Check if there is already a pre-auth user for that name + user, err := r.preAuthUserRecords.userByLogin(loginName) + if err != nil && !errors.Is(err, NoDataFoundError{}) { + return 0, fmt.Errorf("could not check if pre-auth user %q already exists: %w", + loginName, err) + } + if err == nil { + // A pre-auth user is already registered for this name, so we return the + // already generated UID. + return user.UID, nil } - // Remove the pre-auth user from the pre-auth user records. - r.deletePreAuthUser(user.UID) + for { + uid, err := r.preAuthUserRecords.generatePreAuthUserID(loginName) + if err != nil { + return 0, err + } + + if !r.idTracker.trackID(uid) { + // If the UID is not unique, generate a new one in the next iteration. + continue + } - // Check if the UID and name are unique. - unique, err := r.temporaryUserRecords.uniqueNameAndUID(name, user.UID, tmpID) - if err != nil { - err = fmt.Errorf("checking UID and name uniqueness: %w", err) - cleanup() - return 0, nil, err - } - if !unique { - err = fmt.Errorf("UID (%d) or name (%q) from pre-auth user are not unique", user.UID, name) - cleanup() - return 0, nil, err - } + tracked, currentUID := r.idTracker.trackUser(loginName, uid) + if !tracked { + // If the loginName is already set for a different UID, it means + // that another concurrent request won the race, so let's just + // use that one instead. + r.idTracker.forgetID(uid) + uid = currentUID + } - return user.UID, cleanup, nil + if err := r.addPreAuthUser(uid, loginName); err != nil { + r.idTracker.forgetID(uid) + r.idTracker.forgetUser(loginName) + return 0, fmt.Errorf("could not add pre-auth user record: %w", err) + } + + return uid, nil + } } diff --git a/internal/users/tempentries/tempentries_test.go b/internal/users/tempentries/tempentries_test.go index c477492e9d..86884b7bbf 100644 --- a/internal/users/tempentries/tempentries_test.go +++ b/internal/users/tempentries/tempentries_test.go @@ -76,20 +76,20 @@ func TestRegisterUser(t *testing.T) { require.Error(t, err, "RegisterUser should return an error, but did not") return } + t.Cleanup(cleanup) require.NoError(t, err, "RegisterUser should not return an error, but did") require.Equal(t, uidToGenerate, uid, "UID should be the one generated by the IDGenerator") if tc.replacesPreAuthUser { // Check that the pre-auth user was removed - _, err = records.preAuthUserRecords.userByID(preAuthUID) - require.Error(t, err, "userByID should return an error, but did not") + user, err := records.preAuthUserRecords.userByID(preAuthUID) + require.NoError(t, err, "userByID should not return an error, but it not") + checkPreAuthUser(t, user) + return } - user, err := records.UserByID(uid) - require.NoError(t, err, "UserByID should not return an error, but did") - checkUser(t, user) - - cleanup() + _, err = records.UserByID(uid) + require.Error(t, err, "userByID should return an error, but did not") }) } } @@ -107,21 +107,16 @@ func TestUserByIDAndName(t *testing.T) { wantErr bool }{ - "Successfully_get_a_user_by_ID": {registerUser: true}, - "Successfully_get_a_user_by_name": {registerUser: true, byName: true}, - "Error_when_user_is_not_registered_-_UserByID": {wantErr: true}, "Error_when_user_is_not_registered_-_UserByName": {byName: true, wantErr: true}, "Error_when_user_is_already_removed_-_UserByID": { - registerUser: true, - userAlreadyRemoved: true, - wantErr: true, + registerUser: true, + wantErr: true, }, "Error_when_user_is_already_removed_-_UserByName": { - registerUser: true, - userAlreadyRemoved: true, - byName: true, - wantErr: true, + registerUser: true, + byName: true, + wantErr: true, }, } @@ -131,26 +126,20 @@ func TestUserByIDAndName(t *testing.T) { idGeneratorMock := &idgenerator.IDGeneratorMock{UIDsToGenerate: []uint32{uidToGenerate}} records := NewTemporaryRecords(idGeneratorMock) - userRecords := records.temporaryUserRecords if tc.registerUser { uid, cleanup, err := records.RegisterUser(userName) require.NoError(t, err, "RegisterUser should not return an error, but did") require.Equal(t, uidToGenerate, uid, "UID should be the one generated by the IDGenerator") - - if tc.userAlreadyRemoved { - cleanup() - } else { - defer cleanup() - } + t.Cleanup(cleanup) } var user types.UserEntry var err error if tc.byName { - user, err = userRecords.userByName(userName) + user, err = records.userByName(userName) } else { - user, err = userRecords.userByID(uidToGenerate) + user, err = records.userByID(uidToGenerate) } if tc.wantErr { @@ -166,9 +155,5 @@ func TestUserByIDAndName(t *testing.T) { func checkUser(t *testing.T, user types.UserEntry) { t.Helper() - // The gecos field is randomly generated, so unset it before comparing the user with the golden file. - require.NotEmpty(t, user.Gecos, "Gecos should not be empty") - user.Gecos = "" - golden.CheckOrUpdateYAML(t, user) } diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUser/Successfully_register_a_user_if_the_pre-auth_user_already_exists b/internal/users/tempentries/testdata/golden/TestRegisterUser/Successfully_register_a_user_if_the_pre-auth_user_already_exists index c6f5874e44..6713d89aa7 100644 --- a/internal/users/tempentries/testdata/golden/TestRegisterUser/Successfully_register_a_user_if_the_pre-auth_user_already_exists +++ b/internal/users/tempentries/testdata/golden/TestRegisterUser/Successfully_register_a_user_if_the_pre-auth_user_already_exists @@ -1,6 +1,6 @@ -name: authd-temp-users-test +name: authd-pre-auth-user-XXXXXXXX uid: 12345 gid: 12345 -gecos: "" +gecos: authd-temp-users-test dir: /nonexistent shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/users.go b/internal/users/tempentries/users.go index cce8fba461..cb24266deb 100644 --- a/internal/users/tempentries/users.go +++ b/internal/users/tempentries/users.go @@ -2,102 +2,97 @@ package tempentries import ( "context" - "crypto/rand" "fmt" "sync" - "github.com/ubuntu/authd/internal/users/db" - "github.com/ubuntu/authd/internal/users/localentries" "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/authd/log" ) -type userRecord struct { - name string - uid uint32 - gecos string +type idTracker struct { + mu sync.Mutex + ids map[uint32]struct{} + userNames map[string]uint32 } -type temporaryUserRecords struct { - idGenerator IDGenerator - registerMu sync.Mutex - rwMu sync.RWMutex - users map[uint32]userRecord - uidByName map[string]uint32 +func newIDTracker() *idTracker { + return &idTracker{ + ids: make(map[uint32]struct{}), + userNames: make(map[string]uint32), + } } -func newTemporaryUserRecords(idGenerator IDGenerator) *temporaryUserRecords { - return &temporaryUserRecords{ - idGenerator: idGenerator, - registerMu: sync.Mutex{}, - rwMu: sync.RWMutex{}, - users: make(map[uint32]userRecord), - uidByName: make(map[string]uint32), +func (r *idTracker) trackID(id uint32) bool { + r.mu.Lock() + defer r.mu.Unlock() + + _, ok := r.ids[id] + if ok { + log.Debugf(context.Background(), "Not tracking ID %d, already tracked", id) + return false } + + log.Debugf(context.Background(), "Tracking ID %d", id) + r.ids[id] = struct{}{} + return true } -// UserByID returns the user information for the given user ID. -func (r *temporaryUserRecords) userByID(uid uint32) (types.UserEntry, error) { - r.rwMu.RLock() - defer r.rwMu.RUnlock() +func (r *idTracker) forgetID(id uint32) { + r.mu.Lock() + defer r.mu.Unlock() - user, ok := r.users[uid] - if !ok { - return types.UserEntry{}, db.NewUIDNotFoundError(uid) + if log.IsLevelEnabled(log.DebugLevel) { + _, ok := r.ids[id] + log.Debugf(context.Background(), + "Forgetting tracked ID %d: %v", id, ok) } - return userEntry(user), nil + delete(r.ids, id) } -// UserByName returns the user information for the given user name. -func (r *temporaryUserRecords) userByName(name string) (types.UserEntry, error) { - r.rwMu.RLock() - defer r.rwMu.RUnlock() +func (r *idTracker) trackUser(name string, id uint32) (tracked bool, currentID uint32) { + r.mu.Lock() + defer r.mu.Unlock() - uid, ok := r.uidByName[name] - if !ok { - return types.UserEntry{}, db.NewUserNotFoundError(name) + if currentID, ok := r.userNames[name]; ok { + log.Debugf(context.Background(), + "Not tracking user name %q for UID %d, already tracked as %d", name, id, currentID) + return currentID == id, currentID } - return r.userByID(uid) + log.Debugf(context.Background(), "Tracking user name %q for UID %d", name, id) + r.userNames[name] = id + return true, id } -func userEntry(user userRecord) types.UserEntry { - return types.UserEntry{ - Name: user.name, - UID: user.uid, - // The UID is also the GID of the user private group (see https://wiki.debian.org/UserPrivateGroups#UPGs) - GID: user.uid, - Gecos: user.gecos, - Dir: "/nonexistent", - Shell: "/usr/sbin/nologin", +func (r *idTracker) forgetUser(name string) { + r.mu.Lock() + defer r.mu.Unlock() + + if log.IsLevelEnabled(log.DebugLevel) { + id, ok := r.userNames[name] + log.Debugf(context.Background(), + "Forgetting tracked user name %q for UID %d: %v", name, id, ok) } + delete(r.userNames, name) } // uniqueNameAndUID returns true if the given UID is unique in the system. It returns false if the UID is already assigned to -// a user by any NSS source (except the given temporary user). -func (r *temporaryUserRecords) uniqueNameAndUID(name string, uid uint32, tmpID string) (bool, error) { - entries, err := localentries.GetPasswdEntries() - if err != nil { - return false, err - } - for _, entry := range entries { +// a user by any NSS source. +func uniqueNameAndUID(name string, uid uint32, passwdEntries []types.UserEntry, groupEntries []types.GroupEntry) (bool, error) { + for _, entry := range passwdEntries { if entry.Name == name && entry.UID != uid { // A user with the same name already exists, we can't register this temporary user. log.Debugf(context.Background(), "Name %q already in use by UID %d", name, entry.UID) return false, fmt.Errorf("user %q already exists", name) } - if entry.UID == uid && entry.Gecos != tmpID { + if entry.UID == uid { log.Debugf(context.Background(), "UID %d already in use by user %q, generating a new one", uid, entry.Name) return false, nil } } - groupEntries, err := localentries.GetGroupEntries() - if err != nil { - return false, fmt.Errorf("failed to get group entries: %w", err) - } for _, group := range groupEntries { if group.GID == uid { // A group with the same ID already exists, so we can't use that ID as the GID of the temporary user. @@ -108,41 +103,3 @@ func (r *temporaryUserRecords) uniqueNameAndUID(name string, uid uint32, tmpID s return true, nil } - -// addTemporaryUser adds a temporary user with a random name and the given UID. It returns the generated name. -// If the UID is already registered, it returns a errUserAlreadyExists. -func (r *temporaryUserRecords) addTemporaryUser(uid uint32, name string) (tmpID string, cleanup func(), err error) { - r.rwMu.Lock() - defer r.rwMu.Unlock() - - // Generate a 64 character (32 bytes in hex) random ID which we store in the gecos field of the temporary user - // record to be able to identify it in isUniqueUID. - bytes := make([]byte, 32) - if _, err := rand.Read(bytes); err != nil { - return "", nil, fmt.Errorf("failed to generate random name: %w", err) - } - tmpID = fmt.Sprintf("authd-temp-user-%x", bytes) - - r.users[uid] = userRecord{name: name, uid: uid, gecos: tmpID} - r.uidByName[name] = uid - - cleanup = func() { r.deleteTemporaryUser(uid) } - - return tmpID, cleanup, nil -} - -// deleteTemporaryUser deletes the temporary user with the given UID. -func (r *temporaryUserRecords) deleteTemporaryUser(uid uint32) { - r.rwMu.Lock() - defer r.rwMu.Unlock() - - user, ok := r.users[uid] - if !ok { - log.Warningf(context.Background(), "Can't delete temporary user with UID %d, it does not exist", uid) - return - } - delete(r.users, uid) - delete(r.uidByName, user.name) - - log.Debugf(context.Background(), "Removed temporary record for user %q with UID %d", user.name, uid) -} diff --git a/internal/users/testdata/golden/TestUserByIDAndName/Successfully_get_temporary_user_by_ID b/internal/users/testdata/golden/TestUserByIDAndName/Successfully_get_temporary_user_by_ID index 8c4eed4c48..c8c1f6e51b 100644 --- a/internal/users/testdata/golden/TestUserByIDAndName/Successfully_get_temporary_user_by_ID +++ b/internal/users/testdata/golden/TestUserByIDAndName/Successfully_get_temporary_user_by_ID @@ -1,4 +1,4 @@ -name: tempuser1 +name: authd-pre-auth-user{{random-suffix}} uid: 0 gid: 0 gecos: "" From af449409890d2322513fed69bcb0dea567defe83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 18 Jun 2025 07:41:35 +0200 Subject: [PATCH 0543/1670] users/tempentries/groups: Prepare for removal of temporary groups from NSS We don't need to register groups anymore to expose them to NSS, for the same reason why we don't have to do it with users. However since an users may have multiple groups we need to still protect ourself from reusing the same GID for two different groups, and so we need to ensure that the gid generation won't override what we've already saved --- internal/users/manager.go | 28 ++++++++--- internal/users/manager_test.go | 7 +-- internal/users/tempentries/groups.go | 60 +++++++---------------- internal/users/tempentries/groups_test.go | 4 +- 4 files changed, 45 insertions(+), 54 deletions(-) diff --git a/internal/users/manager.go b/internal/users/manager.go index d829eb1c5a..7ce9ddd85e 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -174,6 +174,7 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { var groupRows []db.GroupRow var localGroups []string + var newGroups []types.GroupInfo for _, g := range u.Groups { if g.Name == "" { return fmt.Errorf("empty group name for user %q", u.Name) @@ -207,20 +208,33 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { } if g.GID == nil { - // The group does not exist in the database, so we generate a unique GID for it. Similar to the RegisterUser - // call above, this also registers a temporary group in our NSS handler. We remove that temporary group - // before returning from this function, at which point the group is added to the database (so we don't need - // the temporary group anymore to keep the GID unique). + // The group does not exist in the database, so we generate a unique GID for it. + newGroups = append(newGroups, g) + continue + } + + groupRows = append(groupRows, db.NewGroupRow(g.Name, *g.GID, g.UGID)) + } + + if len(newGroups) > 0 { + if err := userslocking.WriteRecLock(); err != nil { + return err + } + defer func() { + if unlockErr := userslocking.WriteRecUnlock(); unlockErr != nil { + err = errors.Join(err, unlockErr) + } + }() + + for _, g := range newGroups { gid, cleanup, err := m.temporaryRecords.RegisterGroup(g.Name) if err != nil { return fmt.Errorf("could not generate GID for group %q: %v", g.Name, err) } defer cleanup() - g.GID = &gid + groupRows = append(groupRows, db.NewGroupRow(g.Name, gid, g.UGID)) } - - groupRows = append(groupRows, db.NewGroupRow(g.Name, *g.GID, g.UGID)) } oldLocalGroups, err := m.db.UserLocalGroups(uid) diff --git a/internal/users/manager_test.go b/internal/users/manager_test.go index 1fd54588c5..3748f02527 100644 --- a/internal/users/manager_test.go +++ b/internal/users/manager_test.go @@ -461,8 +461,10 @@ func TestGroupByIDAndName(t *testing.T) { m := newManagerForTests(t, dbDir) if tc.isTempGroup { - tc.gid, _, err = m.TemporaryRecords().RegisterGroup("tempgroup1") + var cleanup func() + tc.gid, cleanup, err = m.TemporaryRecords().RegisterGroup("tempgroup1") require.NoError(t, err, "RegisterGroup should not return an error, but did") + t.Cleanup(cleanup) } var group types.GroupEntry @@ -482,8 +484,7 @@ func TestGroupByIDAndName(t *testing.T) { if tc.isTempGroup { require.Equal(t, tc.gid, group.GID) group.GID = 0 - require.NotEmpty(t, group.Passwd) - group.Passwd = "" + require.Empty(t, group.Passwd) } golden.CheckOrUpdateYAML(t, group) diff --git a/internal/users/tempentries/groups.go b/internal/users/tempentries/groups.go index 9837931ec7..ddfb70bee2 100644 --- a/internal/users/tempentries/groups.go +++ b/internal/users/tempentries/groups.go @@ -2,7 +2,6 @@ package tempentries import ( "context" - "crypto/rand" "errors" "fmt" "sync" @@ -84,6 +83,11 @@ func (r *temporaryGroupRecords) RegisterGroup(name string) (gid uint32, cleanup return 0, nil, fmt.Errorf("group %q already exists", name) } + groupEntries, err := localentries.GetGroupEntries() + if err != nil { + return 0, nil, fmt.Errorf("could not register group, failed to get group entries: %w", err) + } + // Generate a GID until we find a unique one for { gid, err = r.idGenerator.GenerateGID() @@ -91,46 +95,30 @@ func (r *temporaryGroupRecords) RegisterGroup(name string) (gid uint32, cleanup return 0, nil, err } - // To avoid races where a group with this GID is created by some NSS source after we checked, we register this - // GID in our NSS handler and then check if another group with the same GID exists in the system. This way we - // can guarantee that the GID is unique, under the assumption that other NSS sources don't add groups with a GID - // that we already registered (if they do, there's nothing we can do about it). - var tmpID string - tmpID, cleanup, err = r.addTemporaryGroup(gid, name) - if err != nil { - return 0, nil, fmt.Errorf("could not register temporary group: %w", err) - } - - unique, err := r.uniqueNameAndGID(name, gid, tmpID) + unique, err := r.uniqueNameAndGID(name, gid, groupEntries) if err != nil { - cleanup() return 0, nil, fmt.Errorf("could not check if GID %d is unique: %w", gid, err) } - if unique { - break + if !unique { + // If the GID is not unique, generate a new one in the next iteration. + continue } - // If the GID is not unique, remove the temporary group and generate a new one in the next iteration. - cleanup() + cleanup = r.addTemporaryGroup(gid, name) + log.Debugf(context.Background(), "Registered group %q with GID %d", name, gid) + return gid, cleanup, nil } - - log.Debugf(context.Background(), "Registered group %q with GID %d", name, gid) - return gid, cleanup, nil } -func (r *temporaryGroupRecords) uniqueNameAndGID(name string, gid uint32, tmpID string) (bool, error) { - entries, err := localentries.GetGroupEntries() - if err != nil { - return false, err - } - for _, entry := range entries { - if entry.Name == name && entry.Passwd != tmpID { +func (r *temporaryGroupRecords) uniqueNameAndGID(name string, gid uint32, groupEntries []types.GroupEntry) (bool, error) { + for _, entry := range groupEntries { + if entry.Name == name { // A group with the same name already exists, we can't register this temporary group. log.Debugf(context.Background(), "Name %q already in use by GID %d", name, entry.GID) return false, fmt.Errorf("group %q already exists", name) } - if entry.GID == gid && entry.Passwd != tmpID { + if entry.GID == gid { log.Debugf(context.Background(), "GID %d already in use by group %q, generating a new one", gid, entry.Name) return false, nil } @@ -139,24 +127,14 @@ func (r *temporaryGroupRecords) uniqueNameAndGID(name string, gid uint32, tmpID return true, nil } -func (r *temporaryGroupRecords) addTemporaryGroup(gid uint32, name string) (tmpID string, cleanup func(), err error) { +func (r *temporaryGroupRecords) addTemporaryGroup(gid uint32, name string) (cleanup func()) { r.rwMu.Lock() defer r.rwMu.Unlock() - // Generate a 64 character (32 bytes in hex) random ID which we store in the passwd field of the temporary group - // record to be able to identify it in isUniqueGID. - bytes := make([]byte, 32) - if _, err := rand.Read(bytes); err != nil { - return "", nil, fmt.Errorf("failed to generate random name: %w", err) - } - tmpID = fmt.Sprintf("authd-temp-group-%x", bytes) - - r.groups[gid] = groupRecord{name: name, gid: gid, passwd: tmpID} + r.groups[gid] = groupRecord{name: name, gid: gid} r.gidByName[name] = gid - cleanup = func() { r.deleteTemporaryGroup(gid) } - - return tmpID, cleanup, nil + return func() { r.deleteTemporaryGroup(gid) } } func (r *temporaryGroupRecords) deleteTemporaryGroup(gid uint32) { diff --git a/internal/users/tempentries/groups_test.go b/internal/users/tempentries/groups_test.go index 78e3a5b0de..7f9845cdcd 100644 --- a/internal/users/tempentries/groups_test.go +++ b/internal/users/tempentries/groups_test.go @@ -135,9 +135,7 @@ func TestGroupByIDAndName(t *testing.T) { func checkGroup(t *testing.T, group types.GroupEntry) { t.Helper() - // The passwd field is randomly generated, so unset it before comparing the group with the golden file. - require.NotEmpty(t, group.Passwd, "Passwd should not be empty") - group.Passwd = "" + require.Empty(t, group.Passwd, "Passwd should be empty") golden.CheckOrUpdateYAML(t, group) } From 73afb3d80016f8be51f6078850af3b24470e791a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 18 Jun 2025 16:47:38 +0200 Subject: [PATCH 0544/1670] users/tempentries/groups-test: Allow to register multiple groups As we do with the preauth entries now we can simulate the test where multiple items are registered --- internal/users/tempentries/groups_test.go | 105 +++++++++++++++++----- 1 file changed, 83 insertions(+), 22 deletions(-) diff --git a/internal/users/tempentries/groups_test.go b/internal/users/tempentries/groups_test.go index 7f9845cdcd..9ef244fdc4 100644 --- a/internal/users/tempentries/groups_test.go +++ b/internal/users/tempentries/groups_test.go @@ -1,6 +1,7 @@ package tempentries import ( + "slices" "testing" "github.com/stretchr/testify/require" @@ -12,54 +13,114 @@ import ( func TestRegisterGroup(t *testing.T) { t.Parallel() + defaultGroupName := "authd-temp-groups-test" gidToGenerate := uint32(12345) tests := map[string]struct { - groupName string + groups []string gidsToGenerate []uint32 - wantErr bool + wantErr []bool + wantGIDs []uint32 }{ "Successfully_register_a_new_group": {}, "Successfully_register_a_group_if_the_first_generated_GID_is_already_in_use": { gidsToGenerate: []uint32{0, gidToGenerate}, // GID 0 (root) always exists + wantGIDs: []uint32{gidToGenerate}, }, - "Error_when_name_is_already_in_use": {groupName: "root", wantErr: true}, + "Error_when_name_is_already_in_use": {groups: []string{"root"}, wantErr: []bool{true}}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { t.Parallel() - if tc.groupName == "" { - tc.groupName = "authd-temp-groups-test" + if len(tc.groups) == 0 { + tc.groups = append(tc.groups, defaultGroupName) } if tc.gidsToGenerate == nil { - tc.gidsToGenerate = []uint32{gidToGenerate} + gid := gidToGenerate + for range tc.groups { + tc.gidsToGenerate = append(tc.gidsToGenerate, gid) + gid++ + } + } + if tc.wantGIDs == nil { + tc.wantGIDs = tc.gidsToGenerate + } + if tc.wantErr == nil { + tc.wantErr = make([]bool, len(tc.groups)) } + wantRegistered := 0 + var registeredGIDs []uint32 + cleanupFunctions := map[uint32]func(){} + + t.Log("GIDs to generate", tc.gidsToGenerate) idGeneratorMock := &idgenerator.IDGeneratorMock{GIDsToGenerate: tc.gidsToGenerate} records := newTemporaryGroupRecords(idGeneratorMock) - gid, cleanup, err := records.RegisterGroup(tc.groupName) - if tc.wantErr { - require.Error(t, err, "RegisterGroup should return an error, but did not") + for idx, groupName := range tc.groups { + t.Logf("Registering group %q", groupName) + gid, cleanup, err := records.RegisterGroup(groupName) + if tc.wantErr[idx] { + require.Error(t, err, "RegisterGroup should return an error, but did not") + continue + } + + require.NoError(t, err, "RegisterGroup should not return an error, but did") + t.Cleanup(cleanup) + + isDuplicated := slices.Contains(tc.groups[0:idx], groupName) + if !isDuplicated { + wantRegistered++ + } + + if isDuplicated { + require.Contains(t, registeredGIDs, gid, "GID %d has been already registered!", gid) + } else { + require.NotContains(t, registeredGIDs, gid, "GID %d has not been already registered!", gid) + } + + wantGID := tc.wantGIDs[wantRegistered-1] + registeredGIDs = append(registeredGIDs, gid) + cleanupFunctions[gid] = cleanup + + require.NoError(t, err, "RegisterGroup should not return an error, but did") + require.Equal(t, wantGID, gid, "GID should be the one generated by the IDGenerator") + require.Equal(t, wantRegistered, len(records.groups), + "Number of groups registered, users should be %d", wantRegistered) + + // Check that the temporary group was created + group, err := records.GroupByID(gid) + require.NoError(t, err, "GroupByID should not return an error, but did") + + var goldenOptions []golden.Option + if idx > 0 { + goldenOptions = append(goldenOptions, golden.WithSuffix("_"+groupName)) + } + checkGroup(t, group, goldenOptions...) + } + + if wantRegistered == 0 { return } - require.NoError(t, err, "RegisterGroup should not return an error, but did") - require.Equal(t, gidToGenerate, gid, "GID should be the one generated by the IDGenerator") - // Check that the temporary group was created - group, err := records.GroupByID(gid) - require.NoError(t, err, "GroupByID should not return an error, but did") - checkGroup(t, group) - // Delete the temporary group - cleanup() + for idx, groupName := range tc.groups { + isDuplicated := slices.Contains(tc.groups[0:idx], groupName) + if !isDuplicated { + wantRegistered-- + } - // Check that the temporary group was deleted - _, err = records.GroupByID(gid) - require.Error(t, err, "GroupByID should return an error, but did not") + removeGID := registeredGIDs[len(registeredGIDs)-wantRegistered-1] + t.Logf("Removing group %q for GID %v", groupName, removeGID) + cleanupFunctions[removeGID]() + + // Check that the temporary group was deleted + _, err := records.GroupByID(removeGID) + require.Error(t, err, "GroupByID should return an error, but did not") + } }) } } @@ -132,10 +193,10 @@ func TestGroupByIDAndName(t *testing.T) { } } -func checkGroup(t *testing.T, group types.GroupEntry) { +func checkGroup(t *testing.T, group types.GroupEntry, options ...golden.Option) { t.Helper() require.Empty(t, group.Passwd, "Passwd should be empty") - golden.CheckOrUpdateYAML(t, group) + golden.CheckOrUpdateYAML(t, group, options...) } From 848c5892285f6f85fc0aa8f408b5c3d4343f2afb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 18 Jun 2025 16:57:22 +0200 Subject: [PATCH 0545/1670] users/tempentries/groups: Ensure we cannot create users with the same name --- internal/users/tempentries/groups_test.go | 4 ++++ .../Error_when_registering_a_group_with_the_same_name | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterGroup/Error_when_registering_a_group_with_the_same_name diff --git a/internal/users/tempentries/groups_test.go b/internal/users/tempentries/groups_test.go index 9ef244fdc4..dd544e07e1 100644 --- a/internal/users/tempentries/groups_test.go +++ b/internal/users/tempentries/groups_test.go @@ -30,6 +30,10 @@ func TestRegisterGroup(t *testing.T) { }, "Error_when_name_is_already_in_use": {groups: []string{"root"}, wantErr: []bool{true}}, + "Error_when_registering_a_group_with_the_same_name": { + groups: []string{defaultGroupName, defaultGroupName}, + wantErr: []bool{false, true}, + }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { diff --git a/internal/users/tempentries/testdata/golden/TestRegisterGroup/Error_when_registering_a_group_with_the_same_name b/internal/users/tempentries/testdata/golden/TestRegisterGroup/Error_when_registering_a_group_with_the_same_name new file mode 100644 index 0000000000..e992cfed83 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterGroup/Error_when_registering_a_group_with_the_same_name @@ -0,0 +1,4 @@ +name: authd-temp-groups-test +gid: 12345 +users: [] +passwd: "" From f165f92bbc7b60d7ea2359c1feb1986c1ae21fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 18 Jun 2025 17:00:53 +0200 Subject: [PATCH 0546/1670] users/tempentries/groups: Never allow registering a group for a known GID --- internal/users/tempentries/groups.go | 9 +++++++++ internal/users/tempentries/groups_test.go | 5 +++++ ...roup_if_the_first_generated_GID_is_already_registered | 4 ++++ ...first_generated_GID_is_already_registered_other-group | 4 ++++ 4 files changed, 22 insertions(+) create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_register_a_group_if_the_first_generated_GID_is_already_registered create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_register_a_group_if_the_first_generated_GID_is_already_registered_other-group diff --git a/internal/users/tempentries/groups.go b/internal/users/tempentries/groups.go index ddfb70bee2..a8a31a5aeb 100644 --- a/internal/users/tempentries/groups.go +++ b/internal/users/tempentries/groups.go @@ -111,6 +111,15 @@ func (r *temporaryGroupRecords) RegisterGroup(name string) (gid uint32, cleanup } func (r *temporaryGroupRecords) uniqueNameAndGID(name string, gid uint32, groupEntries []types.GroupEntry) (bool, error) { + r.rwMu.RLock() + defer r.rwMu.RUnlock() + + if _, ok := r.groups[gid]; ok { + return false, nil + } + + // FIXME: Also check here if the generated GID is not part of the pre-check users UIDs! + for _, entry := range groupEntries { if entry.Name == name { // A group with the same name already exists, we can't register this temporary group. diff --git a/internal/users/tempentries/groups_test.go b/internal/users/tempentries/groups_test.go index dd544e07e1..bfde5c43db 100644 --- a/internal/users/tempentries/groups_test.go +++ b/internal/users/tempentries/groups_test.go @@ -28,6 +28,11 @@ func TestRegisterGroup(t *testing.T) { gidsToGenerate: []uint32{0, gidToGenerate}, // GID 0 (root) always exists wantGIDs: []uint32{gidToGenerate}, }, + "Successfully_register_a_group_if_the_first_generated_GID_is_already_registered": { + groups: []string{defaultGroupName, "other-group"}, + gidsToGenerate: []uint32{gidToGenerate, gidToGenerate, gidToGenerate + 1}, + wantGIDs: []uint32{gidToGenerate, gidToGenerate + 1}, + }, "Error_when_name_is_already_in_use": {groups: []string{"root"}, wantErr: []bool{true}}, "Error_when_registering_a_group_with_the_same_name": { diff --git a/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_register_a_group_if_the_first_generated_GID_is_already_registered b/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_register_a_group_if_the_first_generated_GID_is_already_registered new file mode 100644 index 0000000000..e992cfed83 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_register_a_group_if_the_first_generated_GID_is_already_registered @@ -0,0 +1,4 @@ +name: authd-temp-groups-test +gid: 12345 +users: [] +passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_register_a_group_if_the_first_generated_GID_is_already_registered_other-group b/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_register_a_group_if_the_first_generated_GID_is_already_registered_other-group new file mode 100644 index 0000000000..409866d28f --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_register_a_group_if_the_first_generated_GID_is_already_registered_other-group @@ -0,0 +1,4 @@ +name: other-group +gid: 12346 +users: [] +passwd: "" From 2e87843896f4762ef61941bb40088d4557800496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 18 Jun 2025 20:17:07 +0000 Subject: [PATCH 0547/1670] users/tempentries: Replace RegisterGroup with RegisterGroupForUser When a new group has to be registered we need to do various checks to ensure that the GID that is generated for it is valid: - The gid must not match the user-generated UID - The gid must not be used by any other temporary user record - The gid must not be used by any other temporary user group We were doing some of these checks implicitly (by relying on NSS) or were recently added by previous commits, but we were not checking the case in which a pre-check user was sharing the same ID. As per this, move the registration at higher level and make it checking all the assertions said above while generating. Comes with more complex tests where we simulate a bit more what the users manager does, by registering the users and testing the various fail cases --- internal/users/manager.go | 2 +- internal/users/manager_test.go | 3 +- internal/users/tempentries/groups.go | 9 +- internal/users/tempentries/groups_test.go | 4 +- internal/users/tempentries/tempentries.go | 28 ++ .../users/tempentries/tempentries_test.go | 409 +++++++++++++++++- .../Error_if_there_are_no_GID_to_generate | 6 + ..._are_no_GID_to_generate_for_pre-check_user | 6 + ...lly_register_a_new_group_for_generic_user} | 4 +- ...thd-temp-users-test+authd-temp-groups-test | 4 + ...lly_register_a_new_group_for_pre-auth_user | 6 + ...ster_a_new_group_for_various_generic_users | 6 + ...hd-temp-users-test1+authd-temp-groups-test | 4 + ...hd-temp-users-test2+authd-temp-groups-test | 4 + ...ous_generic_users_authd-temp-users-test2_1 | 6 + ...hd-temp-users-test3+authd-temp-groups-test | 4 + ...ous_generic_users_authd-temp-users-test3_2 | 6 + ...ter_a_new_group_for_various_pre-auth_users | 6 + ...us_pre-auth_users_authd-temp-users-test2_1 | 6 + ...us_pre-auth_users_authd-temp-users-test3_2 | 6 + ...the_first_generated_GID_is_already_in_use} | 4 +- ...thd-temp-users-test+authd-temp-groups-test | 4 + ..._GID_is_already_in_use_by_a_pre-check_user | 6 + ...r_another-user-name+authd-temp-groups-test | 4 + ...se_by_a_pre-check_user_another-user-name_1 | 6 + ..._first_generated_GID_matches_the_user_UID} | 4 +- ...thd-temp-users-test+authd-temp-groups-test | 4 + ..._the_first_generated_UID_is_already_in_use | 6 + ...ser_if_multiple_concurrent_requests_happen | 6 + ...oncurrent_requests_happen_for_the_same_UID | 6 + ...for_the_same_UID_racing-pre-checked-user_1 | 6 + ..._requests_happen_racing-pre-checked-user_1 | 6 + ..._first_generated_UID_is_already_registered | 6 + ...lready_registered_other-pre-checked-user_1 | 6 + ...a_pre-checked_user_twice_with_the_same_UID | 6 + ...twice_with_the_same_UID_pre-checked-user_1 | 6 + ...egister_a_various_groups_for_generic_user} | 4 +- ...generic_user_authd-temp-users-test+group-a | 4 + ...s_for_generic_user_authd-temp-users-test_1 | 4 + ...s_for_generic_user_authd-temp-users-test_2 | 4 + ...egister_a_various_groups_for_pre-auth_user | 6 + ...ully_register_an_user_after_two_pre_checks | 6 + ...ks_pre-checked-user+authd-temp-groups-test | 4 + ...er_after_two_pre_checks_pre-checked-user_1 | 6 + ...er_after_two_pre_checks_pre-checked-user_2 | 6 + ...tiple_concurrent_pre-check_requests_happen | 6 + ...pre-check_requests_happen_for_the_same_UID | 6 + ...ng-pre-checked-user+authd-temp-groups-test | 4 + ...for_the_same_UID_racing-pre-checked-user_1 | 6 + ...ng-pre-checked-user+authd-temp-groups-test | 4 + ..._requests_happen_racing-pre-checked-user_1 | 6 + ...ser_if_multiple_concurrent_requests_happen | 6 + ...oncurrent_requests_happen_for_the_same_UID | 6 + ...ame_UID_racing-user+authd-temp-groups-test | 4 + ...ests_happen_for_the_same_UID_racing-user_1 | 6 + ..._happen_racing-user+authd-temp-groups-test | 4 + ...e_concurrent_requests_happen_racing-user_1 | 6 + ..._first_generated_UID_is_already_registered | 6 + ...gistered_other-user+authd-temp-groups-test | 4 + ...ted_UID_is_already_registered_other-user_2 | 6 + ...ed_pre-checked-user+authd-temp-groups-test | 4 + ...D_is_already_registered_pre-checked-user_1 | 6 + ...er_with_the_same_name_after_two_pre_checks | 6 + ...ks_pre-checked-user+authd-temp-groups-test | 4 + ...me_after_two_pre_checks_pre-checked-user_1 | 6 + ...me_after_two_pre_checks_pre-checked-user_2 | 6 + ...me_after_two_pre_checks_pre-checked-user_3 | 6 + 67 files changed, 757 insertions(+), 20 deletions(-) create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Error_if_there_are_no_GID_to_generate create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Error_if_there_are_no_GID_to_generate_for_pre-check_user rename internal/users/tempentries/testdata/golden/{TestRegisterUser/Successfully_register_a_new_user => TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_generic_user} (58%) create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_generic_user_authd-temp-users-test+authd-temp-groups-test create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_pre-auth_user create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test1+authd-temp-groups-test create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test2+authd-temp-groups-test create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test2_1 create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test3+authd-temp-groups-test create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test3_2 create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_pre-auth_users create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_pre-auth_users_authd-temp-users-test2_1 create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_pre-auth_users_authd-temp-users-test3_2 rename internal/users/tempentries/testdata/golden/{TestRegisterUser/Successfully_register_a_user_if_the_first_generated_UID_is_already_in_use => TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use} (58%) create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_authd-temp-users-test+authd-temp-groups-test create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user_another-user-name+authd-temp-groups-test create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user_another-user-name_1 rename internal/users/tempentries/testdata/golden/{TestUserByIDAndName/Successfully_get_a_user_by_ID => TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_matches_the_user_UID} (58%) create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_matches_the_user_UID_authd-temp-users-test+authd-temp-groups-test create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_user_if_the_first_generated_UID_is_already_in_use create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_for_the_same_UID create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_for_the_same_UID_racing-pre-checked-user_1 create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_racing-pre-checked-user_1 create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_if_the_first_generated_UID_is_already_registered create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_if_the_first_generated_UID_is_already_registered_other-pre-checked-user_1 create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_twice_with_the_same_UID create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_twice_with_the_same_UID_pre-checked-user_1 rename internal/users/tempentries/testdata/golden/{TestUserByIDAndName/Successfully_get_a_user_by_name => TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user} (58%) create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user_authd-temp-users-test+group-a create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user_authd-temp-users-test_1 create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user_authd-temp-users-test_2 create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_pre-auth_user create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks_pre-checked-user+authd-temp-groups-test create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks_pre-checked-user_1 create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks_pre-checked-user_2 create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID_racing-pre-checked-user+authd-temp-groups-test create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID_racing-pre-checked-user_1 create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_racing-pre-checked-user+authd-temp-groups-test create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_racing-pre-checked-user_1 create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID_racing-user+authd-temp-groups-test create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID_racing-user_1 create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_racing-user+authd-temp-groups-test create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_racing-user_1 create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_other-user+authd-temp-groups-test create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_other-user_2 create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_pre-checked-user+authd-temp-groups-test create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_pre-checked-user_1 create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user+authd-temp-groups-test create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user_1 create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user_2 create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user_3 diff --git a/internal/users/manager.go b/internal/users/manager.go index 7ce9ddd85e..f0232864b9 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -227,7 +227,7 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { }() for _, g := range newGroups { - gid, cleanup, err := m.temporaryRecords.RegisterGroup(g.Name) + gid, cleanup, err := m.temporaryRecords.RegisterGroupForUser(uid, g.Name) if err != nil { return fmt.Errorf("could not generate GID for group %q: %v", g.Name, err) } diff --git a/internal/users/manager_test.go b/internal/users/manager_test.go index 3748f02527..a7eb1c59bb 100644 --- a/internal/users/manager_test.go +++ b/internal/users/manager_test.go @@ -433,6 +433,7 @@ func TestAllUsers(t *testing.T) { func TestGroupByIDAndName(t *testing.T) { t.Parallel() + userUID := uint32(12345) tests := map[string]struct { gid uint32 @@ -462,7 +463,7 @@ func TestGroupByIDAndName(t *testing.T) { if tc.isTempGroup { var cleanup func() - tc.gid, cleanup, err = m.TemporaryRecords().RegisterGroup("tempgroup1") + tc.gid, cleanup, err = m.TemporaryRecords().RegisterGroupForUser(userUID, "tempgroup1") require.NoError(t, err, "RegisterGroup should not return an error, but did") t.Cleanup(cleanup) } diff --git a/internal/users/tempentries/groups.go b/internal/users/tempentries/groups.go index a8a31a5aeb..89e0734056 100644 --- a/internal/users/tempentries/groups.go +++ b/internal/users/tempentries/groups.go @@ -66,11 +66,10 @@ func groupEntry(group groupRecord) types.GroupEntry { return types.GroupEntry{Name: group.name, GID: group.gid, Passwd: group.passwd} } -// RegisterGroup registers a temporary group with a unique GID in our NSS handler (in memory, not in the database). +// registerGroup registers a temporary group with a unique GID in our NSS handler (in memory, not in the database). // -// Returns the generated GID and a cleanup function that should be called to remove the temporary group once the group -// was added to the database. -func (r *temporaryGroupRecords) RegisterGroup(name string) (gid uint32, cleanup func(), err error) { +// Returns the generated GID and a cleanup function that should be called to remove the temporary group. +func (r *temporaryGroupRecords) registerGroup(name string) (gid uint32, cleanup func(), err error) { r.registerMu.Lock() defer r.registerMu.Unlock() @@ -118,8 +117,6 @@ func (r *temporaryGroupRecords) uniqueNameAndGID(name string, gid uint32, groupE return false, nil } - // FIXME: Also check here if the generated GID is not part of the pre-check users UIDs! - for _, entry := range groupEntries { if entry.Name == name { // A group with the same name already exists, we can't register this temporary group. diff --git a/internal/users/tempentries/groups_test.go b/internal/users/tempentries/groups_test.go index bfde5c43db..768d825040 100644 --- a/internal/users/tempentries/groups_test.go +++ b/internal/users/tempentries/groups_test.go @@ -72,7 +72,7 @@ func TestRegisterGroup(t *testing.T) { for idx, groupName := range tc.groups { t.Logf("Registering group %q", groupName) - gid, cleanup, err := records.RegisterGroup(groupName) + gid, cleanup, err := records.registerGroup(groupName) if tc.wantErr[idx] { require.Error(t, err, "RegisterGroup should return an error, but did not") continue @@ -173,7 +173,7 @@ func TestGroupByIDAndName(t *testing.T) { records := newTemporaryGroupRecords(idGeneratorMock) if tc.registerGroup { - gid, cleanup, err := records.RegisterGroup(groupName) + gid, cleanup, err := records.registerGroup(groupName) require.NoError(t, err, "RegisterGroup should not return an error, but did") require.Equal(t, gidToGenerate, gid, "GID should be the one generated by the IDGenerator") diff --git a/internal/users/tempentries/tempentries.go b/internal/users/tempentries/tempentries.go index 1dee0883d6..5292933108 100644 --- a/internal/users/tempentries/tempentries.go +++ b/internal/users/tempentries/tempentries.go @@ -185,3 +185,31 @@ func (r *TemporaryRecords) RegisterPreAuthUser(loginName string) (uid uint32, er return uid, nil } } + +// RegisterGroupForUser registers a temporary group with a unique GID in our +// NSS handler (in memory, not in the database) for the provided UID. +// +// Returns the generated GID and a cleanup function that should be called to remove +// the temporary group once the group was added to the database. +func (r *TemporaryRecords) RegisterGroupForUser(uid uint32, name string) (gid uint32, cleanup func(), err error) { + for { + gid, cleanup, err := r.temporaryGroupRecords.registerGroup(name) + if err != nil { + return gid, cleanup, err + } + + if gid == uid { + // Generated GID matches current user UID, try again... + cleanup() + continue + } + + if !r.idTracker.trackID(gid) { + // If the UID is not unique, generate a new one in the next iteration. + cleanup() + continue + } + + return gid, func() { cleanup(); r.idTracker.forgetID(gid) }, nil + } +} diff --git a/internal/users/tempentries/tempentries_test.go b/internal/users/tempentries/tempentries_test.go index 86884b7bbf..b57b439518 100644 --- a/internal/users/tempentries/tempentries_test.go +++ b/internal/users/tempentries/tempentries_test.go @@ -1,6 +1,8 @@ package tempentries import ( + "fmt" + "slices" "testing" "github.com/stretchr/testify/require" @@ -94,6 +96,409 @@ func TestRegisterUser(t *testing.T) { } } +func TestRegisterUserAndGroupForUser(t *testing.T) { + t.Parallel() + + defaultUserName := "authd-temp-users-test" + uidToGenerate := uint32(12345) + defaultGroupName := "authd-temp-groups-test" + gidToGenerate := uint32(54321) + + type userTestData struct { + name string + preAuth bool + groups []string + runCleanup bool + + // Simulates the case in which an UID has been registered while another + // request wins the UID registration race. + simulateUIDRace bool + + // Simulates the case in which an user has been registered while another + // request wins the username registration race. + simulateNameRace bool + + wantUID uint32 + wantErr bool + wantGroupErr []bool + wantGIDs []uint32 + } + + defaultUserTestData := userTestData{name: defaultUserName} + + tests := map[string]struct { + users []userTestData + uidsToGenerate []uint32 + gidsToGenerate []uint32 + }{ + "Successfully_register_a_new_group_for_generic_user": {}, + "Successfully_register_a_new_group_for_pre-auth_user": { + users: []userTestData{{name: defaultUserName, preAuth: true}}, + }, + "Successfully_register_a_new_group_for_various_generic_users": { + users: []userTestData{ + {name: defaultUserName + fmt.Sprint(1)}, + {name: defaultUserName + fmt.Sprint(2)}, + {name: defaultUserName + fmt.Sprint(3)}, + }, + }, + "Successfully_register_a_new_group_for_various_pre-auth_users": { + users: []userTestData{ + {name: defaultUserName + fmt.Sprint(1), preAuth: true}, + {name: defaultUserName + fmt.Sprint(2), preAuth: true}, + {name: defaultUserName + fmt.Sprint(3), preAuth: true}, + }, + }, + "Successfully_register_a_various_groups_for_generic_user": { + users: []userTestData{ + {name: defaultUserName, groups: []string{"group-a", "group-b", "group-c"}}, + }, + }, + "Successfully_register_a_various_groups_for_pre-auth_user": { + users: []userTestData{ + {name: defaultUserName, preAuth: true, groups: []string{"group-a", "group-b", "group-c"}}, + }, + }, + "Successfully_register_a_new_user_if_the_first_generated_UID_is_already_in_use": { + uidsToGenerate: []uint32{0, uidToGenerate}, // UID 0 (root) always exists + users: []userTestData{{name: defaultUserName, preAuth: true, wantUID: uidToGenerate}}, + }, + "Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use": { + users: []userTestData{{name: defaultUserName, wantGIDs: []uint32{gidToGenerate}}}, + gidsToGenerate: []uint32{0, gidToGenerate}, // GID 0 (root) always exists + }, + "Successfully_register_a_new_group_if_the_first_generated_GID_matches_the_user_UID": { + users: []userTestData{{name: defaultUserName, wantGIDs: []uint32{gidToGenerate}}}, + gidsToGenerate: []uint32{uidToGenerate, gidToGenerate}, // UID should be skipped! + }, + "Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user": { + users: []userTestData{ + {name: defaultUserName, preAuth: true}, + {name: "another-user-name"}, + }, + gidsToGenerate: []uint32{uidToGenerate, gidToGenerate, gidToGenerate + 1}, + }, + "Successfully_register_an_user_if_the_first_generated_UID_is_already_registered": { + users: []userTestData{ + {name: "pre-checked-user", preAuth: true, wantUID: uidToGenerate}, + {name: "pre-checked-user", wantUID: uidToGenerate, wantGIDs: []uint32{gidToGenerate}}, + {name: "other-user", wantUID: uidToGenerate + 1, wantGIDs: []uint32{gidToGenerate + 1}}, + }, + uidsToGenerate: []uint32{uidToGenerate, uidToGenerate, uidToGenerate + 1}, + }, + "Successfully_register_a_pre-checked_user_if_the_first_generated_UID_is_already_registered": { + users: []userTestData{ + {name: "pre-checked-user", preAuth: true, wantUID: uidToGenerate}, + {name: "other-pre-checked-user", preAuth: true, wantUID: uidToGenerate + 1}, + }, + uidsToGenerate: []uint32{uidToGenerate, uidToGenerate, uidToGenerate + 1}, + }, + "Successfully_register_a_pre-checked_user_twice_with_the_same_UID": { + users: []userTestData{ + {name: "pre-checked-user", preAuth: true, wantUID: uidToGenerate}, + {name: "pre-checked-user", preAuth: true, wantUID: uidToGenerate}, + }, + }, + "Successfully_register_an_user_after_two_pre_checks": { + users: []userTestData{ + {name: "pre-checked-user", preAuth: true, wantUID: uidToGenerate}, + {name: "pre-checked-user", preAuth: true, wantUID: uidToGenerate}, + {name: "pre-checked-user", wantUID: uidToGenerate, wantGIDs: []uint32{gidToGenerate}}, + }, + }, + "Successfully_register_an_user_with_the_same_name_after_two_pre_checks": { + users: []userTestData{ + {name: "pre-checked-user", preAuth: true, wantUID: uidToGenerate}, + {name: "pre-checked-user", preAuth: true, wantUID: uidToGenerate}, + {name: "pre-checked-user", wantUID: uidToGenerate, wantGIDs: []uint32{gidToGenerate}, runCleanup: true}, + {name: "pre-checked-user", preAuth: true, wantUID: uidToGenerate + 1}, + }, + }, + "Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen": { + users: []userTestData{ + {name: "racing-pre-checked-user", preAuth: true, simulateNameRace: true}, + {name: "racing-pre-checked-user", preAuth: true, wantUID: uidToGenerate}, + }, + }, + "Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen": { + users: []userTestData{ + {name: "racing-pre-checked-user", preAuth: true, simulateNameRace: true}, + {name: "racing-pre-checked-user", wantUID: uidToGenerate, wantGIDs: []uint32{gidToGenerate}}, + }, + }, + "Successfully_register_an_user_if_multiple_concurrent_requests_happen": { + users: []userTestData{ + {name: "racing-user", simulateNameRace: true, wantUID: uidToGenerate}, + {name: "racing-user", wantUID: uidToGenerate + 1, wantGIDs: []uint32{gidToGenerate}}, + }, + }, + "Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_for_the_same_UID": { + users: []userTestData{ + {name: "racing-pre-checked-user", preAuth: true, simulateUIDRace: true}, + {name: "racing-pre-checked-user", preAuth: true, wantUID: uidToGenerate}, + }, + }, + "Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID": { + users: []userTestData{ + {name: "racing-pre-checked-user", preAuth: true, simulateUIDRace: true}, + {name: "racing-pre-checked-user", wantUID: uidToGenerate, wantGIDs: []uint32{gidToGenerate}}, + }, + }, + "Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID": { + users: []userTestData{ + {name: "racing-user", simulateUIDRace: true, wantUID: uidToGenerate}, + {name: "racing-user", wantUID: uidToGenerate, wantGIDs: []uint32{gidToGenerate}}, + }, + }, + + "Error_if_there_are_no_UID_to_generate": { + users: []userTestData{{name: defaultUserName, wantErr: true}}, + uidsToGenerate: []uint32{0}, + }, + "Error_if_there_are_no_UID_to_generate_for_pre-check_user": { + users: []userTestData{{name: defaultUserName, preAuth: true, wantErr: true}}, + uidsToGenerate: []uint32{0}, + }, + "Error_if_there_are_no_GID_to_generate": { + users: []userTestData{{name: defaultUserName, preAuth: true, wantGroupErr: []bool{true}}}, + gidsToGenerate: []uint32{}, + }, + "Error_if_there_are_no_GID_to_generate_for_pre-check_user": { + users: []userTestData{{name: defaultUserName, preAuth: true, wantGroupErr: []bool{true}}}, + gidsToGenerate: []uint32{}, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + if len(tc.users) == 0 { + tc.users = append(tc.users, defaultUserTestData) + } + + if tc.uidsToGenerate == nil { + uid := uidToGenerate + for range tc.users { + tc.uidsToGenerate = append(tc.uidsToGenerate, uid) + uid++ + } + } + t.Log("UIDs to generate", tc.uidsToGenerate) + + if tc.gidsToGenerate == nil { + gid := gidToGenerate + for _, u := range tc.users { + if u.preAuth { + continue + } + groups := len(u.groups) + if u.groups == nil { + groups = 1 + } + for range groups { + tc.gidsToGenerate = append(tc.gidsToGenerate, gid) + gid++ + } + } + } + t.Log("GIDs to generate", tc.gidsToGenerate) + + wantRegisteredUsers := 0 + var registeredUIDs []uint32 + + wantRegisteredGroups := 0 + var registeredGIDs []uint32 + + lastCleanupIdx := 0 + + idGeneratorMock := &idgenerator.IDGeneratorMock{ + UIDsToGenerate: tc.uidsToGenerate, + GIDsToGenerate: tc.gidsToGenerate, + } + records := NewTemporaryRecords(idGeneratorMock) + + for idx, uc := range tc.users { + t.Logf("Registering user %q", uc.name) + + replacingPreAuthUser := false + if !uc.preAuth { + _, err := records.userByLogin(uc.name) + replacingPreAuthUser = err == nil + } + + var uid uint32 + var err error + var cleanup func() + if uc.preAuth { + uid, err = records.RegisterPreAuthUser(uc.name) + } else { + uid, cleanup, err = records.RegisterUser(uc.name) + } + + if cleanup != nil { + t.Cleanup(cleanup) + } + + if uc.wantErr { + require.Error(t, err, "User registration should return an error, but did not") + continue + } + + require.NoError(t, err, "User registration should not return an error, but did") + t.Logf("Registered user %q (preauth: %v) with UID %d", uc.name, uc.preAuth, uid) + + isDuplicated := slices.ContainsFunc(tc.users[lastCleanupIdx:idx], func(u userTestData) bool { + return u.name == uc.name + }) + + if uc.wantUID == 0 { + uc.wantUID = tc.uidsToGenerate[idx] + } + + if !isDuplicated && uc.preAuth { + wantRegisteredUsers++ + } + + require.Equal(t, uc.wantUID, uid, "%q UID is not matching expected value", uc.name) + require.Equal(t, wantRegisteredUsers, len(records.users), + "Number of pre-auth registered, users should be %d", wantRegisteredUsers) + + if isDuplicated { + require.Contains(t, registeredUIDs, uid, "UID %d has been already registered!", uid) + } else { + require.NotContains(t, registeredUIDs, uid, "UID %d has not been already registered!", uid) + } + + registeredUIDs = append(registeredUIDs, uid) + + if uc.runCleanup { + require.NotNil(t, cleanup, "Cleanup function is invalid!") + cleanup() + lastCleanupIdx = idx + 1 + + if replacingPreAuthUser { + wantRegisteredUsers-- + require.Equal(t, wantRegisteredUsers, len(records.users), + "Number of pre-auth registered, users should be %d", wantRegisteredUsers) + } + } + + // Check that the user was registered + user, err := records.userByLogin(uc.name) + if uc.preAuth || (replacingPreAuthUser && !uc.runCleanup) { + require.NoError(t, err, "UserByID should not return an error, but did") + } else { + require.ErrorIs(t, err, NoDataFoundError{}) + require.Zero(t, user, "User should be unset") + } + + var goldenOptions []golden.Option + userSuffix := "" + if idx > 0 { + userSuffix = fmt.Sprintf("_%s_%d", uc.name, idx) + goldenOptions = append(goldenOptions, golden.WithSuffix(userSuffix)) + } + + if uc.preAuth { + checkPreAuthUser(t, user, goldenOptions...) + + if uc.simulateUIDRace { + t.Logf("Dropping the registered UID %d for %q", uid, uc.name) + delete(records.preAuthUserRecords.users, uid) + + lastCleanupIdx = idx + 1 + registeredUIDs = slices.DeleteFunc(registeredUIDs, + func(u uint32) bool { return u == uid }) + wantRegisteredUsers-- + } + + if uc.simulateNameRace { + t.Logf("Dropping the registered login name %q for %d", uc.name, uid) + delete(records.preAuthUserRecords.uidByLogin, uc.name) + } + + // We don't have groups registration for the pre-check user. + continue + } + + checkUser(t, types.UserEntry{Name: uc.name, UID: uid, GID: uid}, goldenOptions...) + + if uc.simulateUIDRace { + t.Logf("Dropping the registered UID %d for %q", uid, uc.name) + delete(records.idTracker.ids, uid) + continue + } + + if uc.simulateNameRace { + // We drop the registered name to check if the logic can handle such case. + t.Logf("Dropping the registered user name %q for %d", uc.name, uid) + delete(records.idTracker.userNames, uc.name) + lastCleanupIdx = idx + 1 + continue + } + + if uc.groups == nil { + uc.groups = append(uc.groups, defaultGroupName) + } + if uc.wantGIDs == nil { + uc.wantGIDs = tc.gidsToGenerate[idx:] + } + if uc.wantGroupErr == nil { + uc.wantGroupErr = make([]bool, len(uc.groups)) + } + + numGroups := 0 + + for idx, groupName := range uc.groups { + groupName = uc.name + "+" + groupName + t.Logf("Registering group %q", groupName) + + gid, cleanup, err := records.RegisterGroupForUser(uid, groupName) + if uc.wantGroupErr[idx] { + require.Error(t, err, "RegisterGroup should return an error, but did not") + continue + } + + require.NoError(t, err, "RegisterGroup should not return an error, but did") + t.Logf("Registered group %q with GID %d", groupName, gid) + t.Cleanup(cleanup) + + isDuplicated := slices.Contains(uc.groups[:idx], groupName) + if !isDuplicated { + numGroups++ + wantRegisteredGroups++ + } + + if isDuplicated { + require.Contains(t, registeredGIDs, gid, "GID %d has been already registered!", gid) + } else { + require.NotContains(t, registeredGIDs, gid, "GID %d has not been already registered!", gid) + } + + wantGID := uc.wantGIDs[numGroups-1] + registeredGIDs = append(registeredGIDs, gid) + + require.NoError(t, err, "RegisterGroup should not return an error, but did") + require.Equal(t, wantGID, gid, "%q GID is not matching expected value", groupName) + require.Equal(t, wantRegisteredGroups, len(records.groups), + "Number of groups registered, users should be %d", wantRegisteredGroups) + + // Check that the temporary group was created + group, err := records.GroupByID(gid) + require.NoError(t, err, "GroupByID should not return an error, but did") + + groupSuffix := groupName + if idx > 0 { + groupSuffix = fmt.Sprintf("%s_%d", uc.name, idx) + } + checkGroup(t, group, + golden.WithSuffix("_"+groupSuffix)) + } + } + }) + } +} + func TestUserByIDAndName(t *testing.T) { t.Parallel() @@ -152,8 +557,8 @@ func TestUserByIDAndName(t *testing.T) { } } -func checkUser(t *testing.T, user types.UserEntry) { +func checkUser(t *testing.T, user types.UserEntry, options ...golden.Option) { t.Helper() - golden.CheckOrUpdateYAML(t, user) + golden.CheckOrUpdateYAML(t, user, options...) } diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Error_if_there_are_no_GID_to_generate b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Error_if_there_are_no_GID_to_generate new file mode 100644 index 0000000000..6713d89aa7 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Error_if_there_are_no_GID_to_generate @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: authd-temp-users-test +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Error_if_there_are_no_GID_to_generate_for_pre-check_user b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Error_if_there_are_no_GID_to_generate_for_pre-check_user new file mode 100644 index 0000000000..6713d89aa7 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Error_if_there_are_no_GID_to_generate_for_pre-check_user @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: authd-temp-users-test +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUser/Successfully_register_a_new_user b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_generic_user similarity index 58% rename from internal/users/tempentries/testdata/golden/TestRegisterUser/Successfully_register_a_new_user rename to internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_generic_user index c6f5874e44..afa721ad15 100644 --- a/internal/users/tempentries/testdata/golden/TestRegisterUser/Successfully_register_a_new_user +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_generic_user @@ -2,5 +2,5 @@ name: authd-temp-users-test uid: 12345 gid: 12345 gecos: "" -dir: /nonexistent -shell: /usr/sbin/nologin +dir: "" +shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_generic_user_authd-temp-users-test+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_generic_user_authd-temp-users-test+authd-temp-groups-test new file mode 100644 index 0000000000..8c86b68a77 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_generic_user_authd-temp-users-test+authd-temp-groups-test @@ -0,0 +1,4 @@ +name: authd-temp-users-test+authd-temp-groups-test +gid: 54321 +users: [] +passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_pre-auth_user b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_pre-auth_user new file mode 100644 index 0000000000..6713d89aa7 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_pre-auth_user @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: authd-temp-users-test +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users new file mode 100644 index 0000000000..c4e480f88d --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users @@ -0,0 +1,6 @@ +name: authd-temp-users-test1 +uid: 12345 +gid: 12345 +gecos: "" +dir: "" +shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test1+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test1+authd-temp-groups-test new file mode 100644 index 0000000000..2f0f2a9aba --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test1+authd-temp-groups-test @@ -0,0 +1,4 @@ +name: authd-temp-users-test1+authd-temp-groups-test +gid: 54321 +users: [] +passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test2+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test2+authd-temp-groups-test new file mode 100644 index 0000000000..23f5f9cc6b --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test2+authd-temp-groups-test @@ -0,0 +1,4 @@ +name: authd-temp-users-test2+authd-temp-groups-test +gid: 54322 +users: [] +passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test2_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test2_1 new file mode 100644 index 0000000000..911bf43e39 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test2_1 @@ -0,0 +1,6 @@ +name: authd-temp-users-test2 +uid: 12346 +gid: 12346 +gecos: "" +dir: "" +shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test3+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test3+authd-temp-groups-test new file mode 100644 index 0000000000..f4eb834fdb --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test3+authd-temp-groups-test @@ -0,0 +1,4 @@ +name: authd-temp-users-test3+authd-temp-groups-test +gid: 54323 +users: [] +passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test3_2 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test3_2 new file mode 100644 index 0000000000..b46fc62da6 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test3_2 @@ -0,0 +1,6 @@ +name: authd-temp-users-test3 +uid: 12347 +gid: 12347 +gecos: "" +dir: "" +shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_pre-auth_users b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_pre-auth_users new file mode 100644 index 0000000000..fed9e90062 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_pre-auth_users @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: authd-temp-users-test1 +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_pre-auth_users_authd-temp-users-test2_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_pre-auth_users_authd-temp-users-test2_1 new file mode 100644 index 0000000000..0a7f178b57 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_pre-auth_users_authd-temp-users-test2_1 @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12346 +gid: 12346 +gecos: authd-temp-users-test2 +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_pre-auth_users_authd-temp-users-test3_2 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_pre-auth_users_authd-temp-users-test3_2 new file mode 100644 index 0000000000..d566a268b2 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_pre-auth_users_authd-temp-users-test3_2 @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12347 +gid: 12347 +gecos: authd-temp-users-test3 +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUser/Successfully_register_a_user_if_the_first_generated_UID_is_already_in_use b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use similarity index 58% rename from internal/users/tempentries/testdata/golden/TestRegisterUser/Successfully_register_a_user_if_the_first_generated_UID_is_already_in_use rename to internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use index c6f5874e44..afa721ad15 100644 --- a/internal/users/tempentries/testdata/golden/TestRegisterUser/Successfully_register_a_user_if_the_first_generated_UID_is_already_in_use +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use @@ -2,5 +2,5 @@ name: authd-temp-users-test uid: 12345 gid: 12345 gecos: "" -dir: /nonexistent -shell: /usr/sbin/nologin +dir: "" +shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_authd-temp-users-test+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_authd-temp-users-test+authd-temp-groups-test new file mode 100644 index 0000000000..8c86b68a77 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_authd-temp-users-test+authd-temp-groups-test @@ -0,0 +1,4 @@ +name: authd-temp-users-test+authd-temp-groups-test +gid: 54321 +users: [] +passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user new file mode 100644 index 0000000000..6713d89aa7 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: authd-temp-users-test +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user_another-user-name+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user_another-user-name+authd-temp-groups-test new file mode 100644 index 0000000000..dc2f03061f --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user_another-user-name+authd-temp-groups-test @@ -0,0 +1,4 @@ +name: another-user-name+authd-temp-groups-test +gid: 54321 +users: [] +passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user_another-user-name_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user_another-user-name_1 new file mode 100644 index 0000000000..434285dfec --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user_another-user-name_1 @@ -0,0 +1,6 @@ +name: another-user-name +uid: 12346 +gid: 12346 +gecos: "" +dir: "" +shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestUserByIDAndName/Successfully_get_a_user_by_ID b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_matches_the_user_UID similarity index 58% rename from internal/users/tempentries/testdata/golden/TestUserByIDAndName/Successfully_get_a_user_by_ID rename to internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_matches_the_user_UID index c6f5874e44..afa721ad15 100644 --- a/internal/users/tempentries/testdata/golden/TestUserByIDAndName/Successfully_get_a_user_by_ID +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_matches_the_user_UID @@ -2,5 +2,5 @@ name: authd-temp-users-test uid: 12345 gid: 12345 gecos: "" -dir: /nonexistent -shell: /usr/sbin/nologin +dir: "" +shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_matches_the_user_UID_authd-temp-users-test+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_matches_the_user_UID_authd-temp-users-test+authd-temp-groups-test new file mode 100644 index 0000000000..8c86b68a77 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_matches_the_user_UID_authd-temp-users-test+authd-temp-groups-test @@ -0,0 +1,4 @@ +name: authd-temp-users-test+authd-temp-groups-test +gid: 54321 +users: [] +passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_user_if_the_first_generated_UID_is_already_in_use b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_user_if_the_first_generated_UID_is_already_in_use new file mode 100644 index 0000000000..6713d89aa7 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_user_if_the_first_generated_UID_is_already_in_use @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: authd-temp-users-test +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen new file mode 100644 index 0000000000..9cece8375e --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: racing-pre-checked-user +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_for_the_same_UID b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_for_the_same_UID new file mode 100644 index 0000000000..9cece8375e --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_for_the_same_UID @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: racing-pre-checked-user +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_for_the_same_UID_racing-pre-checked-user_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_for_the_same_UID_racing-pre-checked-user_1 new file mode 100644 index 0000000000..9cece8375e --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_for_the_same_UID_racing-pre-checked-user_1 @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: racing-pre-checked-user +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_racing-pre-checked-user_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_racing-pre-checked-user_1 new file mode 100644 index 0000000000..9cece8375e --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_racing-pre-checked-user_1 @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: racing-pre-checked-user +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_if_the_first_generated_UID_is_already_registered b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_if_the_first_generated_UID_is_already_registered new file mode 100644 index 0000000000..f6449d4f14 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_if_the_first_generated_UID_is_already_registered @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: pre-checked-user +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_if_the_first_generated_UID_is_already_registered_other-pre-checked-user_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_if_the_first_generated_UID_is_already_registered_other-pre-checked-user_1 new file mode 100644 index 0000000000..829bf88826 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_if_the_first_generated_UID_is_already_registered_other-pre-checked-user_1 @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12346 +gid: 12346 +gecos: other-pre-checked-user +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_twice_with_the_same_UID b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_twice_with_the_same_UID new file mode 100644 index 0000000000..f6449d4f14 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_twice_with_the_same_UID @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: pre-checked-user +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_twice_with_the_same_UID_pre-checked-user_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_twice_with_the_same_UID_pre-checked-user_1 new file mode 100644 index 0000000000..f6449d4f14 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_twice_with_the_same_UID_pre-checked-user_1 @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: pre-checked-user +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestUserByIDAndName/Successfully_get_a_user_by_name b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user similarity index 58% rename from internal/users/tempentries/testdata/golden/TestUserByIDAndName/Successfully_get_a_user_by_name rename to internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user index c6f5874e44..afa721ad15 100644 --- a/internal/users/tempentries/testdata/golden/TestUserByIDAndName/Successfully_get_a_user_by_name +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user @@ -2,5 +2,5 @@ name: authd-temp-users-test uid: 12345 gid: 12345 gecos: "" -dir: /nonexistent -shell: /usr/sbin/nologin +dir: "" +shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user_authd-temp-users-test+group-a b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user_authd-temp-users-test+group-a new file mode 100644 index 0000000000..d88cf63118 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user_authd-temp-users-test+group-a @@ -0,0 +1,4 @@ +name: authd-temp-users-test+group-a +gid: 54321 +users: [] +passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user_authd-temp-users-test_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user_authd-temp-users-test_1 new file mode 100644 index 0000000000..e9b0e5a23b --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user_authd-temp-users-test_1 @@ -0,0 +1,4 @@ +name: authd-temp-users-test+group-b +gid: 54322 +users: [] +passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user_authd-temp-users-test_2 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user_authd-temp-users-test_2 new file mode 100644 index 0000000000..0c8758a449 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user_authd-temp-users-test_2 @@ -0,0 +1,4 @@ +name: authd-temp-users-test+group-c +gid: 54323 +users: [] +passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_pre-auth_user b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_pre-auth_user new file mode 100644 index 0000000000..6713d89aa7 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_pre-auth_user @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: authd-temp-users-test +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks new file mode 100644 index 0000000000..f6449d4f14 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: pre-checked-user +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks_pre-checked-user+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks_pre-checked-user+authd-temp-groups-test new file mode 100644 index 0000000000..fb728da53c --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks_pre-checked-user+authd-temp-groups-test @@ -0,0 +1,4 @@ +name: pre-checked-user+authd-temp-groups-test +gid: 54321 +users: [] +passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks_pre-checked-user_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks_pre-checked-user_1 new file mode 100644 index 0000000000..f6449d4f14 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks_pre-checked-user_1 @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: pre-checked-user +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks_pre-checked-user_2 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks_pre-checked-user_2 new file mode 100644 index 0000000000..8bd14e83d8 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks_pre-checked-user_2 @@ -0,0 +1,6 @@ +name: pre-checked-user +uid: 12345 +gid: 12345 +gecos: "" +dir: "" +shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen new file mode 100644 index 0000000000..9cece8375e --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: racing-pre-checked-user +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID new file mode 100644 index 0000000000..9cece8375e --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: racing-pre-checked-user +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID_racing-pre-checked-user+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID_racing-pre-checked-user+authd-temp-groups-test new file mode 100644 index 0000000000..a77db7210a --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID_racing-pre-checked-user+authd-temp-groups-test @@ -0,0 +1,4 @@ +name: racing-pre-checked-user+authd-temp-groups-test +gid: 54321 +users: [] +passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID_racing-pre-checked-user_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID_racing-pre-checked-user_1 new file mode 100644 index 0000000000..4c7f974d0f --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID_racing-pre-checked-user_1 @@ -0,0 +1,6 @@ +name: racing-pre-checked-user +uid: 12345 +gid: 12345 +gecos: "" +dir: "" +shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_racing-pre-checked-user+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_racing-pre-checked-user+authd-temp-groups-test new file mode 100644 index 0000000000..a77db7210a --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_racing-pre-checked-user+authd-temp-groups-test @@ -0,0 +1,4 @@ +name: racing-pre-checked-user+authd-temp-groups-test +gid: 54321 +users: [] +passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_racing-pre-checked-user_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_racing-pre-checked-user_1 new file mode 100644 index 0000000000..4c7f974d0f --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_racing-pre-checked-user_1 @@ -0,0 +1,6 @@ +name: racing-pre-checked-user +uid: 12345 +gid: 12345 +gecos: "" +dir: "" +shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen new file mode 100644 index 0000000000..4b914fec45 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen @@ -0,0 +1,6 @@ +name: racing-user +uid: 12345 +gid: 12345 +gecos: "" +dir: "" +shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID new file mode 100644 index 0000000000..4b914fec45 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID @@ -0,0 +1,6 @@ +name: racing-user +uid: 12345 +gid: 12345 +gecos: "" +dir: "" +shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID_racing-user+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID_racing-user+authd-temp-groups-test new file mode 100644 index 0000000000..0176b5efc7 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID_racing-user+authd-temp-groups-test @@ -0,0 +1,4 @@ +name: racing-user+authd-temp-groups-test +gid: 54321 +users: [] +passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID_racing-user_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID_racing-user_1 new file mode 100644 index 0000000000..4b914fec45 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID_racing-user_1 @@ -0,0 +1,6 @@ +name: racing-user +uid: 12345 +gid: 12345 +gecos: "" +dir: "" +shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_racing-user+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_racing-user+authd-temp-groups-test new file mode 100644 index 0000000000..0176b5efc7 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_racing-user+authd-temp-groups-test @@ -0,0 +1,4 @@ +name: racing-user+authd-temp-groups-test +gid: 54321 +users: [] +passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_racing-user_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_racing-user_1 new file mode 100644 index 0000000000..bcc33aaabd --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_racing-user_1 @@ -0,0 +1,6 @@ +name: racing-user +uid: 12346 +gid: 12346 +gecos: "" +dir: "" +shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered new file mode 100644 index 0000000000..f6449d4f14 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: pre-checked-user +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_other-user+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_other-user+authd-temp-groups-test new file mode 100644 index 0000000000..37c90f6054 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_other-user+authd-temp-groups-test @@ -0,0 +1,4 @@ +name: other-user+authd-temp-groups-test +gid: 54322 +users: [] +passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_other-user_2 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_other-user_2 new file mode 100644 index 0000000000..73fa2bc2d6 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_other-user_2 @@ -0,0 +1,6 @@ +name: other-user +uid: 12346 +gid: 12346 +gecos: "" +dir: "" +shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_pre-checked-user+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_pre-checked-user+authd-temp-groups-test new file mode 100644 index 0000000000..fb728da53c --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_pre-checked-user+authd-temp-groups-test @@ -0,0 +1,4 @@ +name: pre-checked-user+authd-temp-groups-test +gid: 54321 +users: [] +passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_pre-checked-user_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_pre-checked-user_1 new file mode 100644 index 0000000000..8bd14e83d8 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_pre-checked-user_1 @@ -0,0 +1,6 @@ +name: pre-checked-user +uid: 12345 +gid: 12345 +gecos: "" +dir: "" +shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks new file mode 100644 index 0000000000..f6449d4f14 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: pre-checked-user +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user+authd-temp-groups-test new file mode 100644 index 0000000000..fb728da53c --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user+authd-temp-groups-test @@ -0,0 +1,4 @@ +name: pre-checked-user+authd-temp-groups-test +gid: 54321 +users: [] +passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user_1 new file mode 100644 index 0000000000..f6449d4f14 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user_1 @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12345 +gid: 12345 +gecos: pre-checked-user +dir: /nonexistent +shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user_2 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user_2 new file mode 100644 index 0000000000..8bd14e83d8 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user_2 @@ -0,0 +1,6 @@ +name: pre-checked-user +uid: 12345 +gid: 12345 +gecos: "" +dir: "" +shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user_3 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user_3 new file mode 100644 index 0000000000..891dd18c29 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user_3 @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-XXXXXXXX +uid: 12346 +gid: 12346 +gecos: pre-checked-user +dir: /nonexistent +shell: /usr/sbin/nologin From 1e106a9b5149c969f2257c3d534dd04279ca1669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 19 Jun 2025 01:06:02 +0200 Subject: [PATCH 0548/1670] users/tempentries: Do not expose the temporary registered groups While it was important to expose the temporary users to NSS in the past, this is not required anymore because they are now existing only while we are updating an User, and during that phase the NSS database is locked, so there's no way that there may be a race on defining the UIDs, at least externally. Authd has still to make sure that while registering new users (pre-auth or not) or new groups the UIDs are unique, but this has been ensured already in the previous commits --- internal/users/manager.go | 8 --- internal/users/manager_test.go | 29 ++------ internal/users/tempentries/export_test.go | 30 ++++++++ internal/users/tempentries/groups.go | 69 ++++--------------- internal/users/tempentries/tempentries.go | 8 +-- .../Successfully_get_temporary_group_by_ID | 4 -- .../Successfully_get_temporary_group_by_name | 4 -- .../Successfully_get_temporary_user_by_name | 6 -- 8 files changed, 52 insertions(+), 106 deletions(-) create mode 100644 internal/users/tempentries/export_test.go delete mode 100644 internal/users/testdata/golden/TestGroupByIDAndName/Successfully_get_temporary_group_by_ID delete mode 100644 internal/users/testdata/golden/TestGroupByIDAndName/Successfully_get_temporary_group_by_name delete mode 100644 internal/users/testdata/golden/TestUserByIDAndName/Successfully_get_temporary_user_by_name diff --git a/internal/users/manager.go b/internal/users/manager.go index f0232864b9..d3f0bed528 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -415,10 +415,6 @@ func (m *Manager) AllUsers() ([]types.UserEntry, error) { // GroupByName returns the group information for the given group name. func (m *Manager) GroupByName(groupname string) (types.GroupEntry, error) { grp, err := m.db.GroupWithMembersByName(groupname) - if errors.Is(err, db.NoDataFoundError{}) { - // Check if the group is a temporary group. - return m.temporaryRecords.GroupByName(groupname) - } if err != nil { return types.GroupEntry{}, err } @@ -428,10 +424,6 @@ func (m *Manager) GroupByName(groupname string) (types.GroupEntry, error) { // GroupByID returns the group information for the given group ID. func (m *Manager) GroupByID(gid uint32) (types.GroupEntry, error) { grp, err := m.db.GroupWithMembersByID(gid) - if errors.Is(err, db.NoDataFoundError{}) { - // Check if the group is a temporary group. - return m.temporaryRecords.GroupByID(gid) - } if err != nil { return types.GroupEntry{}, err } diff --git a/internal/users/manager_test.go b/internal/users/manager_test.go index a7eb1c59bb..107ea8f683 100644 --- a/internal/users/manager_test.go +++ b/internal/users/manager_test.go @@ -433,21 +433,17 @@ func TestAllUsers(t *testing.T) { func TestGroupByIDAndName(t *testing.T) { t.Parallel() - userUID := uint32(12345) tests := map[string]struct { - gid uint32 - groupname string - dbFile string - isTempGroup bool + gid uint32 + groupname string + dbFile string wantErr bool wantErrType error }{ - "Successfully_get_group_by_ID": {gid: 11111, dbFile: "multiple_users_and_groups"}, - "Successfully_get_group_by_name": {groupname: "group1", dbFile: "multiple_users_and_groups"}, - "Successfully_get_temporary_group_by_ID": {dbFile: "multiple_users_and_groups", isTempGroup: true}, - "Successfully_get_temporary_group_by_name": {groupname: "tempgroup1", dbFile: "multiple_users_and_groups", isTempGroup: true}, + "Successfully_get_group_by_ID": {gid: 11111, dbFile: "multiple_users_and_groups"}, + "Successfully_get_group_by_name": {groupname: "group1", dbFile: "multiple_users_and_groups"}, "Error_if_group_does_not_exist_-_by_ID": {gid: 0, dbFile: "multiple_users_and_groups", wantErrType: db.NoDataFoundError{}}, "Error_if_group_does_not_exist_-_by_name": {groupname: "doesnotexist", dbFile: "multiple_users_and_groups", wantErrType: db.NoDataFoundError{}}, @@ -461,13 +457,6 @@ func TestGroupByIDAndName(t *testing.T) { require.NoError(t, err, "Setup: could not create database from testdata") m := newManagerForTests(t, dbDir) - if tc.isTempGroup { - var cleanup func() - tc.gid, cleanup, err = m.TemporaryRecords().RegisterGroupForUser(userUID, "tempgroup1") - require.NoError(t, err, "RegisterGroup should not return an error, but did") - t.Cleanup(cleanup) - } - var group types.GroupEntry if tc.groupname != "" { group, err = m.GroupByName(tc.groupname) @@ -480,14 +469,6 @@ func TestGroupByIDAndName(t *testing.T) { return } - // Registering a temporary group creates it with a random GID and random passwd, so we have to make it - // deterministic before comparing it with the golden file - if tc.isTempGroup { - require.Equal(t, tc.gid, group.GID) - group.GID = 0 - require.Empty(t, group.Passwd) - } - golden.CheckOrUpdateYAML(t, group) }) } diff --git a/internal/users/tempentries/export_test.go b/internal/users/tempentries/export_test.go new file mode 100644 index 0000000000..247c4eaeba --- /dev/null +++ b/internal/users/tempentries/export_test.go @@ -0,0 +1,30 @@ +package tempentries + +import ( + "github.com/ubuntu/authd/internal/users/db" + "github.com/ubuntu/authd/internal/users/types" +) + +// GroupByID returns the group information for the given group ID. +func (r *temporaryGroupRecords) GroupByID(gid uint32) (types.GroupEntry, error) { + group, ok := r.groups[gid] + if !ok { + return types.GroupEntry{}, db.NewGIDNotFoundError(gid) + } + + return groupEntry(group), nil +} + +func groupEntry(group groupRecord) types.GroupEntry { + return types.GroupEntry{Name: group.name, GID: group.gid} +} + +// GroupByName returns the group information for the given group name. +func (r *temporaryGroupRecords) GroupByName(name string) (types.GroupEntry, error) { + gid, ok := r.gidByName[name] + if !ok { + return types.GroupEntry{}, db.NewGroupNotFoundError(name) + } + + return r.GroupByID(gid) +} diff --git a/internal/users/tempentries/groups.go b/internal/users/tempentries/groups.go index 89e0734056..213911a773 100644 --- a/internal/users/tempentries/groups.go +++ b/internal/users/tempentries/groups.go @@ -2,26 +2,22 @@ package tempentries import ( "context" - "errors" "fmt" "sync" - "github.com/ubuntu/authd/internal/users/db" "github.com/ubuntu/authd/internal/users/localentries" "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/authd/log" ) type groupRecord struct { - name string - gid uint32 - passwd string + name string + gid uint32 } type temporaryGroupRecords struct { idGenerator IDGenerator - registerMu sync.Mutex - rwMu sync.RWMutex + mu sync.Mutex groups map[uint32]groupRecord gidByName map[string]uint32 } @@ -29,56 +25,21 @@ type temporaryGroupRecords struct { func newTemporaryGroupRecords(idGenerator IDGenerator) *temporaryGroupRecords { return &temporaryGroupRecords{ idGenerator: idGenerator, - registerMu: sync.Mutex{}, - rwMu: sync.RWMutex{}, groups: make(map[uint32]groupRecord), gidByName: make(map[string]uint32), } } -// GroupByID returns the group information for the given group ID. -func (r *temporaryGroupRecords) GroupByID(gid uint32) (types.GroupEntry, error) { - r.rwMu.RLock() - defer r.rwMu.RUnlock() - - group, ok := r.groups[gid] - if !ok { - return types.GroupEntry{}, db.NewGIDNotFoundError(gid) - } - - return groupEntry(group), nil -} - -// GroupByName returns the group information for the given group name. -func (r *temporaryGroupRecords) GroupByName(name string) (types.GroupEntry, error) { - r.rwMu.RLock() - defer r.rwMu.RUnlock() - - gid, ok := r.gidByName[name] - if !ok { - return types.GroupEntry{}, db.NewGroupNotFoundError(name) - } - - return r.GroupByID(gid) -} - -func groupEntry(group groupRecord) types.GroupEntry { - return types.GroupEntry{Name: group.name, GID: group.gid, Passwd: group.passwd} -} - // registerGroup registers a temporary group with a unique GID in our NSS handler (in memory, not in the database). // // Returns the generated GID and a cleanup function that should be called to remove the temporary group. func (r *temporaryGroupRecords) registerGroup(name string) (gid uint32, cleanup func(), err error) { - r.registerMu.Lock() - defer r.registerMu.Unlock() + r.mu.Lock() + defer r.mu.Unlock() // Check if there is already a temporary group with this name - _, err = r.GroupByName(name) - if err != nil && !errors.Is(err, NoDataFoundError{}) { - return 0, nil, fmt.Errorf("could not check if temporary group %q already exists: %w", name, err) - } - if err == nil { + _, ok := r.gidByName[name] + if ok { return 0, nil, fmt.Errorf("group %q already exists", name) } @@ -110,9 +71,6 @@ func (r *temporaryGroupRecords) registerGroup(name string) (gid uint32, cleanup } func (r *temporaryGroupRecords) uniqueNameAndGID(name string, gid uint32, groupEntries []types.GroupEntry) (bool, error) { - r.rwMu.RLock() - defer r.rwMu.RUnlock() - if _, ok := r.groups[gid]; ok { return false, nil } @@ -134,19 +92,18 @@ func (r *temporaryGroupRecords) uniqueNameAndGID(name string, gid uint32, groupE } func (r *temporaryGroupRecords) addTemporaryGroup(gid uint32, name string) (cleanup func()) { - r.rwMu.Lock() - defer r.rwMu.Unlock() - r.groups[gid] = groupRecord{name: name, gid: gid} r.gidByName[name] = gid - return func() { r.deleteTemporaryGroup(gid) } + return func() { + r.mu.Lock() + defer r.mu.Unlock() + + r.deleteTemporaryGroup(gid) + } } func (r *temporaryGroupRecords) deleteTemporaryGroup(gid uint32) { - r.rwMu.Lock() - defer r.rwMu.Unlock() - group, ok := r.groups[gid] if !ok { log.Warningf(context.Background(), "Can't delete temporary group with GID %d, it does not exist", gid) diff --git a/internal/users/tempentries/tempentries.go b/internal/users/tempentries/tempentries.go index 5292933108..c9254ab8f3 100644 --- a/internal/users/tempentries/tempentries.go +++ b/internal/users/tempentries/tempentries.go @@ -186,11 +186,11 @@ func (r *TemporaryRecords) RegisterPreAuthUser(loginName string) (uid uint32, er } } -// RegisterGroupForUser registers a temporary group with a unique GID in our -// NSS handler (in memory, not in the database) for the provided UID. +// RegisterGroupForUser registers a temporary group with a unique GID in +// memory for the provided UID. // -// Returns the generated GID and a cleanup function that should be called to remove -// the temporary group once the group was added to the database. +// Returns the generated GID and a cleanup function that should be called to +// remove the temporary group once the group was added to the database. func (r *TemporaryRecords) RegisterGroupForUser(uid uint32, name string) (gid uint32, cleanup func(), err error) { for { gid, cleanup, err := r.temporaryGroupRecords.registerGroup(name) diff --git a/internal/users/testdata/golden/TestGroupByIDAndName/Successfully_get_temporary_group_by_ID b/internal/users/testdata/golden/TestGroupByIDAndName/Successfully_get_temporary_group_by_ID deleted file mode 100644 index 6a90d9b2a5..0000000000 --- a/internal/users/testdata/golden/TestGroupByIDAndName/Successfully_get_temporary_group_by_ID +++ /dev/null @@ -1,4 +0,0 @@ -name: tempgroup1 -gid: 0 -users: [] -passwd: "" diff --git a/internal/users/testdata/golden/TestGroupByIDAndName/Successfully_get_temporary_group_by_name b/internal/users/testdata/golden/TestGroupByIDAndName/Successfully_get_temporary_group_by_name deleted file mode 100644 index 6a90d9b2a5..0000000000 --- a/internal/users/testdata/golden/TestGroupByIDAndName/Successfully_get_temporary_group_by_name +++ /dev/null @@ -1,4 +0,0 @@ -name: tempgroup1 -gid: 0 -users: [] -passwd: "" diff --git a/internal/users/testdata/golden/TestUserByIDAndName/Successfully_get_temporary_user_by_name b/internal/users/testdata/golden/TestUserByIDAndName/Successfully_get_temporary_user_by_name deleted file mode 100644 index 8c4eed4c48..0000000000 --- a/internal/users/testdata/golden/TestUserByIDAndName/Successfully_get_temporary_user_by_name +++ /dev/null @@ -1,6 +0,0 @@ -name: tempuser1 -uid: 0 -gid: 0 -gecos: "" -dir: /nonexistent -shell: /usr/sbin/nologin From 6d08b5fd22cc86c7db6f1a5d55482bdbc0e75d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 25 Jun 2025 03:33:35 +0200 Subject: [PATCH 0549/1670] users/tempentries: Do not deadlock preauth on concurrent reads/writes While a RWMutex may allow read while read-locked, we may still end up in a situation in which we have a write lock and we are trying to read a value. And this can be easily tested by an upcoming change. To prevent this starvation to happen, let's just avoid re-locking a read lock. This is because as per https://pkg.go.dev/sync#RWMutex: If any goroutine calls RWMutex.Lock while the lock is already held by one or more readers, concurrent calls to RWMutex.RLock will block until the writer has acquired (and released) the lock, to ensure that the lock eventually becomes available to the writer. Note that this prohibits recursive read-locking. --- internal/users/tempentries/preauth.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/users/tempentries/preauth.go b/internal/users/tempentries/preauth.go index e11386bd3d..b4a63cdb74 100644 --- a/internal/users/tempentries/preauth.go +++ b/internal/users/tempentries/preauth.go @@ -57,6 +57,10 @@ func (r *preAuthUserRecords) userByID(uid uint32) (types.UserEntry, error) { r.rwMu.RLock() defer r.rwMu.RUnlock() + return r.userByIDWithoutLock(uid) +} + +func (r *preAuthUserRecords) userByIDWithoutLock(uid uint32) (types.UserEntry, error) { user, ok := r.users[uid] if !ok { return types.UserEntry{}, db.NewUIDNotFoundError(uid) @@ -75,7 +79,7 @@ func (r *preAuthUserRecords) userByName(name string) (types.UserEntry, error) { return types.UserEntry{}, db.NewUserNotFoundError(name) } - return r.userByID(uid) + return r.userByIDWithoutLock(uid) } func (r *preAuthUserRecords) userByLogin(loginName string) (types.UserEntry, error) { @@ -87,7 +91,7 @@ func (r *preAuthUserRecords) userByLogin(loginName string) (types.UserEntry, err return types.UserEntry{}, db.NewUserNotFoundError(loginName) } - return r.userByID(uid) + return r.userByIDWithoutLock(uid) } func preAuthUserEntry(user preAuthUser) types.UserEntry { From a277898b76e43615b3ae8e2a828b40c3e7f1b2cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 19 Jun 2025 03:11:56 +0200 Subject: [PATCH 0550/1670] users/manager: Add specific tests for RegisterUserPreAuth function We do test their callers and the callees but not the function itself, so add some basic tests and handle some errors we did not consider at this level --- internal/users/manager.go | 4 + internal/users/manager_test.go | 82 +++++++++++++++++++ internal/users/tempentries/preauth.go | 4 + .../Successfully_if_user_already_exists_on_db | 8 ++ .../Successfully_update_user | 6 ++ 5 files changed, 104 insertions(+) create mode 100644 internal/users/testdata/golden/TestRegisterUserPreauth/Successfully_if_user_already_exists_on_db create mode 100644 internal/users/testdata/golden/TestRegisterUserPreauth/Successfully_update_user diff --git a/internal/users/manager.go b/internal/users/manager.go index d3f0bed528..b71e2f2014 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -474,6 +474,10 @@ func (m *Manager) AllShadows() ([]types.ShadowEntry, error) { func (m *Manager) RegisterUserPreAuth(name string) (uid uint32, err error) { defer decorate.OnError(&err, "failed to register pre-auth user %q", name) + if userRow, err := m.db.UserByName(name); err == nil { + return userRow.UID, nil + } + if err := userslocking.WriteRecLock(); err != nil { return 0, err } diff --git a/internal/users/manager_test.go b/internal/users/manager_test.go index 107ea8f683..83dfffc2a5 100644 --- a/internal/users/manager_test.go +++ b/internal/users/manager_test.go @@ -252,6 +252,88 @@ func TestUpdateUser(t *testing.T) { } } +func TestRegisterUserPreauth(t *testing.T) { + t.Parallel() + + userCases := map[string]userCase{ + "user1": {UserInfo: types.UserInfo{Name: "user1"}, UID: 1111}, + "nameless": {UID: 1111}, + "same-name-different-uid": {UserInfo: types.UserInfo{Name: "user1"}, UID: 3333}, + "user-exists-on-system": {UserInfo: types.UserInfo{Name: "root"}, UID: 1111}, + } + + tests := map[string]struct { + userCase string + + dbFile string + + wantUserInDB bool + wantErr bool + }{ + "Successfully_update_user": {}, + "Successfully_if_user_already_exists_on_db": { + userCase: "same-name-different-uid", dbFile: "one_user_and_group", wantUserInDB: true, + }, + + "Error_if_user_has_no_username": {userCase: "nameless", wantErr: true}, + "Error_if_user_exists_on_system": {userCase: "user-exists-on-system", wantErr: true}, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + if tc.userCase == "" { + tc.userCase = "user1" + } + + user := userCases[tc.userCase] + + dbDir := t.TempDir() + if tc.dbFile != "" { + err := db.Z_ForTests_CreateDBFromYAML(filepath.Join("testdata", "db", tc.dbFile+".db.yaml"), dbDir) + require.NoError(t, err, "Setup: could not create database from testdata") + } + + managerOpts := []users.Option{ + users.WithIDGenerator(&idgenerator.IDGeneratorMock{ + UIDsToGenerate: []uint32{user.UID}, + }), + } + m := newManagerForTests(t, dbDir, managerOpts...) + + uid, err := m.RegisterUserPreAuth(user.Name) + + requireErrorAssertions(t, err, nil, tc.wantErr) + if tc.wantErr { + return + } + + _, err = m.UserByName(user.Name) + if tc.wantUserInDB { + require.NoError(t, err, "UserByName should not return an error, but did") + } else { + require.Error(t, err, "UserByName should return an error, but did not") + } + + newUser, err := m.UserByID(uid) + require.NoError(t, err, "UserByID should not return an error, but did") + + require.Equal(t, uid, newUser.UID, "UID should not have changed") + + if tc.wantUserInDB { + require.Equal(t, user.Name, newUser.Name, "User name does not match") + } else { + require.True(t, strings.HasPrefix(newUser.Name, tempentries.UserPrefix), + "Pre-auth users should have %q as prefix: %q", tempentries.UserPrefix, + newUser.Name) + newUser.Name = tempentries.UserPrefix + "-{{random-suffix}}" + } + + golden.CheckOrUpdateYAML(t, newUser) + }) + } +} + func TestBrokerForUser(t *testing.T) { t.Parallel() diff --git a/internal/users/tempentries/preauth.go b/internal/users/tempentries/preauth.go index b4a63cdb74..eafdc2253f 100644 --- a/internal/users/tempentries/preauth.go +++ b/internal/users/tempentries/preauth.go @@ -107,6 +107,10 @@ func preAuthUserEntry(user preAuthUser) types.UserEntry { } func (r *preAuthUserRecords) generatePreAuthUserID(loginName string) (uid uint32, err error) { + if loginName == "" { + return 0, errors.New("empty username") + } + // To mitigate DoS attacks, we limit the length of the name to 256 characters. if len(loginName) > 256 { return 0, errors.New("username is too long (max 256 characters)") diff --git a/internal/users/testdata/golden/TestRegisterUserPreauth/Successfully_if_user_already_exists_on_db b/internal/users/testdata/golden/TestRegisterUserPreauth/Successfully_if_user_already_exists_on_db new file mode 100644 index 0000000000..8dc6feb43a --- /dev/null +++ b/internal/users/testdata/golden/TestRegisterUserPreauth/Successfully_if_user_already_exists_on_db @@ -0,0 +1,8 @@ +name: user1 +uid: 1111 +gid: 11111 +gecos: |- + User1 gecos + On multiple lines +dir: /home/user1 +shell: /bin/bash diff --git a/internal/users/testdata/golden/TestRegisterUserPreauth/Successfully_update_user b/internal/users/testdata/golden/TestRegisterUserPreauth/Successfully_update_user new file mode 100644 index 0000000000..5c1d726d00 --- /dev/null +++ b/internal/users/testdata/golden/TestRegisterUserPreauth/Successfully_update_user @@ -0,0 +1,6 @@ +name: authd-pre-auth-user-{{random-suffix}} +uid: 1111 +gid: 1111 +gecos: user1 +dir: /nonexistent +shell: /usr/sbin/nologin From 3f6fea965745c03688546d5a1131af1ecba12882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 20 Jun 2025 05:38:49 +0200 Subject: [PATCH 0551/1670] users/tempentries/preauth: Let's not trust random enough This can't really never, ever happen... But if it does? So in case we generate random name, and such name is already registered we were overriding it. It's not a big deal, but we ended up with two entries for the same, so let's just handle this remote case --- internal/users/tempentries/preauth.go | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/internal/users/tempentries/preauth.go b/internal/users/tempentries/preauth.go index eafdc2253f..24bc127fec 100644 --- a/internal/users/tempentries/preauth.go +++ b/internal/users/tempentries/preauth.go @@ -219,13 +219,23 @@ func (r *preAuthUserRecords) addPreAuthUser(uid uint32, loginName string) (err e return fmt.Errorf("failed to register again %q as %d", loginName, uid) } - // Generate a 64 character (32 bytes in hex) random ID which we store in the name field of the temporary user - // record to be able to identify it in isUniqueUID. - bytes := make([]byte, 32) - if _, err := rand.Read(bytes); err != nil { - return fmt.Errorf("failed to generate random name: %w", err) + var name string + for { + // Generate a 64 character (32 bytes in hex) random ID which we store in the + // name field of the temporary user record to be able to identify it. + bytes := make([]byte, 32) + if _, err := rand.Read(bytes); err != nil { + return fmt.Errorf("failed to generate random name: %w", err) + } + name = fmt.Sprintf("%s-%x", UserPrefix, bytes) + + if _, ok := r.uidByName[name]; ok { + log.Debugf(context.Background(), "Generated user %q was not unique", name) + continue + } + + break } - name := fmt.Sprintf("%s-%x", UserPrefix, bytes) log.Debugf(context.Background(), "Added temporary record for user %q with UID %d as %q", loginName, uid, name) From 755584adffcdcabc2bccd171797e5c597723569d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 20 Jun 2025 07:03:33 +0200 Subject: [PATCH 0552/1670] users/tempentries: Use a more generic and uniform way to generate and check IDs We had similar logic for all the various temporary entries implementations, however we can factorize quite a bit of code to ensure that we do always the same and most important checks for all the elements. So move more logic to TemporaryRecords, where we can share more functions and where we will be able to share more in next commits --- internal/users/tempentries/export_test.go | 2 +- internal/users/tempentries/groups.go | 119 +++++----- internal/users/tempentries/groups_test.go | 26 ++- internal/users/tempentries/preauth.go | 112 +-------- internal/users/tempentries/tempentries.go | 167 ++++++++++---- .../users/tempentries/tempentries_test.go | 213 +++++++++++++++++- ..._with_the_same_name_returning_the_same_GID | 4 + ...urning_the_same_GID_authd-temp-groups-test | 4 + internal/users/tempentries/users.go | 70 +++--- 9 files changed, 449 insertions(+), 268 deletions(-) create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_registering_a_group_with_the_same_name_returning_the_same_GID create mode 100644 internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_registering_a_group_with_the_same_name_returning_the_same_GID_authd-temp-groups-test diff --git a/internal/users/tempentries/export_test.go b/internal/users/tempentries/export_test.go index 247c4eaeba..d163dfd127 100644 --- a/internal/users/tempentries/export_test.go +++ b/internal/users/tempentries/export_test.go @@ -12,7 +12,7 @@ func (r *temporaryGroupRecords) GroupByID(gid uint32) (types.GroupEntry, error) return types.GroupEntry{}, db.NewGIDNotFoundError(gid) } - return groupEntry(group), nil + return groupEntry(*group), nil } func groupEntry(group groupRecord) types.GroupEntry { diff --git a/internal/users/tempentries/groups.go b/internal/users/tempentries/groups.go index 213911a773..e7f31a6f4c 100644 --- a/internal/users/tempentries/groups.go +++ b/internal/users/tempentries/groups.go @@ -5,113 +5,96 @@ import ( "fmt" "sync" - "github.com/ubuntu/authd/internal/users/localentries" - "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/authd/log" ) type groupRecord struct { - name string - gid uint32 + refCount uint64 + name string + gid uint32 } type temporaryGroupRecords struct { idGenerator IDGenerator mu sync.Mutex - groups map[uint32]groupRecord + groups map[uint32]*groupRecord gidByName map[string]uint32 } func newTemporaryGroupRecords(idGenerator IDGenerator) *temporaryGroupRecords { return &temporaryGroupRecords{ idGenerator: idGenerator, - groups: make(map[uint32]groupRecord), + groups: make(map[uint32]*groupRecord), gidByName: make(map[string]uint32), } } -// registerGroup registers a temporary group with a unique GID in our NSS handler (in memory, not in the database). +// generateGroupID generates a potential unique GID. // -// Returns the generated GID and a cleanup function that should be called to remove the temporary group. -func (r *temporaryGroupRecords) registerGroup(name string) (gid uint32, cleanup func(), err error) { - r.mu.Lock() - defer r.mu.Unlock() - - // Check if there is already a temporary group with this name - _, ok := r.gidByName[name] - if ok { - return 0, nil, fmt.Errorf("group %q already exists", name) - } +// Returns the generated GID . +func (r *temporaryGroupRecords) generateGroupID() (gid uint32, err error) { + // Generate a GID + return r.idGenerator.GenerateGID() +} - groupEntries, err := localentries.GetGroupEntries() - if err != nil { - return 0, nil, fmt.Errorf("could not register group, failed to get group entries: %w", err) +// getTemporaryGroup gets the GID of a temporary group with the given name, +// if any, increasing its reference count. +// +// This must be called with the group mutex locked. +func (r *temporaryGroupRecords) getTemporaryGroup(name string) (gid uint32) { + gid, ok := r.gidByName[name] + if !ok { + return 0 } - // Generate a GID until we find a unique one - for { - gid, err = r.idGenerator.GenerateGID() - if err != nil { - return 0, nil, err - } - - unique, err := r.uniqueNameAndGID(name, gid, groupEntries) - if err != nil { - return 0, nil, fmt.Errorf("could not check if GID %d is unique: %w", gid, err) - } - if !unique { - // If the GID is not unique, generate a new one in the next iteration. - continue - } - - cleanup = r.addTemporaryGroup(gid, name) - log.Debugf(context.Background(), "Registered group %q with GID %d", name, gid) - return gid, cleanup, nil - } + group := r.groups[gid] + group.refCount++ + + log.Debugf(context.Background(), "Reusing GID %d for group %q", gid, group.name) + + return group.gid } -func (r *temporaryGroupRecords) uniqueNameAndGID(name string, gid uint32, groupEntries []types.GroupEntry) (bool, error) { - if _, ok := r.groups[gid]; ok { - return false, nil +// Adds a temporary group. +// +// This must be called with the group mutex locked. +func (r *temporaryGroupRecords) addTemporaryGroup(gid uint32, name string) { + // log.Debugf(nil, "Adding group %d (%q)", gid, name) + if oldGroup, ok := r.groups[gid]; ok { + panic(fmt.Sprintf("group ID %d is already registered for %q, cannot register %q", + gid, oldGroup.name, name)) } - - for _, entry := range groupEntries { - if entry.Name == name { - // A group with the same name already exists, we can't register this temporary group. - log.Debugf(context.Background(), "Name %q already in use by GID %d", name, entry.GID) - return false, fmt.Errorf("group %q already exists", name) - } - - if entry.GID == gid { - log.Debugf(context.Background(), "GID %d already in use by group %q, generating a new one", gid, entry.Name) - return false, nil - } + if oldGID, ok := r.gidByName[name]; ok { + panic(fmt.Sprintf("group %q has already GID %d, cannot use %d", name, oldGID, gid)) } - return true, nil -} - -func (r *temporaryGroupRecords) addTemporaryGroup(gid uint32, name string) (cleanup func()) { - r.groups[gid] = groupRecord{name: name, gid: gid} + r.groups[gid] = &groupRecord{name: name, gid: gid, refCount: 1} r.gidByName[name] = gid - return func() { - r.mu.Lock() - defer r.mu.Unlock() - - r.deleteTemporaryGroup(gid) - } + log.Debugf(context.Background(), "Registered group %q with GID %d", name, gid) } -func (r *temporaryGroupRecords) deleteTemporaryGroup(gid uint32) { +// releaseTemporaryGroup releases a temporary group reference, it returns +// whether the last reference has been dropped. +// +// This must be called with the group mutex locked. +func (r *temporaryGroupRecords) releaseTemporaryGroup(gid uint32) bool { group, ok := r.groups[gid] if !ok { log.Warningf(context.Background(), "Can't delete temporary group with GID %d, it does not exist", gid) - return + return false + } + + if group.refCount > 1 { + log.Debugf(context.Background(), "Removed reference on temporary record for group %q with GID %d", + group.name, gid) + group.refCount-- + return false } delete(r.groups, gid) delete(r.gidByName, group.name) log.Debugf(context.Background(), "Removed temporary record for group %q with GID %d", group.name, gid) + return true } diff --git a/internal/users/tempentries/groups_test.go b/internal/users/tempentries/groups_test.go index 768d825040..8bd4d058c3 100644 --- a/internal/users/tempentries/groups_test.go +++ b/internal/users/tempentries/groups_test.go @@ -13,6 +13,7 @@ import ( func TestRegisterGroup(t *testing.T) { t.Parallel() + ownerUID := uint32(54321) defaultGroupName := "authd-temp-groups-test" gidToGenerate := uint32(12345) @@ -33,12 +34,12 @@ func TestRegisterGroup(t *testing.T) { gidsToGenerate: []uint32{gidToGenerate, gidToGenerate, gidToGenerate + 1}, wantGIDs: []uint32{gidToGenerate, gidToGenerate + 1}, }, + "Successfully_registering_a_group_with_the_same_name_returning_the_same_GID": { + groups: []string{defaultGroupName, defaultGroupName}, + wantGIDs: []uint32{gidToGenerate, gidToGenerate}, + }, "Error_when_name_is_already_in_use": {groups: []string{"root"}, wantErr: []bool{true}}, - "Error_when_registering_a_group_with_the_same_name": { - groups: []string{defaultGroupName, defaultGroupName}, - wantErr: []bool{false, true}, - }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { @@ -68,11 +69,11 @@ func TestRegisterGroup(t *testing.T) { t.Log("GIDs to generate", tc.gidsToGenerate) idGeneratorMock := &idgenerator.IDGeneratorMock{GIDsToGenerate: tc.gidsToGenerate} - records := newTemporaryGroupRecords(idGeneratorMock) + records := NewTemporaryRecords(idGeneratorMock) for idx, groupName := range tc.groups { t.Logf("Registering group %q", groupName) - gid, cleanup, err := records.registerGroup(groupName) + gid, cleanup, err := records.RegisterGroupForUser(ownerUID, groupName) if tc.wantErr[idx] { require.Error(t, err, "RegisterGroup should return an error, but did not") continue @@ -128,7 +129,13 @@ func TestRegisterGroup(t *testing.T) { // Check that the temporary group was deleted _, err := records.GroupByID(removeGID) - require.Error(t, err, "GroupByID should return an error, but did not") + + if !slices.Contains(tc.groups[idx+1:], groupName) { + require.Error(t, err, "GroupByID should return an error, but did not") + continue + } + + require.NoError(t, err, "GroupByID should return an error, but did not") } }) } @@ -137,6 +144,7 @@ func TestRegisterGroup(t *testing.T) { func TestGroupByIDAndName(t *testing.T) { t.Parallel() + ownerUID := uint32(54321) groupName := "authd-temp-groups-test" gidToGenerate := uint32(12345) @@ -170,10 +178,10 @@ func TestGroupByIDAndName(t *testing.T) { t.Parallel() idGeneratorMock := &idgenerator.IDGeneratorMock{GIDsToGenerate: []uint32{gidToGenerate}} - records := newTemporaryGroupRecords(idGeneratorMock) + records := NewTemporaryRecords(idGeneratorMock) if tc.registerGroup { - gid, cleanup, err := records.registerGroup(groupName) + gid, cleanup, err := records.RegisterGroupForUser(ownerUID, groupName) require.NoError(t, err, "RegisterGroup should not return an error, but did") require.Equal(t, gidToGenerate, gid, "GID should be the one generated by the IDGenerator") diff --git a/internal/users/tempentries/preauth.go b/internal/users/tempentries/preauth.go index 24bc127fec..d2d3e8e4cd 100644 --- a/internal/users/tempentries/preauth.go +++ b/internal/users/tempentries/preauth.go @@ -5,11 +5,9 @@ import ( "crypto/rand" "errors" "fmt" - "slices" "sync" "github.com/ubuntu/authd/internal/users/db" - "github.com/ubuntu/authd/internal/users/localentries" "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/authd/log" ) @@ -116,109 +114,17 @@ func (r *preAuthUserRecords) generatePreAuthUserID(loginName string) (uid uint32 return 0, errors.New("username is too long (max 256 characters)") } - if err = func() error { - r.rwMu.RLock() - defer r.rwMu.RUnlock() - - if len(r.users) >= MaxPreAuthUsers { - return errors.New("maximum number of pre-auth users reached, login for new users via SSH is disabled until authd is restarted") - } - return nil - }(); err != nil { - return 0, err - } - - passwdEntries, err := localentries.GetPasswdEntries() - if err != nil { - return 0, fmt.Errorf("could not check user, failed to get passwd entries: %w", err) - } - groupEntries, err := localentries.GetGroupEntries() - if err != nil { - return 0, fmt.Errorf("could not check user, failed to get group entries: %w", err) - } - - if slices.ContainsFunc(passwdEntries, func(p types.UserEntry) bool { - return p.Name == loginName - }) { - log.Errorf(context.Background(), "User already exists on the system: %+v", loginName) - return 0, fmt.Errorf("user %q already exists on the system", loginName) - } - - // Generate a UID until we find a unique one - for { - uid, err := r.idGenerator.GenerateUID() - if err != nil { - return 0, err - } - - unique, err := r.isUniqueUID(uid, passwdEntries, groupEntries) - if err != nil { - return 0, fmt.Errorf("could not check if UID %d is unique: %w", uid, err) - } - - if !unique { - // If the UID is not unique, generate a new one in the next iteration. - continue - } - - return uid, nil - } -} - -// isUniqueUID returns true if the given UID is unique in the system. It returns false if the UID is already assigned to -// a user by any NSS source. -func (r *preAuthUserRecords) isUniqueUID(uid uint32, passwdEntries []types.UserEntry, groupEntries []types.GroupEntry) (bool, error) { - r.rwMu.RLock() - defer r.rwMu.RUnlock() - - if _, ok := r.users[uid]; ok { - return false, nil - } - - for _, entry := range passwdEntries { - if entry.UID == uid { - log.Debugf(context.Background(), "ID %d already in use by user %q", uid, entry.Name) - return false, nil - } - } - - for _, group := range groupEntries { - if group.GID == uid { - // A group with the same ID already exists, so we can't use that ID as the GID of the temporary user - log.Debugf(context.Background(), "ID %d already in use by group %q", uid, group.Name) - return false, fmt.Errorf("group with GID %d already exists", uid) - } + if len(r.users) >= MaxPreAuthUsers { + return 0, errors.New("maximum number of pre-auth users reached, login for new users via SSH is disabled until authd is restarted") } - return true, nil + // Generate a UID + return r.idGenerator.GenerateUID() } // addPreAuthUser adds a temporary user with a random name and the given UID. We use a random name here to avoid // creating user records with attacker-controlled names. -// -// It returns the generated name and a cleanup function to remove the temporary user record. func (r *preAuthUserRecords) addPreAuthUser(uid uint32, loginName string) (err error) { - r.rwMu.Lock() - defer r.rwMu.Unlock() - - if currentUID, ok := r.users[uid]; ok { - if currentUID.uid == uid { - r.uidByLogin[loginName] = uid - log.Debugf(context.Background(), - "Pre-auth user %q with UID %d is already registered", loginName, uid) - return nil - } - // This is really a programmer error if we get there, so... Let's just avoid it. - return fmt.Errorf("An user with ID %d already exists, this should never happen", uid) - } - - if currentUID, ok := r.uidByLogin[loginName]; ok && currentUID != uid { - log.Warningf(context.Background(), - "An temporary user entry for %q already exists as %d, impossible to register user", - loginName, uid) - return fmt.Errorf("failed to register again %q as %d", loginName, uid) - } - var name string for { // Generate a 64 character (32 bytes in hex) random ID which we store in the @@ -249,19 +155,19 @@ func (r *preAuthUserRecords) addPreAuthUser(uid uint32, loginName string) (err e } // deletePreAuthUser deletes the temporary user with the given UID. -func (r *preAuthUserRecords) deletePreAuthUser(uid uint32) { - r.rwMu.Lock() - defer r.rwMu.Unlock() - +// +// This must be called with the mutex locked. +func (r *preAuthUserRecords) deletePreAuthUser(uid uint32) bool { user, ok := r.users[uid] if !ok { // We ignore the case that the pre-auth user does not exist, because it can happen that the same user is // registered multiple times (because multiple SSH sessions are opened for the same user) and the cleanup // function is called multiple times. - return + return false } delete(r.users, uid) delete(r.uidByName, user.name) delete(r.uidByLogin, user.loginName) log.Debugf(context.Background(), "Removed temporary record for user %q with UID %d", user.name, uid) + return true } diff --git a/internal/users/tempentries/tempentries.go b/internal/users/tempentries/tempentries.go index c9254ab8f3..f278a36d4e 100644 --- a/internal/users/tempentries/tempentries.go +++ b/internal/users/tempentries/tempentries.go @@ -5,11 +5,13 @@ import ( "context" "errors" "fmt" + "slices" "github.com/ubuntu/authd/internal/users/db" "github.com/ubuntu/authd/internal/users/localentries" "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/authd/log" + "github.com/ubuntu/decorate" ) // NoDataFoundError is the error returned when no entry is found in the database. @@ -59,6 +61,11 @@ func (r *TemporaryRecords) RegisterUser(name string) (uid uint32, cleanup func() if err != nil { return 0, nil, fmt.Errorf("could not check user, failed to get passwd entries: %w", err) } + + if !isUniqueSystemUserName(name, passwdEntries) { + return 0, nil, fmt.Errorf("user %q already exists", name) + } + groupEntries, err := localentries.GetGroupEntries() if err != nil { return 0, nil, fmt.Errorf("could not check user, failed to get group entries: %w", err) @@ -76,21 +83,20 @@ func (r *TemporaryRecords) RegisterUser(name string) (uid uint32, cleanup func() // ones we manage in authd while we're updating the users, although there's no risk // that someone else takes the UID here, since we're locked. cleanup := func() { - r.deletePreAuthUser(user.UID) - r.forgetID(user.UID) + r.preAuthUserRecords.rwMu.Lock() + defer r.preAuthUserRecords.rwMu.Unlock() + r.idTracker.forgetUser(name) + if r.deletePreAuthUser(user.UID) { + r.forgetID(user.UID) + } } // Now that the user authenticated successfully, we don't really need to check again // if the UID is unique, since that's something we did while registering it, and we're // currently locked, so nothing else can add another user with such ID, but we do to - // ensure that there is not another user with the same name. - unique, err := uniqueNameAndUID(name, user.UID, passwdEntries, groupEntries) - if err != nil { - cleanup() - return 0, nil, fmt.Errorf("checking UID and name uniqueness: %w", err) - } - if !unique { + // double check it, just in case. + if !isUniqueSystemID(user.UID, passwdEntries, groupEntries) { cleanup() return 0, nil, fmt.Errorf("UID (%d) or name (%q) from pre-auth user are not unique", user.UID, name) } @@ -105,31 +111,24 @@ func (r *TemporaryRecords) RegisterUser(name string) (uid uint32, cleanup func() return 0, nil, err } - unique, err := uniqueNameAndUID(name, uid, passwdEntries, groupEntries) - if err != nil { - return 0, nil, fmt.Errorf("checking UID and name uniqueness: %w", err) - } - if !unique { - // If the UID is not unique, generate a new one in the next iteration. - continue - } - - if !r.idTracker.trackID(uid) { + if unique := r.maybeTrackUniqueID(uid, passwdEntries, groupEntries); !unique { // If the UID is not unique, generate a new one in the next iteration. continue } - tracked, currentUID := r.idTracker.trackUser(name, uid) - if !tracked { + if tracked, currentUID := r.idTracker.trackUser(name, uid); !tracked { // If the loginName is already set for a different UID, it means // that another concurrent request won the race, so let's just // use that one instead. r.idTracker.forgetID(uid) - uid = currentUID + return currentUID, func() {}, nil } log.Debugf(context.Background(), "Generated UID %d for user UID %s", uid, name) - return uid, func() { r.idTracker.forgetID(uid); r.idTracker.forgetUser(name) }, nil + return uid, func() { + r.idTracker.forgetID(uid) + r.idTracker.forgetUser(name) + }, nil } } @@ -144,16 +143,27 @@ func (r *TemporaryRecords) RegisterUser(name string) (uid uint32, cleanup func() // // Returns the generated UID. func (r *TemporaryRecords) RegisterPreAuthUser(loginName string) (uid uint32, err error) { + r.rwMu.Lock() + defer r.rwMu.Unlock() + // Check if there is already a pre-auth user for that name - user, err := r.preAuthUserRecords.userByLogin(loginName) - if err != nil && !errors.Is(err, NoDataFoundError{}) { - return 0, fmt.Errorf("could not check if pre-auth user %q already exists: %w", - loginName, err) + if uid, ok := r.preAuthUserRecords.uidByLogin[loginName]; ok { + return uid, nil } - if err == nil { - // A pre-auth user is already registered for this name, so we return the - // already generated UID. - return user.UID, nil + + passwdEntries, err := localentries.GetPasswdEntries() + if err != nil { + return 0, fmt.Errorf("could not check user, failed to get passwd entries: %w", err) + } + + if !isUniqueSystemUserName(loginName, passwdEntries) { + log.Errorf(context.Background(), "User already exists on the system: %+v", loginName) + return 0, fmt.Errorf("user %q already exists on the system", loginName) + } + + groupEntries, err := localentries.GetGroupEntries() + if err != nil { + return 0, fmt.Errorf("could not check user, failed to get group entries: %w", err) } for { @@ -162,13 +172,12 @@ func (r *TemporaryRecords) RegisterPreAuthUser(loginName string) (uid uint32, er return 0, err } - if !r.idTracker.trackID(uid) { + if unique := r.maybeTrackUniqueID(uid, passwdEntries, groupEntries); !unique { // If the UID is not unique, generate a new one in the next iteration. continue } - tracked, currentUID := r.idTracker.trackUser(loginName, uid) - if !tracked { + if tracked, currentUID := r.idTracker.trackUser(loginName, uid); !tracked { // If the loginName is already set for a different UID, it means // that another concurrent request won the race, so let's just // use that one instead. @@ -177,8 +186,8 @@ func (r *TemporaryRecords) RegisterPreAuthUser(loginName string) (uid uint32, er } if err := r.addPreAuthUser(uid, loginName); err != nil { - r.idTracker.forgetID(uid) r.idTracker.forgetUser(loginName) + r.idTracker.forgetID(uid) return 0, fmt.Errorf("could not add pre-auth user record: %w", err) } @@ -192,24 +201,98 @@ func (r *TemporaryRecords) RegisterPreAuthUser(loginName string) (uid uint32, er // Returns the generated GID and a cleanup function that should be called to // remove the temporary group once the group was added to the database. func (r *TemporaryRecords) RegisterGroupForUser(uid uint32, name string) (gid uint32, cleanup func(), err error) { + defer decorate.OnError(&err, "failed to register group %q for user ID %d", name, uid) + + groupEntries, err := localentries.GetGroupEntries() + if err != nil { + return 0, nil, fmt.Errorf("failed to get group entries: %w", err) + } + + if slices.ContainsFunc(groupEntries, func(g types.GroupEntry) bool { + return g.Name == name + }) { + return 0, nil, fmt.Errorf("group %q already exists", name) + } + + passwdEntries, err := localentries.GetPasswdEntries() + if err != nil { + return 0, nil, fmt.Errorf("failed to get passwd entries: %w", err) + } + + r.temporaryGroupRecords.mu.Lock() + defer r.temporaryGroupRecords.mu.Unlock() + + groupCleanup := func() { + r.temporaryGroupRecords.mu.Lock() + defer r.temporaryGroupRecords.mu.Unlock() + + if r.releaseTemporaryGroup(gid) { + r.idTracker.forgetID(gid) + } + } + + if gid = r.getTemporaryGroup(name); gid != 0 { + return gid, groupCleanup, nil + } + for { - gid, cleanup, err := r.temporaryGroupRecords.registerGroup(name) + gid, err = r.temporaryGroupRecords.generateGroupID() if err != nil { - return gid, cleanup, err + return 0, nil, err } if gid == uid { // Generated GID matches current user UID, try again... - cleanup() continue } - if !r.idTracker.trackID(gid) { - // If the UID is not unique, generate a new one in the next iteration. - cleanup() + if unique := r.maybeTrackUniqueID(gid, passwdEntries, groupEntries); !unique { + // If the GID is not unique, generate a new one in the next iteration. continue } - return gid, func() { cleanup(); r.idTracker.forgetID(gid) }, nil + r.addTemporaryGroup(gid, name) + + return gid, groupCleanup, nil } } + +func (r *TemporaryRecords) maybeTrackUniqueID(id uint32, passwdEntries []types.UserEntry, groupEntries []types.GroupEntry) (unique bool) { + defer func() { + if !unique { + log.Debugf(context.TODO(), "ID %d is not unique in this system", id) + } + }() + + if !isUniqueSystemID(id, passwdEntries, groupEntries) { + return false + } + + return r.idTracker.trackID(id) +} + +func isUniqueSystemID(uid uint32, passwdEntries []types.UserEntry, groupEntries []types.GroupEntry) bool { + if slices.ContainsFunc(passwdEntries, func(p types.UserEntry) (found bool) { + found = p.UID == uid + if found { + log.Debugf(context.Background(), "ID %d already in use by user %q", uid, p.Name) + } + return found + }) { + return false + } + + return !slices.ContainsFunc(groupEntries, func(g types.GroupEntry) (found bool) { + found = g.GID == uid + if found { + log.Debugf(context.Background(), "ID %d already in use by group %q", uid, g.Name) + } + return found + }) +} + +func isUniqueSystemUserName(name string, passwdEntries []types.UserEntry) bool { + return !slices.ContainsFunc(passwdEntries, func(p types.UserEntry) bool { + return p.Name == name + }) +} diff --git a/internal/users/tempentries/tempentries_test.go b/internal/users/tempentries/tempentries_test.go index b57b439518..ae5c20fdbb 100644 --- a/internal/users/tempentries/tempentries_test.go +++ b/internal/users/tempentries/tempentries_test.go @@ -3,6 +3,8 @@ package tempentries import ( "fmt" "slices" + "sync" + "sync/atomic" "testing" "github.com/stretchr/testify/require" @@ -11,6 +13,211 @@ import ( "github.com/ubuntu/authd/internal/users/types" ) +func TestRacingLockingActions(t *testing.T) { + t.Parallel() + + // These tests are meant to stress-test in parallel our temporary entries, + // this is happening by registering new users or pre-auth some of them and + // ensure that the generated IDs and GIDs are not clashing. + // There may be still clashes when the cleanup functions are called, but + // then it's up to the caller to ensure that these IDs are not duplicated + // in the database. + + const nIterations = 100 + + type cleanupType string + const ( + noCleanup cleanupType = "no_cleanup" + perUserCleanup cleanupType = "per_user_cleanup" + perTestCleanup cleanupType = "per_test_cleanup" + ) + + for _, cleanupType := range []cleanupType{noCleanup, perTestCleanup, perUserCleanup} { + registeredUsersMu := sync.Mutex{} + registeredUsers := make(map[string][]uint32) + registeredGroups := make(map[string][]uint32) + + tmpRecords := NewTemporaryRecords(&idgenerator.IDGenerator{ + UIDMin: 0, + UIDMax: nIterations * 5, + GIDMin: 0, + GIDMax: nIterations * 5, + }) + + t.Run(fmt.Sprintf("with_%s", cleanupType), func(t *testing.T) { + t.Parallel() + + for idx := range nIterations { + t.Run(fmt.Sprintf("iteration_%d", idx), func(t *testing.T) { + t.Parallel() + + records := tmpRecords + doPreAuth := idx%3 == 0 + userName := fmt.Sprintf("authd-test-user%d", idx) + + cleanupsMu := sync.Mutex{} + var cleanups []func() + + var userID atomic.Uint32 + var firstGroupID atomic.Uint32 + + if doPreAuth { + // In the pre-auth case we do even more parallelization, so that + // the pre-auth happens without a defined order of the actual + // registration. + userName = fmt.Sprintf("authd-test-maybe-pre-check-user%d", idx) + + //nolint:thelper // This is actually a test function! + preAuth := func(t *testing.T) { + t.Parallel() + + uid, err := records.RegisterPreAuthUser(userName) + require.NoError(t, err, "RegisterPreAuthUser should not fail but it did") + + if !userID.CompareAndSwap(0, uid) && cleanupType != perTestCleanup { + require.Equal(t, int(uid), int(userID.Load()), + "Pre-auth UID for already-registered user is not matching expected") + } + } + + for i := range 3 { + t.Run(fmt.Sprintf("pre_auth%d", i), preAuth) + } + } + + //nolint:thelper // This is actually a test function! + userUpdate := func(t *testing.T) { + t.Parallel() + + // We do not run the cleanup function here, because we want to preserve the + // user in our temporary entries, to ensure that we may not register the same + // twice. + uid, userCleanup, err := records.RegisterUser(userName) + require.NoError(t, err, "RegisterUser should not fail but it did") + if cleanupType == perTestCleanup { + t.Cleanup(userCleanup) + } + + if !userID.CompareAndSwap(0, uid) && cleanupType != perTestCleanup { + require.Equal(t, int(uid), int(userID.Load()), + "UID for pre-auth or already registered user %q is not matching expected", + userName) + } + + groupName1 := fmt.Sprintf("authd-test-group%d.1", idx) + gid1, groupCleanup1, err := records.RegisterGroupForUser(uid, groupName1) + require.NoError(t, err, "RegisterGroupForUser should not fail but it did") + if cleanupType == perTestCleanup { + t.Cleanup(groupCleanup1) + } + + if !firstGroupID.CompareAndSwap(0, gid1) && cleanupType != perTestCleanup { + require.Equal(t, int(gid1), int(firstGroupID.Load()), + "GID for group %q is not matching expected", groupName1) + } + + groupName2 := fmt.Sprintf("authd-test-group%d.2", idx) + gid2, groupCleanup2, err := records.RegisterGroupForUser(uid, groupName2) + require.NoError(t, err, "RegisterGroupForUser should not fail but it did") + if cleanupType == perTestCleanup { + t.Cleanup(groupCleanup2) + } + + registeredUsersMu.Lock() + defer registeredUsersMu.Unlock() + registeredUsers[userName] = append(registeredUsers[userName], uid) + registeredGroups[groupName1] = append(registeredGroups[groupName1], gid1) + registeredGroups[groupName2] = append(registeredGroups[groupName2], gid2) + + cleanupsMu.Lock() + defer cleanupsMu.Unlock() + + if cleanupType == perUserCleanup { + cleanups = append(cleanups, userCleanup, groupCleanup1, groupCleanup2) + } + } + + testName := "update_user" + if doPreAuth { + testName = "maybe_finish_registration" + } + + for i := range 3 { + t.Run(fmt.Sprintf("%s%d", testName, i), userUpdate) + } + + t.Cleanup(func() { + cleanupsMu.Lock() + defer cleanupsMu.Unlock() + + t.Logf("Running cleanups for iteration %d", idx) + for _, cleanup := range cleanups { + cleanup() + } + }) + }) + } + + t.Cleanup(func() { + t.Log("Running final checks for cleanup mode " + fmt.Sprint(cleanupType)) + + registeredUsersMu.Lock() + defer registeredUsersMu.Unlock() + + if cleanupType == perTestCleanup { + // In such case we may have duplicate UIDs or GIDs since they + // may be duplicated after each test has finished. + // This is not actually a problem because in the real scenario + // the temporary entries owner (the user manager) should check + // that such IDs are already registered in the database before + // actually using them. + return + } + + uniqueIDs := make(map[uint32]string) + + for u, uids := range registeredUsers { + uids = slices.Compact(uids) + require.Len(t, uids, 1, "Only one UID should be registered for user %q", u) + + if cleanupType == perUserCleanup { + // In this case we only care about the fact of having registered only one + // UID for each user, although the UIDs may not be unique across all the + // tests, since the cleanup functions have deleted the temporary data. + // It's still important to test this case though, since it allows to ensure + // that the pre-check and registered user IDs are valid. + continue + } + + old, ok := uniqueIDs[uids[0]] + require.False(t, ok, "ID %d must be unique across entries, but it's used by both %q and %q", + uids[0], u, old) + uniqueIDs[uids[0]] = u + } + + for g, gids := range registeredGroups { + gids = slices.Compact(gids) + require.Len(t, gids, 1, "Only one GID should be registered for group %q", g) + + if cleanupType == perUserCleanup { + // In this case we only care about the fact of having registered only one + // UID for each user, although the UIDs may not be unique across all the + // tests, since the cleanup functions have deleted the temporary data. + // It's still important to test this case though, since it allows to ensure + // that the pre-check and registered user IDs are valid. + continue + } + + old, ok := uniqueIDs[gids[0]] + require.False(t, ok, "ID %d must be unique across entries, but it's used both %q and %q", + gids[0], g, old) + uniqueIDs[gids[0]] = g + } + }) + }) + } +} + func TestRegisterUser(t *testing.T) { t.Parallel() @@ -232,12 +439,6 @@ func TestRegisterUserAndGroupForUser(t *testing.T) { {name: "racing-user", wantUID: uidToGenerate + 1, wantGIDs: []uint32{gidToGenerate}}, }, }, - "Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_for_the_same_UID": { - users: []userTestData{ - {name: "racing-pre-checked-user", preAuth: true, simulateUIDRace: true}, - {name: "racing-pre-checked-user", preAuth: true, wantUID: uidToGenerate}, - }, - }, "Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID": { users: []userTestData{ {name: "racing-pre-checked-user", preAuth: true, simulateUIDRace: true}, diff --git a/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_registering_a_group_with_the_same_name_returning_the_same_GID b/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_registering_a_group_with_the_same_name_returning_the_same_GID new file mode 100644 index 0000000000..e992cfed83 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_registering_a_group_with_the_same_name_returning_the_same_GID @@ -0,0 +1,4 @@ +name: authd-temp-groups-test +gid: 12345 +users: [] +passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_registering_a_group_with_the_same_name_returning_the_same_GID_authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_registering_a_group_with_the_same_name_returning_the_same_GID_authd-temp-groups-test new file mode 100644 index 0000000000..e992cfed83 --- /dev/null +++ b/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_registering_a_group_with_the_same_name_returning_the_same_GID_authd-temp-groups-test @@ -0,0 +1,4 @@ +name: authd-temp-groups-test +gid: 12345 +users: [] +passwd: "" diff --git a/internal/users/tempentries/users.go b/internal/users/tempentries/users.go index cb24266deb..b6a80c3af1 100644 --- a/internal/users/tempentries/users.go +++ b/internal/users/tempentries/users.go @@ -2,23 +2,26 @@ package tempentries import ( "context" - "fmt" "sync" - "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/authd/log" ) +type referencedUserName struct { + refCount uint64 + uid uint32 +} + type idTracker struct { mu sync.Mutex ids map[uint32]struct{} - userNames map[string]uint32 + userNames map[string]*referencedUserName } func newIDTracker() *idTracker { return &idTracker{ ids: make(map[uint32]struct{}), - userNames: make(map[string]uint32), + userNames: make(map[string]*referencedUserName), } } @@ -50,56 +53,45 @@ func (r *idTracker) forgetID(id uint32) { delete(r.ids, id) } -func (r *idTracker) trackUser(name string, id uint32) (tracked bool, currentID uint32) { +func (r *idTracker) trackUser(name string, uid uint32) (tracked bool, currentID uint32) { r.mu.Lock() defer r.mu.Unlock() - if currentID, ok := r.userNames[name]; ok { + if current, ok := r.userNames[name]; ok { log.Debugf(context.Background(), - "Not tracking user name %q for UID %d, already tracked as %d", name, id, currentID) - return currentID == id, currentID + "Not tracking user name %q for UID %d, already tracked as %d", + name, uid, current.uid) + if current.uid != uid { + return false, current.uid + } + + current.refCount++ + return true, current.uid } - log.Debugf(context.Background(), "Tracking user name %q for UID %d", name, id) - r.userNames[name] = id - return true, id + log.Debugf(context.Background(), "Tracking user name %q for UID %d", name, uid) + r.userNames[name] = &referencedUserName{uid: uid, refCount: 1} + return true, uid } func (r *idTracker) forgetUser(name string) { r.mu.Lock() defer r.mu.Unlock() - if log.IsLevelEnabled(log.DebugLevel) { - id, ok := r.userNames[name] - log.Debugf(context.Background(), - "Forgetting tracked user name %q for UID %d: %v", name, id, ok) - } - delete(r.userNames, name) -} - -// uniqueNameAndUID returns true if the given UID is unique in the system. It returns false if the UID is already assigned to -// a user by any NSS source. -func uniqueNameAndUID(name string, uid uint32, passwdEntries []types.UserEntry, groupEntries []types.GroupEntry) (bool, error) { - for _, entry := range passwdEntries { - if entry.Name == name && entry.UID != uid { - // A user with the same name already exists, we can't register this temporary user. - log.Debugf(context.Background(), "Name %q already in use by UID %d", name, entry.UID) - return false, fmt.Errorf("user %q already exists", name) - } + current, ok := r.userNames[name] - if entry.UID == uid { - log.Debugf(context.Background(), "UID %d already in use by user %q, generating a new one", uid, entry.Name) - return false, nil - } + if !ok { + log.Debugf(context.Background(), "No tracked user for %q", name) + return } - for _, group := range groupEntries { - if group.GID == uid { - // A group with the same ID already exists, so we can't use that ID as the GID of the temporary user. - log.Debugf(context.Background(), "ID %d already in use by group %q", uid, group.Name) - return false, fmt.Errorf("group with GID %d already exists", uid) - } + if current.refCount > 1 { + current.refCount-- + return } - return true, nil + log.Debugf(context.Background(), + "Forgetting tracked user name %q for UID %d", name, current.uid) + + delete(r.userNames, name) } From 8c88e3f5c95eb3748e308246f265efb8e76c7686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 18 Jun 2025 23:37:00 +0200 Subject: [PATCH 0553/1670] users/manager: Avoid looking up NSS twice to check system entries We were checking if an user or a group entries were available in the system through native Go calls, although we can avoid doing this again since we've to fetch all the entries anyways for registration purposes These cases are already tested in the manager and PAM service tests, so this change is safe to do --- internal/users/manager.go | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/internal/users/manager.go b/internal/users/manager.go index b71e2f2014..137be5907f 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "os" - "os/user" "strings" "sync" "syscall" @@ -139,7 +138,6 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { return fmt.Errorf("could not get user %q: %w", u.Name, err) } if errors.Is(err, db.NoDataFoundError{}) { - // Check if the user exists on the system if err := userslocking.WriteRecLock(); err != nil { return err } @@ -149,13 +147,6 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { } }() - existingUser, err := user.Lookup(u.Name) - var unknownUserErr user.UnknownUserError - if !errors.As(err, &unknownUserErr) { - log.Errorf(context.Background(), "User already exists on the system: %+v", existingUser) - return fmt.Errorf("user %q already exists on the system (but not in this authd instance)", u.Name) - } - // The user does not exist, so we generate a unique UID for it or // we reuse the one that has been already pre-registered. var cleanup func() @@ -278,14 +269,8 @@ func (m *Manager) checkGroupNameConflict(name string, ugid string) error { } if errors.Is(err, db.NoDataFoundError{}) { - // The group does not exist in the database, check if it exists on the system. - existingGroup, err := user.LookupGroup(name) - var unknownGroupErr user.UnknownGroupError - if !errors.As(err, &unknownGroupErr) { - log.Errorf(context.Background(), "Group already exists on the system: %+v", existingGroup) - return fmt.Errorf("group %q already exists on the system (but not in this authd instance)", name) - } - // The group does not exist on the system, so we can proceed. + // The group does not exist in the database, the check in the system + // can be delayed to the registration point. return nil } From cbc58337a04bc5ad560551d2a3ab1f9db9fe8765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 20 Jun 2025 19:34:34 +0200 Subject: [PATCH 0554/1670] users/manager: Drop update user global locking As the previous changes we can be sure that: - Concurrent requests for different users will generate different UIDs and GIDs - Concurrent requests for the same user will return the same user ID - Concurrent requests for different users sharing the same group names will generate the same GIDs So we can be confident that we can allow UpdateUser requests to happen concurrently as it is already for the pre-auth users registration --- internal/users/manager.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/internal/users/manager.go b/internal/users/manager.go index 137be5907f..fd47573272 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -7,7 +7,6 @@ import ( "fmt" "os" "strings" - "sync" "syscall" "github.com/ubuntu/authd/internal/users/db" @@ -41,7 +40,6 @@ type Manager struct { db *db.Manager config Config temporaryRecords *tempentries.TemporaryRecords - updateUserMu sync.Mutex } type options struct { @@ -124,14 +122,6 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { var uid uint32 - // Prevent a TOCTOU race condition between the check for existence in our - // database and the registration of the temporary user/group records. - // This does not prevent a race condition where a user is created by some - // other NSS source, but that is handled by locking the users database if - // this happens. - m.updateUserMu.Lock() - defer m.updateUserMu.Unlock() - // Check if the user already exists in the database oldUser, err := m.db.UserByName(u.Name) if err != nil && !errors.Is(err, db.NoDataFoundError{}) { From 84616912e9a013cbf0d96e0c24a04a442ad333b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 20 Jun 2025 22:15:34 +0200 Subject: [PATCH 0555/1670] users/tempentries: Protect temporary entities and reduce the NSS lookups Change temporary entities public API so that all the actions that may involve locking the local entries are protected by a proxy struct type (TemporaryRecordsLocked) that can be generated by calling LockForChanges() that also returns a cleanup function that will unlock the structure again. By locking we lock the user entries database and we fetch them, so that they are ready to use as soon an operation would need them. This is safe since at this point no other NSS source is allowed to make changes to them, so they can be used until the unlock operation is not triggered. It's also a lot faster, compared to what we had before, in my machine the tempentries test TestRacingLockingActions: - Before: 2.283s - After: 0.109s Juts to mention that NSS lookups are really slow (and even slower if authd as to lookup itself). --- internal/users/manager.go | 37 +-- internal/users/manager_test.go | 9 +- internal/users/tempentries/groups_test.go | 4 +- internal/users/tempentries/preauth_test.go | 4 +- internal/users/tempentries/tempentries.go | 301 +++++++++++++----- .../users/tempentries/tempentries_test.go | 88 ++++- 6 files changed, 326 insertions(+), 117 deletions(-) diff --git a/internal/users/manager.go b/internal/users/manager.go index fd47573272..3c69d1cb2e 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -12,7 +12,6 @@ import ( "github.com/ubuntu/authd/internal/users/db" "github.com/ubuntu/authd/internal/users/idgenerator" "github.com/ubuntu/authd/internal/users/localentries" - userslocking "github.com/ubuntu/authd/internal/users/locking" "github.com/ubuntu/authd/internal/users/tempentries" "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/authd/log" @@ -128,19 +127,14 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { return fmt.Errorf("could not get user %q: %w", u.Name, err) } if errors.Is(err, db.NoDataFoundError{}) { - if err := userslocking.WriteRecLock(); err != nil { + records, recordsUnlock, err := m.temporaryRecords.LockForChanges() + if err != nil { return err } - defer func() { - if unlockErr := userslocking.WriteRecUnlock(); unlockErr != nil { - err = errors.Join(err, unlockErr) - } - }() + defer func() { err = errors.Join(err, recordsUnlock()) }() - // The user does not exist, so we generate a unique UID for it or - // we reuse the one that has been already pre-registered. var cleanup func() - uid, cleanup, err = m.temporaryRecords.RegisterUser(u.Name) + uid, cleanup, err = records.RegisterUser(u.Name) if err != nil { return fmt.Errorf("could not register user %q: %w", u.Name, err) } @@ -198,17 +192,14 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { } if len(newGroups) > 0 { - if err := userslocking.WriteRecLock(); err != nil { + records, recordsUnlock, err := m.temporaryRecords.LockForChanges() + if err != nil { return err } - defer func() { - if unlockErr := userslocking.WriteRecUnlock(); unlockErr != nil { - err = errors.Join(err, unlockErr) - } - }() + defer func() { err = errors.Join(err, recordsUnlock()) }() for _, g := range newGroups { - gid, cleanup, err := m.temporaryRecords.RegisterGroupForUser(uid, g.Name) + gid, cleanup, err := records.RegisterGroupForUser(uid, g.Name) if err != nil { return fmt.Errorf("could not generate GID for group %q: %v", g.Name, err) } @@ -453,15 +444,11 @@ func (m *Manager) RegisterUserPreAuth(name string) (uid uint32, err error) { return userRow.UID, nil } - if err := userslocking.WriteRecLock(); err != nil { + records, recordsUnlock, err := m.temporaryRecords.LockForChanges() + if err != nil { return 0, err } - defer func() { - if unlockErr := userslocking.WriteRecUnlock(); unlockErr != nil { - uid = 0 - err = errors.Join(err, unlockErr) - } - }() + defer func() { err = errors.Join(err, recordsUnlock()) }() - return m.temporaryRecords.RegisterPreAuthUser(name) + return records.RegisterPreAuthUser(name) } diff --git a/internal/users/manager_test.go b/internal/users/manager_test.go index 83dfffc2a5..9fad5665d9 100644 --- a/internal/users/manager_test.go +++ b/internal/users/manager_test.go @@ -447,7 +447,14 @@ func TestUserByIDAndName(t *testing.T) { m := newManagerForTests(t, dbDir) if tc.isTempUser { - tc.uid, err = m.TemporaryRecords().RegisterPreAuthUser("tempuser1") + records, recordsUnlock, err := m.TemporaryRecords().LockForChanges() + require.NoError(t, err, "LockForChanges should not return an error, but did") + t.Cleanup(func() { + err := recordsUnlock() + require.NoError(t, err, "recordsCleanup should not return an error, but did") + }) + + tc.uid, err = records.RegisterPreAuthUser("tempuser1") require.NoError(t, err, "RegisterUser should not return an error, but did") } diff --git a/internal/users/tempentries/groups_test.go b/internal/users/tempentries/groups_test.go index 8bd4d058c3..26ca92376e 100644 --- a/internal/users/tempentries/groups_test.go +++ b/internal/users/tempentries/groups_test.go @@ -73,7 +73,7 @@ func TestRegisterGroup(t *testing.T) { for idx, groupName := range tc.groups { t.Logf("Registering group %q", groupName) - gid, cleanup, err := records.RegisterGroupForUser(ownerUID, groupName) + gid, cleanup, err := records.registerGroupForUser(ownerUID, groupName) if tc.wantErr[idx] { require.Error(t, err, "RegisterGroup should return an error, but did not") continue @@ -181,7 +181,7 @@ func TestGroupByIDAndName(t *testing.T) { records := NewTemporaryRecords(idGeneratorMock) if tc.registerGroup { - gid, cleanup, err := records.RegisterGroupForUser(ownerUID, groupName) + gid, cleanup, err := records.registerGroupForUser(ownerUID, groupName) require.NoError(t, err, "RegisterGroup should not return an error, but did") require.Equal(t, gidToGenerate, gid, "GID should be the one generated by the IDGenerator") diff --git a/internal/users/tempentries/preauth_test.go b/internal/users/tempentries/preauth_test.go index 9add6e019a..5e2d788900 100644 --- a/internal/users/tempentries/preauth_test.go +++ b/internal/users/tempentries/preauth_test.go @@ -81,7 +81,7 @@ func TestPreAuthUser(t *testing.T) { for idx, loginName := range tc.users { t.Logf("Registering user %q", loginName) - uid, err := records.RegisterPreAuthUser(loginName) + uid, err := records.registerPreAuthUser(loginName) if tc.wantErr { require.Error(t, err, "RegisterPreAuthUser should return an error, but did not") continue @@ -169,7 +169,7 @@ func TestPreAuthUserByIDAndName(t *testing.T) { records := NewTemporaryRecords(idGeneratorMock) if tc.registerUser { - uid, err := records.RegisterPreAuthUser(loginName) + uid, err := records.registerPreAuthUser(loginName) require.NoError(t, err, "generatePreAuthUserID should not return an error, but did") require.Equal(t, uidToGenerate, uid, "UID should be the one generated by the IDGenerator") } diff --git a/internal/users/tempentries/tempentries.go b/internal/users/tempentries/tempentries.go index f278a36d4e..ba8f5f8253 100644 --- a/internal/users/tempentries/tempentries.go +++ b/internal/users/tempentries/tempentries.go @@ -6,9 +6,13 @@ import ( "errors" "fmt" "slices" + "sync" + "sync/atomic" + "github.com/ubuntu/authd/internal/testsdetection" "github.com/ubuntu/authd/internal/users/db" "github.com/ubuntu/authd/internal/users/localentries" + userslocking "github.com/ubuntu/authd/internal/users/locking" "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/authd/log" "github.com/ubuntu/decorate" @@ -29,7 +33,139 @@ type TemporaryRecords struct { *preAuthUserRecords *temporaryGroupRecords - idGenerator IDGenerator + idGenerator IDGenerator + lockedRecords atomic.Pointer[TemporaryRecordsWithLock] +} + +// TemporaryRecordsWithLock is the structure that allows to safely perform write +// changes to [TemporaryRecords] entries while the local entries database is +// locked. +type TemporaryRecordsWithLock struct { + tr *TemporaryRecords + + locksMu sync.RWMutex + locks uint64 + + cachedLocalPasswd []types.UserEntry + cachedLocalGroup []types.GroupEntry +} + +func (l *TemporaryRecordsWithLock) mustBeLocked() (cleanup func()) { + // While all the [temporaryRecordsLocked] operations are happening + // we need to keep a read lock on it, to prevent it being unlocked + // while some action is still ongoing. + l.locksMu.RLock() + cleanup = l.locksMu.RUnlock + + if l.locks == 0 { + defer cleanup() + panic("locked groups are not locked!") + } + + return cleanup +} + +// RegisterUser registers a temporary user with a unique UID. +// +// Returns the generated UID and a cleanup function that should be called to +// remove the temporary user once the user is added to the database. +func (l *TemporaryRecordsWithLock) RegisterUser(name string) (uid uint32, cleanup func(), err error) { + unlock := l.mustBeLocked() + defer unlock() + + return l.tr.registerUser(name) +} + +// RegisterPreAuthUser registers a temporary user with a unique UID in our NSS +// handler (in memory, not in the database). +// +// The temporary user record is removed when [RegisterUser] is called with the +// same username. +// +// This method is called when a user logs in for the first time via SSH, in +// which case sshd checks if the user exists on the system (before +// authentication), and denies the login if the user does not exist. +// We pretend that the user exists by creating this temporary user record, +// which is converted into a permanent user record when [RegisterUser] is called +// after the user authenticated successfully. +// +// Returns the generated UID. +func (l *TemporaryRecordsWithLock) RegisterPreAuthUser(loginName string) (uid uint32, err error) { + unlock := l.mustBeLocked() + defer unlock() + + return l.tr.registerPreAuthUser(loginName) +} + +// RegisterGroupForUser registers a temporary group with a unique GID in +// memory for the provided UID. +// +// Returns the generated GID and a cleanup function that should be called to +// remove the temporary group once the group was added to the database. +func (l *TemporaryRecordsWithLock) RegisterGroupForUser(uid uint32, name string) (gid uint32, cleanup func(), err error) { + unlock := l.mustBeLocked() + defer unlock() + + return l.tr.registerGroupForUser(uid, name) +} + +func (l *TemporaryRecordsWithLock) lock() (err error) { + defer decorate.OnError(&err, "could not lock temporary records") + + l.locksMu.Lock() + defer l.locksMu.Unlock() + + if l.locks != 0 { + l.locks++ + return nil + } + + log.Debug(context.Background(), "Locking temporary records ") + + l.cachedLocalPasswd, err = localentries.GetPasswdEntries() + if err != nil { + return fmt.Errorf("failed to get passwd entries: %w", err) + } + l.cachedLocalGroup, err = localentries.GetGroupEntries() + if err != nil { + return fmt.Errorf("failed to get group entries: %w", err) + } + + if err := userslocking.WriteRecLock(); err != nil { + return err + } + + l.locks++ + + return nil +} + +func (l *TemporaryRecordsWithLock) unlock() (err error) { + defer decorate.OnError(&err, "could not unlock temporary records") + + l.locksMu.Lock() + defer l.locksMu.Unlock() + + if l.locks == 0 { + return errors.New("temporary records are already unlocked") + } + + if l.locks != 1 { + l.locks-- + return nil + } + + log.Debug(context.Background(), "Unlocking temporary records") + + if err := userslocking.WriteRecUnlock(); err != nil { + return err + } + + l.cachedLocalPasswd = nil + l.cachedLocalGroup = nil + l.locks-- + + return nil } // NewTemporaryRecords creates a new TemporaryRecords. @@ -52,23 +188,45 @@ func (r *TemporaryRecords) UserByName(name string) (types.UserEntry, error) { return r.preAuthUserRecords.userByName(name) } -// RegisterUser registers a temporary user with a unique UID in our NSS handler (in memory, not in the database). +// LockForChanges locks the underneath system user entries databases and returns +// a [temporaryRecordsLocked] that allows to perform write-modifications to the +// user records in a way that is safe for the environment, and preventing races +// with other NSS modules. // -// Returns the generated UID and a cleanup function that should be called to -// remove the temporary user once the user is added to the database. -func (r *TemporaryRecords) RegisterUser(name string) (uid uint32, cleanup func(), err error) { - passwdEntries, err := localentries.GetPasswdEntries() - if err != nil { - return 0, nil, fmt.Errorf("could not check user, failed to get passwd entries: %w", err) +//nolint:revive,nolintlint // [temporaryRecordsLocked] is not a type we want to be able to use outside of this package +func (r *TemporaryRecords) LockForChanges() (tra *TemporaryRecordsWithLock, unlock func() error, err error) { + defer decorate.OnError(&err, "failed to lock for changes") + + lockedRecords := &TemporaryRecordsWithLock{tr: r} + + for { + if r.lockedRecords.CompareAndSwap(nil, lockedRecords) { + break + } + + // We've old locked records, let's just return these once we're sure! + l := r.lockedRecords.Load() + if l == nil { + continue + } + if err := l.lock(); err != nil { + return nil, nil, err + } + return l, l.unlock, nil } - if !isUniqueSystemUserName(name, passwdEntries) { - return 0, nil, fmt.Errorf("user %q already exists", name) + if err := lockedRecords.lock(); err != nil { + return nil, nil, err } - groupEntries, err := localentries.GetGroupEntries() - if err != nil { - return 0, nil, fmt.Errorf("could not check user, failed to get group entries: %w", err) + return lockedRecords, lockedRecords.unlock, nil +} + +func (r *TemporaryRecords) registerUser(name string) (uid uint32, cleanup func(), err error) { + defer decorate.OnError(&err, "failed to register user %q", name) + + if !r.isUniqueSystemUserName(name) { + return 0, nil, fmt.Errorf("user %q already exists", name) } // Check if there is a pre-auth user with the same login name. @@ -96,7 +254,7 @@ func (r *TemporaryRecords) RegisterUser(name string) (uid uint32, cleanup func() // if the UID is unique, since that's something we did while registering it, and we're // currently locked, so nothing else can add another user with such ID, but we do to // double check it, just in case. - if !isUniqueSystemID(user.UID, passwdEntries, groupEntries) { + if !r.isUniqueSystemID(user.UID) { cleanup() return 0, nil, fmt.Errorf("UID (%d) or name (%q) from pre-auth user are not unique", user.UID, name) } @@ -111,7 +269,7 @@ func (r *TemporaryRecords) RegisterUser(name string) (uid uint32, cleanup func() return 0, nil, err } - if unique := r.maybeTrackUniqueID(uid, passwdEntries, groupEntries); !unique { + if unique := r.maybeTrackUniqueID(uid); !unique { // If the UID is not unique, generate a new one in the next iteration. continue } @@ -132,17 +290,7 @@ func (r *TemporaryRecords) RegisterUser(name string) (uid uint32, cleanup func() } } -// RegisterPreAuthUser registers a temporary user with a unique UID in our NSS handler (in memory, not in the database). -// -// The temporary user record is removed when UpdateUser is called with the same username. -// -// This method is called when a user logs in for the first time via SSH, in which case sshd checks if the user exists on -// the system (before authentication), and denies the login if the user does not exist. We pretend that the user exists -// by creating this temporary user record, which is converted into a permanent user record when UpdateUser is called -// after the user authenticated successfully. -// -// Returns the generated UID. -func (r *TemporaryRecords) RegisterPreAuthUser(loginName string) (uid uint32, err error) { +func (r *TemporaryRecords) registerPreAuthUser(loginName string) (uid uint32, err error) { r.rwMu.Lock() defer r.rwMu.Unlock() @@ -151,28 +299,18 @@ func (r *TemporaryRecords) RegisterPreAuthUser(loginName string) (uid uint32, er return uid, nil } - passwdEntries, err := localentries.GetPasswdEntries() - if err != nil { - return 0, fmt.Errorf("could not check user, failed to get passwd entries: %w", err) - } - - if !isUniqueSystemUserName(loginName, passwdEntries) { + if !r.isUniqueSystemUserName(loginName) { log.Errorf(context.Background(), "User already exists on the system: %+v", loginName) return 0, fmt.Errorf("user %q already exists on the system", loginName) } - groupEntries, err := localentries.GetGroupEntries() - if err != nil { - return 0, fmt.Errorf("could not check user, failed to get group entries: %w", err) - } - for { uid, err := r.preAuthUserRecords.generatePreAuthUserID(loginName) if err != nil { return 0, err } - if unique := r.maybeTrackUniqueID(uid, passwdEntries, groupEntries); !unique { + if unique := r.maybeTrackUniqueID(uid); !unique { // If the UID is not unique, generate a new one in the next iteration. continue } @@ -195,30 +333,45 @@ func (r *TemporaryRecords) RegisterPreAuthUser(loginName string) (uid uint32, er } } -// RegisterGroupForUser registers a temporary group with a unique GID in -// memory for the provided UID. -// -// Returns the generated GID and a cleanup function that should be called to -// remove the temporary group once the group was added to the database. -func (r *TemporaryRecords) RegisterGroupForUser(uid uint32, name string) (gid uint32, cleanup func(), err error) { - defer decorate.OnError(&err, "failed to register group %q for user ID %d", name, uid) +func (r *TemporaryRecords) passwdEntries() []types.UserEntry { + l := r.lockedRecords.Load() + if l == nil { + testsdetection.MustBeTesting() - groupEntries, err := localentries.GetGroupEntries() - if err != nil { - return 0, nil, fmt.Errorf("failed to get group entries: %w", err) + entries, err := localentries.GetPasswdEntries() + if err != nil { + panic(fmt.Sprintf("Failed get local passwd: %v", err)) + } + return entries + } + + return l.cachedLocalPasswd +} + +func (r *TemporaryRecords) groupEntries() []types.GroupEntry { + l := r.lockedRecords.Load() + if l == nil { + testsdetection.MustBeTesting() + + entries, err := localentries.GetGroupEntries() + if err != nil { + panic(fmt.Sprintf("Failed get local groups: %v", err)) + } + return entries } - if slices.ContainsFunc(groupEntries, func(g types.GroupEntry) bool { + return l.cachedLocalGroup +} + +func (r *TemporaryRecords) registerGroupForUser(uid uint32, name string) (gid uint32, cleanup func(), err error) { + defer decorate.OnError(&err, "failed to register group %q for user ID %d", name, uid) + + if slices.ContainsFunc(r.groupEntries(), func(g types.GroupEntry) bool { return g.Name == name }) { return 0, nil, fmt.Errorf("group %q already exists", name) } - passwdEntries, err := localentries.GetPasswdEntries() - if err != nil { - return 0, nil, fmt.Errorf("failed to get passwd entries: %w", err) - } - r.temporaryGroupRecords.mu.Lock() defer r.temporaryGroupRecords.mu.Unlock() @@ -246,7 +399,7 @@ func (r *TemporaryRecords) RegisterGroupForUser(uid uint32, name string) (gid ui continue } - if unique := r.maybeTrackUniqueID(gid, passwdEntries, groupEntries); !unique { + if unique := r.maybeTrackUniqueID(gid); !unique { // If the GID is not unique, generate a new one in the next iteration. continue } @@ -257,42 +410,42 @@ func (r *TemporaryRecords) RegisterGroupForUser(uid uint32, name string) (gid ui } } -func (r *TemporaryRecords) maybeTrackUniqueID(id uint32, passwdEntries []types.UserEntry, groupEntries []types.GroupEntry) (unique bool) { +func (r *TemporaryRecords) maybeTrackUniqueID(id uint32) (unique bool) { defer func() { if !unique { log.Debugf(context.TODO(), "ID %d is not unique in this system", id) } }() - if !isUniqueSystemID(id, passwdEntries, groupEntries) { + if !r.isUniqueSystemID(id) { return false } return r.idTracker.trackID(id) } -func isUniqueSystemID(uid uint32, passwdEntries []types.UserEntry, groupEntries []types.GroupEntry) bool { - if slices.ContainsFunc(passwdEntries, func(p types.UserEntry) (found bool) { - found = p.UID == uid - if found { - log.Debugf(context.Background(), "ID %d already in use by user %q", uid, p.Name) - } - return found - }) { +func (r *TemporaryRecords) isUniqueSystemID(id uint32) bool { + if idx := slices.IndexFunc(r.passwdEntries(), func(p types.UserEntry) (found bool) { + return p.UID == id + }); idx != -1 { + log.Debugf(context.Background(), "ID %d already in use by user %q", + id, r.passwdEntries()[idx].Name) return false } - return !slices.ContainsFunc(groupEntries, func(g types.GroupEntry) (found bool) { - found = g.GID == uid - if found { - log.Debugf(context.Background(), "ID %d already in use by group %q", uid, g.Name) - } - return found - }) + if idx := slices.IndexFunc(r.groupEntries(), func(g types.GroupEntry) (found bool) { + return g.GID == id + }); idx != -1 { + log.Debugf(context.Background(), "ID %d already in use by user %q", + id, r.groupEntries()[idx].Name) + return false + } + + return true } -func isUniqueSystemUserName(name string, passwdEntries []types.UserEntry) bool { - return !slices.ContainsFunc(passwdEntries, func(p types.UserEntry) bool { +func (r *TemporaryRecords) isUniqueSystemUserName(name string) bool { + return !slices.ContainsFunc(r.passwdEntries(), func(p types.UserEntry) bool { return p.Name == name }) } diff --git a/internal/users/tempentries/tempentries_test.go b/internal/users/tempentries/tempentries_test.go index ae5c20fdbb..02b2ac9224 100644 --- a/internal/users/tempentries/tempentries_test.go +++ b/internal/users/tempentries/tempentries_test.go @@ -10,9 +10,48 @@ import ( "github.com/stretchr/testify/require" "github.com/ubuntu/authd/internal/testutils/golden" "github.com/ubuntu/authd/internal/users/idgenerator" + userslocking "github.com/ubuntu/authd/internal/users/locking" "github.com/ubuntu/authd/internal/users/types" + "github.com/ubuntu/authd/log" ) +func TestLockedInvalidActions(t *testing.T) { + t.Parallel() + + require.Panics(t, func() { _, _ = (&TemporaryRecordsWithLock{}).RegisterPreAuthUser("foobar") }, + "RegisterPreAuthUser should panic but did not") + require.Panics(t, func() { _, _, _ = (&TemporaryRecordsWithLock{}).RegisterUser("foobar") }, + "RegisterUser should panic but did not") + require.Panics(t, func() { _, _, _ = (&TemporaryRecordsWithLock{}).RegisterGroupForUser(123, "foobar") }, + "RegisterGroupForUser should panic but did not") + + tmpRecords := NewTemporaryRecords(&idgenerator.IDGenerator{}) + records, recordsUnlock, err := tmpRecords.LockForChanges() + require.NoError(t, err, "Setup: failed to lock the temporary records") + err = recordsUnlock() + require.NoError(t, err, "recordsUnlock should not fail to unlock the temporary records") + + err = recordsUnlock() + require.Error(t, err, "Cleaning up twice should fail") + + require.Panics(t, func() { _, _ = records.RegisterPreAuthUser("foobar") }, + "RegisterPreAuthUser should panic but did not") + require.Panics(t, func() { _, _, _ = records.RegisterUser("foobar") }, + "RegisterUser should panic but did not") + require.Panics(t, func() { _, _, _ = records.RegisterGroupForUser(123, "foobar") }, + "RegisterGroupForUser should panic but did not") + + // This is to ensure that we're in a good state, despite the actions above + for range 10 { + _, recordsUnlock, err := tmpRecords.LockForChanges() + require.NoError(t, err, "Failed to lock the temporary records") + defer func() { + err := recordsUnlock() + require.NoError(t, err, "recordsUnlock should not fail to unlock the temporary records") + }() + } +} + func TestRacingLockingActions(t *testing.T) { t.Parallel() @@ -51,7 +90,13 @@ func TestRacingLockingActions(t *testing.T) { t.Run(fmt.Sprintf("iteration_%d", idx), func(t *testing.T) { t.Parallel() - records := tmpRecords + records, recordsUnlock, err := tmpRecords.LockForChanges() + require.NoError(t, err, "Setup: failed to lock the temporary records") + t.Cleanup(func() { + err = recordsUnlock() + require.NoError(t, err, "recordsUnlock should not fail to unlock the temporary records") + }) + doPreAuth := idx%3 == 0 userName := fmt.Sprintf("authd-test-user%d", idx) @@ -280,7 +325,7 @@ func TestRegisterUser(t *testing.T) { require.NoError(t, err, "addPreAuthUser should not return an error, but did") } - uid, cleanup, err := records.RegisterUser(tc.userName) + uid, cleanup, err := records.registerUser(tc.userName) if tc.wantErr { require.Error(t, err, "RegisterUser should return an error, but did not") return @@ -527,8 +572,16 @@ func TestRegisterUserAndGroupForUser(t *testing.T) { replacingPreAuthUser = err == nil } + records, recordsUnlock, err := records.LockForChanges() + require.NoError(t, err, "LockForChanges should not return an error, but did") + t.Cleanup(func() { + err := recordsUnlock() + require.NoError(t, err, "recordsCleanup should not return an error, but did") + }) + + internalRecords := records.tr + var uid uint32 - var err error var cleanup func() if uc.preAuth { uid, err = records.RegisterPreAuthUser(uc.name) @@ -561,7 +614,7 @@ func TestRegisterUserAndGroupForUser(t *testing.T) { } require.Equal(t, uc.wantUID, uid, "%q UID is not matching expected value", uc.name) - require.Equal(t, wantRegisteredUsers, len(records.users), + require.Equal(t, wantRegisteredUsers, len(internalRecords.users), "Number of pre-auth registered, users should be %d", wantRegisteredUsers) if isDuplicated { @@ -579,13 +632,13 @@ func TestRegisterUserAndGroupForUser(t *testing.T) { if replacingPreAuthUser { wantRegisteredUsers-- - require.Equal(t, wantRegisteredUsers, len(records.users), + require.Equal(t, wantRegisteredUsers, len(internalRecords.users), "Number of pre-auth registered, users should be %d", wantRegisteredUsers) } } // Check that the user was registered - user, err := records.userByLogin(uc.name) + user, err := internalRecords.userByLogin(uc.name) if uc.preAuth || (replacingPreAuthUser && !uc.runCleanup) { require.NoError(t, err, "UserByID should not return an error, but did") } else { @@ -605,7 +658,7 @@ func TestRegisterUserAndGroupForUser(t *testing.T) { if uc.simulateUIDRace { t.Logf("Dropping the registered UID %d for %q", uid, uc.name) - delete(records.preAuthUserRecords.users, uid) + delete(internalRecords.preAuthUserRecords.users, uid) lastCleanupIdx = idx + 1 registeredUIDs = slices.DeleteFunc(registeredUIDs, @@ -615,7 +668,7 @@ func TestRegisterUserAndGroupForUser(t *testing.T) { if uc.simulateNameRace { t.Logf("Dropping the registered login name %q for %d", uc.name, uid) - delete(records.preAuthUserRecords.uidByLogin, uc.name) + delete(internalRecords.preAuthUserRecords.uidByLogin, uc.name) } // We don't have groups registration for the pre-check user. @@ -626,14 +679,14 @@ func TestRegisterUserAndGroupForUser(t *testing.T) { if uc.simulateUIDRace { t.Logf("Dropping the registered UID %d for %q", uid, uc.name) - delete(records.idTracker.ids, uid) + delete(internalRecords.idTracker.ids, uid) continue } if uc.simulateNameRace { // We drop the registered name to check if the logic can handle such case. t.Logf("Dropping the registered user name %q for %d", uc.name, uid) - delete(records.idTracker.userNames, uc.name) + delete(internalRecords.idTracker.userNames, uc.name) lastCleanupIdx = idx + 1 continue } @@ -681,11 +734,11 @@ func TestRegisterUserAndGroupForUser(t *testing.T) { require.NoError(t, err, "RegisterGroup should not return an error, but did") require.Equal(t, wantGID, gid, "%q GID is not matching expected value", groupName) - require.Equal(t, wantRegisteredGroups, len(records.groups), + require.Equal(t, wantRegisteredGroups, len(internalRecords.groups), "Number of groups registered, users should be %d", wantRegisteredGroups) // Check that the temporary group was created - group, err := records.GroupByID(gid) + group, err := internalRecords.GroupByID(gid) require.NoError(t, err, "GroupByID should not return an error, but did") groupSuffix := groupName @@ -734,7 +787,7 @@ func TestUserByIDAndName(t *testing.T) { records := NewTemporaryRecords(idGeneratorMock) if tc.registerUser { - uid, cleanup, err := records.RegisterUser(userName) + uid, cleanup, err := records.registerUser(userName) require.NoError(t, err, "RegisterUser should not return an error, but did") require.Equal(t, uidToGenerate, uid, "UID should be the one generated by the IDGenerator") t.Cleanup(cleanup) @@ -763,3 +816,12 @@ func checkUser(t *testing.T, user types.UserEntry, options ...golden.Option) { golden.CheckOrUpdateYAML(t, user, options...) } + +func TestMain(m *testing.M) { + log.SetLevel(log.DebugLevel) + + userslocking.Z_ForTests_OverrideLocking() + defer userslocking.Z_ForTests_RestoreLocking() + + m.Run() +} From 1dbc909672a926973967ae5cd7eb27f96feb7511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 27 Jun 2025 05:15:16 +0200 Subject: [PATCH 0556/1670] users/tempentries: Limit the maximum amount of times we look for a valid ID In case we're short on UIDs/GIDs it's better to return an error than just hang forever trying to figure one that we can't get --- internal/users/tempentries/groups_test.go | 4 ++++ internal/users/tempentries/preauth_test.go | 4 ++++ internal/users/tempentries/tempentries.go | 19 ++++++++++++++++--- .../users/tempentries/tempentries_test.go | 4 ++++ 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/internal/users/tempentries/groups_test.go b/internal/users/tempentries/groups_test.go index 26ca92376e..f72a06df47 100644 --- a/internal/users/tempentries/groups_test.go +++ b/internal/users/tempentries/groups_test.go @@ -40,6 +40,10 @@ func TestRegisterGroup(t *testing.T) { }, "Error_when_name_is_already_in_use": {groups: []string{"root"}, wantErr: []bool{true}}, + "Error_when_a_valid_GID_cannot_be_found": { + gidsToGenerate: make([]uint32, maxIDGenerateIterations*10), + wantErr: []bool{true}, + }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { diff --git a/internal/users/tempentries/preauth_test.go b/internal/users/tempentries/preauth_test.go index 5e2d788900..7fa4464a77 100644 --- a/internal/users/tempentries/preauth_test.go +++ b/internal/users/tempentries/preauth_test.go @@ -44,6 +44,10 @@ func TestPreAuthUser(t *testing.T) { "No_error_when_registering_a_pre-auth_user_with_the_same_name": {users: []string{defaultLoginName, defaultLoginName}}, "Error_when_maximum_number_of_pre-auth_users_is_reached": {maxUsers: true, wantErr: true}, + "Error_when_a_valid_UID_cannot_be_found": { + uidsToGenerate: make([]uint32, maxIDGenerateIterations*10), + wantErr: true, + }, } for name, tc := range tests { diff --git a/internal/users/tempentries/tempentries.go b/internal/users/tempentries/tempentries.go index ba8f5f8253..378e4654b9 100644 --- a/internal/users/tempentries/tempentries.go +++ b/internal/users/tempentries/tempentries.go @@ -18,6 +18,10 @@ import ( "github.com/ubuntu/decorate" ) +// Avoid to loop forever if we can't find an UID for the user, it's just better +// to fail after a limit is reached than hang or crash. +const maxIDGenerateIterations = 256 + // NoDataFoundError is the error returned when no entry is found in the database. type NoDataFoundError = db.NoDataFoundError @@ -263,7 +267,7 @@ func (r *TemporaryRecords) registerUser(name string) (uid uint32, cleanup func() } // Generate a UID until we find a unique one - for { + for range maxIDGenerateIterations { uid, err = r.idGenerator.GenerateUID() if err != nil { return 0, nil, err @@ -288,6 +292,9 @@ func (r *TemporaryRecords) registerUser(name string) (uid uint32, cleanup func() r.idTracker.forgetUser(name) }, nil } + + return 0, nil, fmt.Errorf("failed to find a valid UID after %d attempts", + maxIDGenerateIterations) } func (r *TemporaryRecords) registerPreAuthUser(loginName string) (uid uint32, err error) { @@ -304,7 +311,7 @@ func (r *TemporaryRecords) registerPreAuthUser(loginName string) (uid uint32, er return 0, fmt.Errorf("user %q already exists on the system", loginName) } - for { + for range maxIDGenerateIterations { uid, err := r.preAuthUserRecords.generatePreAuthUserID(loginName) if err != nil { return 0, err @@ -331,6 +338,9 @@ func (r *TemporaryRecords) registerPreAuthUser(loginName string) (uid uint32, er return uid, nil } + + return 0, fmt.Errorf("failed to find a valid UID after %d attempts", + maxIDGenerateIterations) } func (r *TemporaryRecords) passwdEntries() []types.UserEntry { @@ -388,7 +398,7 @@ func (r *TemporaryRecords) registerGroupForUser(uid uint32, name string) (gid ui return gid, groupCleanup, nil } - for { + for range maxIDGenerateIterations { gid, err = r.temporaryGroupRecords.generateGroupID() if err != nil { return 0, nil, err @@ -408,6 +418,9 @@ func (r *TemporaryRecords) registerGroupForUser(uid uint32, name string) (gid ui return gid, groupCleanup, nil } + + return 0, nil, fmt.Errorf("failed to find a valid GID after %d attempts", + maxIDGenerateIterations) } func (r *TemporaryRecords) maybeTrackUniqueID(id uint32) (unique bool) { diff --git a/internal/users/tempentries/tempentries_test.go b/internal/users/tempentries/tempentries_test.go index 02b2ac9224..e899f807c6 100644 --- a/internal/users/tempentries/tempentries_test.go +++ b/internal/users/tempentries/tempentries_test.go @@ -293,6 +293,10 @@ func TestRegisterUser(t *testing.T) { userName: "root", wantErr: true, }, + "Error_when_a_valid_UID_cannot_be_found": { + uidsToGenerate: make([]uint32, maxIDGenerateIterations*10), + wantErr: true, + }, "Error_when_pre-auth_UID_is_not_unique": { replacesPreAuthUser: true, preAuthUIDAlreadyExists: true, From 5863e39fab07aecb7786512747a1f29951d62e70 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 26 Jun 2025 17:14:43 +0200 Subject: [PATCH 0557/1670] Fix TestPreAuthUser and TestPreAuthUserByIDAndName The tests failed on my system where I have a user named "test" defined in my /etc/passwd --- internal/users/tempentries/preauth_test.go | 4 ++-- ..._error_when_registering_a_pre-auth_user_with_the_same_name | 2 +- ...ring_a_pre-auth_user_with_the_same_name_TestPreAuthUser_1} | 2 +- .../TestPreAuthUser/Successfully_register_a_pre-auth_user | 2 +- .../Successfully_register_a_pre-auth_user_again | 2 +- ...essfully_register_a_pre-auth_user_again_TestPreAuthUser_1} | 2 +- ...pre-auth_user_if_the_first_generated_UID_is_already_in_use | 2 +- ...auth_user_if_the_first_generated_UID_is_already_registered | 2 +- .../Successfully_get_a_user_by_ID_and_name | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) rename internal/users/tempentries/testdata/golden/TestPreAuthUser/{No_error_when_registering_a_pre-auth_user_with_the_same_name_test_1 => No_error_when_registering_a_pre-auth_user_with_the_same_name_TestPreAuthUser_1} (81%) rename internal/users/tempentries/testdata/golden/TestPreAuthUser/{Successfully_register_a_pre-auth_user_again_test_1 => Successfully_register_a_pre-auth_user_again_TestPreAuthUser_1} (81%) diff --git a/internal/users/tempentries/preauth_test.go b/internal/users/tempentries/preauth_test.go index 7fa4464a77..38a9f26675 100644 --- a/internal/users/tempentries/preauth_test.go +++ b/internal/users/tempentries/preauth_test.go @@ -15,7 +15,7 @@ import ( func TestPreAuthUser(t *testing.T) { t.Parallel() - defaultLoginName := "test" + defaultLoginName := t.Name() uidToGenerate := uint32(12345) tests := map[string]struct { @@ -150,7 +150,7 @@ func TestPreAuthUser(t *testing.T) { func TestPreAuthUserByIDAndName(t *testing.T) { t.Parallel() - loginName := "test" + loginName := t.Name() uidToGenerate := uint32(12345) tests := map[string]struct { diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUser/No_error_when_registering_a_pre-auth_user_with_the_same_name b/internal/users/tempentries/testdata/golden/TestPreAuthUser/No_error_when_registering_a_pre-auth_user_with_the_same_name index 8b0eea6888..31ef5bbe2a 100644 --- a/internal/users/tempentries/testdata/golden/TestPreAuthUser/No_error_when_registering_a_pre-auth_user_with_the_same_name +++ b/internal/users/tempentries/testdata/golden/TestPreAuthUser/No_error_when_registering_a_pre-auth_user_with_the_same_name @@ -1,6 +1,6 @@ name: authd-pre-auth-user-XXXXXXXX uid: 12345 gid: 12345 -gecos: test +gecos: TestPreAuthUser dir: /nonexistent shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUser/No_error_when_registering_a_pre-auth_user_with_the_same_name_test_1 b/internal/users/tempentries/testdata/golden/TestPreAuthUser/No_error_when_registering_a_pre-auth_user_with_the_same_name_TestPreAuthUser_1 similarity index 81% rename from internal/users/tempentries/testdata/golden/TestPreAuthUser/No_error_when_registering_a_pre-auth_user_with_the_same_name_test_1 rename to internal/users/tempentries/testdata/golden/TestPreAuthUser/No_error_when_registering_a_pre-auth_user_with_the_same_name_TestPreAuthUser_1 index 8b0eea6888..31ef5bbe2a 100644 --- a/internal/users/tempentries/testdata/golden/TestPreAuthUser/No_error_when_registering_a_pre-auth_user_with_the_same_name_test_1 +++ b/internal/users/tempentries/testdata/golden/TestPreAuthUser/No_error_when_registering_a_pre-auth_user_with_the_same_name_TestPreAuthUser_1 @@ -1,6 +1,6 @@ name: authd-pre-auth-user-XXXXXXXX uid: 12345 gid: 12345 -gecos: test +gecos: TestPreAuthUser dir: /nonexistent shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user index 8b0eea6888..31ef5bbe2a 100644 --- a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user +++ b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user @@ -1,6 +1,6 @@ name: authd-pre-auth-user-XXXXXXXX uid: 12345 gid: 12345 -gecos: test +gecos: TestPreAuthUser dir: /nonexistent shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again index 8b0eea6888..31ef5bbe2a 100644 --- a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again +++ b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again @@ -1,6 +1,6 @@ name: authd-pre-auth-user-XXXXXXXX uid: 12345 gid: 12345 -gecos: test +gecos: TestPreAuthUser dir: /nonexistent shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again_test_1 b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again_TestPreAuthUser_1 similarity index 81% rename from internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again_test_1 rename to internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again_TestPreAuthUser_1 index 8b0eea6888..31ef5bbe2a 100644 --- a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again_test_1 +++ b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again_TestPreAuthUser_1 @@ -1,6 +1,6 @@ name: authd-pre-auth-user-XXXXXXXX uid: 12345 gid: 12345 -gecos: test +gecos: TestPreAuthUser dir: /nonexistent shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_in_use b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_in_use index 8b0eea6888..31ef5bbe2a 100644 --- a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_in_use +++ b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_in_use @@ -1,6 +1,6 @@ name: authd-pre-auth-user-XXXXXXXX uid: 12345 gid: 12345 -gecos: test +gecos: TestPreAuthUser dir: /nonexistent shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_registered b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_registered index 8b0eea6888..31ef5bbe2a 100644 --- a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_registered +++ b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_registered @@ -1,6 +1,6 @@ name: authd-pre-auth-user-XXXXXXXX uid: 12345 gid: 12345 -gecos: test +gecos: TestPreAuthUser dir: /nonexistent shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUserByIDAndName/Successfully_get_a_user_by_ID_and_name b/internal/users/tempentries/testdata/golden/TestPreAuthUserByIDAndName/Successfully_get_a_user_by_ID_and_name index 8b0eea6888..9f16ba80a3 100644 --- a/internal/users/tempentries/testdata/golden/TestPreAuthUserByIDAndName/Successfully_get_a_user_by_ID_and_name +++ b/internal/users/tempentries/testdata/golden/TestPreAuthUserByIDAndName/Successfully_get_a_user_by_ID_and_name @@ -1,6 +1,6 @@ name: authd-pre-auth-user-XXXXXXXX uid: 12345 gid: 12345 -gecos: test +gecos: TestPreAuthUserByIDAndName dir: /nonexistent shell: /usr/sbin/nologin From d7aafed05760d730481087479a212174fa61db94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 26 Jun 2025 03:47:17 +0200 Subject: [PATCH 0558/1670] users/manager: Try more to find unique UIDs/GIDs In case we have concurrent requests, as in the initial test, we may end up failing updating the user, as we may have generate an UID/GID that is being registered in the DB. So in case this fails, let's just retry again, making our machinery to look again for a new GID/UID if possible or to reuse the one we have already set. We still have issues in running concurrent updates for the same user though, but this is due to the database, and so maybe to be handled in a different place: 2025/06/26 03:54:31 DEBUG Updating local groups for user "authd-test-maybe-pre-check-user93", new groups: [authd-test-group93 authd-test-other-group93], old groups: [] 2025/06/26 03:54:31 DEBUG Adding "authd-test-maybe-pre-check-user93" to local groups: [authd-test-group93 authd-test-other-group93] 2025/06/26 03:54:31 DEBUG Removing "authd-test-maybe-pre-check-user93" from local groups: [] 2025/06/26 03:54:31 DEBUG Updating entry of user "authd-test-maybe-pre-check-user93" (UID: 70) 2025/06/26 03:54:31 DEBUG Nothing to do, groups are equal 2025/06/26 03:54:31 DEBUG Forgetting tracked user name "authd-test-maybe-pre-check-user93" for UID 70: true 2025/06/26 03:54:31 DEBUG Updating user authd-test-maybe-pre-check-user93 2025/06/26 03:54:31 DEBUG Updating entry of group "authd-test-maybe-pre-check-user93" ({Name:authd-test-maybe-pre-check-user93 GID:70 UGID:authd-test-maybe-pre-check-user93}) 2025/06/26 03:54:31 DEBUG Adding user 70 to group 70 failed to update user "authd-test-maybe-pre-check-user93": failed to add user to group: FOREIGN KEY constraint failed (user with UID 70 does not exist) (group with GID 70 does not exist) So handle such error for now as a retry situation, although the database should likely fail early and being locked in any case. --- internal/users/db/update.go | 17 +- internal/users/export_test.go | 5 + internal/users/manager.go | 45 +++- internal/users/manager_test.go | 247 ++++++++++++++++++ ...e_user_and_group_with_matching_gid.db.yaml | 17 ++ 5 files changed, 325 insertions(+), 6 deletions(-) create mode 100644 internal/users/testdata/db/one_user_and_group_with_matching_gid.db.yaml diff --git a/internal/users/db/update.go b/internal/users/db/update.go index 4c412e22d3..a071585aeb 100644 --- a/internal/users/db/update.go +++ b/internal/users/db/update.go @@ -10,6 +10,12 @@ import ( "github.com/ubuntu/authd/log" ) +// ErrUIDAlreadyInUse is the error we return on registering a duplicate UID. +var ErrUIDAlreadyInUse = errors.New("UID already in use by a different user") + +// ErrGIDAlreadyInUse is the error we return on registering a duplicate GID. +var ErrGIDAlreadyInUse = errors.New("GID already in use by a different group") + // UpdateUserEntry inserts or updates user and group records from the user information. func (m *Manager) UpdateUserEntry(user UserRow, authdGroups []GroupRow, localGroups []string) (err error) { // authd uses lowercase usernames @@ -60,8 +66,9 @@ func handleUserUpdate(db queryable, u UserRow) error { // If a user with the same UID exists, we need to ensure that it's the same user or fail the update otherwise. if existingUser.Name != "" && existingUser.Name != u.Name { - log.Errorf(context.TODO(), "UID for user %q already in use by user %q", u.Name, existingUser.Name) - return errors.New("UID already in use by a different user") + log.Errorf(context.TODO(), "UID %d for user %q already in use by user %q", + u.UID, u.Name, existingUser.Name) + return fmt.Errorf("%w: %q", ErrUIDAlreadyInUse, u.Name) } // Ensure that we use the same homedir as the one we have in the database. @@ -93,7 +100,7 @@ func handleGroupsUpdate(db queryable, groups []GroupRow) error { // UGID, which was the case before https://github.com/ubuntu/authd/pull/647. if groupExists && existingGroup.UGID != "" && existingGroup.UGID != group.UGID { log.Errorf(context.TODO(), "GID %d for group with UGID %q already in use by a group with UGID %q", group.GID, group.UGID, existingGroup.UGID) - return fmt.Errorf("GID for group %q already in use by a different group", group.Name) + return fmt.Errorf("%w: %q", ErrGIDAlreadyInUse, group.Name) } log.Debugf(context.Background(), "Updating entry of group %q (%+v)", group.Name, group) @@ -124,13 +131,13 @@ func handleUsersToGroupsUpdate(db queryable, uid uint32, groups []GroupRow) erro // GID exist. _, userErr := userByID(db, uid) if errors.Is(userErr, NoDataFoundError{}) { - err = fmt.Errorf("%w (user with UID %d does not exist)", err, uid) + err = fmt.Errorf("%w (%w)", err, userErr) } else if userErr != nil { err = errors.Join(err, fmt.Errorf("failed to check if user with UID %d exists: %w", uid, userErr)) } _, groupErr := groupByID(db, group.GID) if errors.Is(groupErr, NoDataFoundError{}) { - err = fmt.Errorf("%w (group with GID %d does not exist)", err, group.GID) + err = fmt.Errorf("%w (%w)", err, groupErr) } else if groupErr != nil { err = errors.Join(err, fmt.Errorf("failed to check if group with GID %d exists: %w", group.GID, groupErr)) } diff --git a/internal/users/export_test.go b/internal/users/export_test.go index 387f7418d0..b139d0c5cc 100644 --- a/internal/users/export_test.go +++ b/internal/users/export_test.go @@ -1,9 +1,14 @@ package users import ( + "github.com/ubuntu/authd/internal/users/db" "github.com/ubuntu/authd/internal/users/tempentries" ) func (m *Manager) TemporaryRecords() *tempentries.TemporaryRecords { return m.temporaryRecords } + +func (m *Manager) DB() *db.Manager { + return m.db +} diff --git a/internal/users/manager.go b/internal/users/manager.go index 3c69d1cb2e..03e1071dac 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "os" + "slices" "strings" "syscall" @@ -48,6 +49,9 @@ type options struct { // Option is a function that allows changing some of the default behaviors of the manager. type Option func(*options) +// errUpdateRetry is an error when the user update failed in a non fatal way. +var errUpdateRetry = errors.New("update failed. Try again") + // WithIDGenerator makes the manager use a specific ID generator. // This option is only useful in tests. func WithIDGenerator(g tempentries.IDGenerator) Option { @@ -108,6 +112,31 @@ func (m *Manager) Stop() error { // UpdateUser updates the user information in the db. func (m *Manager) UpdateUser(u types.UserInfo) (err error) { + // Maybe we can even avoid to have a max... But well we'd likely fail only + // if the UID/GID set is very limited and we've lots of concurrent requests. + // In the worse case we'd fail anyways because temporary entries max + // max generation limit is reached anyways. + const maxRetries = 100 + + for i := range maxRetries { + user := u + user.Groups = slices.Clone(u.Groups) + + err = m.updateUser(user) + if errors.Is(err, errUpdateRetry) { + log.Infof(context.Background(), + "User %q update [try: %d] failed for a recoverable reason: %v", + u.Name, i, err) + continue + } + + break + } + + return err +} + +func (m *Manager) updateUser(u types.UserInfo) (err error) { defer decorate.OnError(&err, "failed to update user %q", u.Name) log.Debugf(context.TODO(), "Updating user %q", u.Name) @@ -120,6 +149,7 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { } var uid uint32 + var isNewUser bool // Check if the user already exists in the database oldUser, err := m.db.UserByName(u.Name) @@ -127,6 +157,7 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { return fmt.Errorf("could not get user %q: %w", u.Name, err) } if errors.Is(err, db.NoDataFoundError{}) { + isNewUser = true records, recordsUnlock, err := m.temporaryRecords.LockForChanges() if err != nil { return err @@ -217,7 +248,19 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { // Update user information in the db. userPrivateGroup := groupRows[0] userRow := db.NewUserRow(u.Name, uid, userPrivateGroup.GID, u.Gecos, u.Dir, u.Shell) - if err := m.db.UpdateUserEntry(userRow, groupRows, localGroups); err != nil { + err = m.db.UpdateUserEntry(userRow, groupRows, localGroups) + if isNewUser && errors.Is(err, db.ErrUIDAlreadyInUse) { + return fmt.Errorf("%w: %w", errUpdateRetry, err) + } + if errors.Is(err, db.ErrGIDAlreadyInUse) { + return fmt.Errorf("%w: %w", errUpdateRetry, err) + } + if errors.Is(err, db.NoDataFoundError{}) { + // The user or group has not been found while updating it, likely due + // to previous concurrent requests not having finished, so let's retry. + return fmt.Errorf("%w: %w", errUpdateRetry, err) + } + if err != nil { return err } diff --git a/internal/users/manager_test.go b/internal/users/manager_test.go index 9fad5665d9..5e73870e8c 100644 --- a/internal/users/manager_test.go +++ b/internal/users/manager_test.go @@ -2,9 +2,13 @@ package users_test import ( "context" + "fmt" "os" "path/filepath" + "slices" "strings" + "sync" + "sync/atomic" "testing" "time" @@ -15,6 +19,7 @@ import ( "github.com/ubuntu/authd/internal/users" "github.com/ubuntu/authd/internal/users/db" "github.com/ubuntu/authd/internal/users/idgenerator" + "github.com/ubuntu/authd/internal/users/localentries" localgroupstestutils "github.com/ubuntu/authd/internal/users/localentries/testutils" userslocking "github.com/ubuntu/authd/internal/users/locking" "github.com/ubuntu/authd/internal/users/tempentries" @@ -334,6 +339,248 @@ func TestRegisterUserPreauth(t *testing.T) { } } +func TestConcurrentUserUpdate(t *testing.T) { + t.Parallel() + + const nIterations = 100 + const preAuthIterations = 3 + const perUserGroups = 3 + const userUpdateRetries = 3 + + dbDir := t.TempDir() + const dbFile = "one_user_and_group_with_matching_gid" + err := db.Z_ForTests_CreateDBFromYAML(filepath.Join("testdata", "db", dbFile+".db.yaml"), dbDir) + require.NoError(t, err, "Setup: could not create database from testdata") + + const registeredUserPrefix = "authd-test-maybe-pre-check-user" + + systemPasswd, err := localentries.GetPasswdEntries() + require.NoError(t, err, "GetPasswdEntries should not fail but it did") + systemGroups, err := localentries.GetGroupEntries() + require.NoError(t, err, "GetGroupEntries should not fail but it did") + + idGenerator := &idgenerator.IDGenerator{ + UIDMin: 0, + //nolint: gosec // we're in tests, overflow is very unlikely to happen. + UIDMax: uint32(len(systemPasswd)) + nIterations*preAuthIterations, + GIDMin: 0, + //nolint: gosec // we're in tests, overflow is very unlikely to happen. + GIDMax: uint32(len(systemGroups)) + nIterations*perUserGroups*3, + } + m := newManagerForTests(t, dbDir, users.WithIDGenerator(idGenerator)) + + originalDBUsers, err := m.AllUsers() + require.NoError(t, err, "AllUsers should not fail but it did") + originalDBGroups, err := m.AllGroups() + require.NoError(t, err, "AllGroups should not fail but it did") + + wg := sync.WaitGroup{} + wg.Add(nIterations) + + // These tests are meant to stress-test in parallel our users manager, + // this is happening by updating new users or pre-auth some of them + // using a very limited UID and GID set, to retry more their generation. + // concurrently so that users gets registered first and then updated. + // Finally ensure that the generated UIDs and GIDs are not clashing. + for idx := range nIterations { + t.Run(fmt.Sprintf("Iteration_%d", idx), func(t *testing.T) { + t.Parallel() + + t.Logf("Running iteration %d", idx) + + idx := idx + doPreAuth := idx%3 == 0 + userName := fmt.Sprintf("authd-test-user%d", idx) + t.Cleanup(wg.Done) + + var preauthUID atomic.Uint32 + // var err error + if doPreAuth { + // In the pre-auth case we do even more parallelization, so that + // the pre-auth happens without a defined order of the actual + // registration. + userName = fmt.Sprintf("%s%d", registeredUserPrefix, idx) + + //nolint:thelper // This is actually a test function! + preAuth := func(t *testing.T) { + t.Parallel() + + t.Logf("Registering pre-auth user %q", userName) + uid, err := m.RegisterUserPreAuth(userName) + require.NoError(t, err, "RegisterPreAuthUser should not fail but it did") + preauthUID.Store(uid) + t.Logf("Registered pre-auth user %q with UID %d", userName, uid) + } + + for i := range preAuthIterations { + t.Run(fmt.Sprintf("Pre_auth%d", i), preAuth) + } + } + + //nolint:thelper // This is actually a test function! + userUpdate := func(t *testing.T) { + t.Parallel() + + uid := preauthUID.Load() + t.Logf("Updating user %q (using UID %d)", userName, uid) + u := types.UserInfo{ + Name: userName, + UID: uid, + Dir: "/home-prefixes/" + userName, + Shell: "/usr/sbin/nologin", + Groups: []types.GroupInfo{{Name: fmt.Sprintf("authd-test-local-group%d", idx)}}, + } + + // One user group matching the user is automatically added by authd. + for gdx := range perUserGroups - 1 { + u.Groups = append(u.Groups, types.GroupInfo{ + Name: fmt.Sprintf("authd-test-group%d.%d", idx, gdx), + UGID: fmt.Sprintf("authd-test-ugid%d.%d", idx, gdx), + }) + } + + err := m.UpdateUser(u) + require.NoError(t, err, "UpdateUser should not fail but it did") + t.Logf("Updated user %q using UID %d", userName, uid) + } + + testName := "Update_user" + if doPreAuth { + testName = "Maybe_finish_registration" + } + + for i := range userUpdateRetries { + t.Run(fmt.Sprintf("%s%d", testName, i), userUpdate) + } + }) + } + + for _, u := range systemPasswd { + t.Run(fmt.Sprintf("Error_updating_user_%s", u.Name), func(t *testing.T) { + t.Parallel() + + err := m.UpdateUser(types.UserInfo{ + Name: u.Name, + Dir: "/home-prefixes/" + u.Name, + Shell: "/usr/sbin/nologin", + }) + require.Error(t, err, "Updating user %q must fail but it does not", u.Name) + }) + } + + for idx, g := range systemGroups { + t.Run(fmt.Sprintf("Error_updating_user_with_non_local_group_%s", g.Name), func(t *testing.T) { + t.Parallel() + + userName := fmt.Sprintf("%s-with-invalid-groups%d", registeredUserPrefix, idx) + err := m.UpdateUser(types.UserInfo{ + Name: userName, + Dir: "/home-prefixes/" + g.Name, + Shell: "/usr/sbin/nologin", + Groups: []types.GroupInfo{{ + Name: g.Name, + UGID: fmt.Sprintf("authd-test-ugid-for-%s", g.Name), + }}, + }) + require.Error(t, err, "Updating user %q must fail but it does not", g.Name) + }) + } + + t.Run("Database_checks", func(t *testing.T) { + t.Parallel() + + // Wait for the other tests to be completed, not using t.Cleanup here + // since this is actually a test. + wg.Wait() + + // This includes the extra user that was already in the DB. + users, err := m.AllUsers() + require.NoError(t, err, "AllUsers should not fail but it did") + require.Len(t, users, nIterations+1, "Number of registered users mismatch") + + // This includes the extra group that was already in the DB. + groups, err := m.AllGroups() + require.NoError(t, err, "AllGroups should not fail but it did") + require.Len(t, groups, nIterations*3+1, "Number of registered groups mismatch") + + localPasswd, err := localentries.GetPasswdEntries() + require.NoError(t, err, "GetPasswdEntries should not fail but it did") + localGroups, err := localentries.GetGroupEntries() + require.NoError(t, err, "GetGroupEntries should not fail but it did") + + uniqueUIDs := make(map[uint32]types.UserEntry) + uniqueGIDs := make(map[uint32]string) + + for _, u := range users { + require.NotZero(t, u.UID, "No user should have the UID equal to zero, but %q has", u.Name) + require.Equal(t, u.UID, u.GID, "GID does not match UID for user %q", u.Name) + + old, ok := uniqueUIDs[u.UID] + require.False(t, ok, + "UID %d must be unique across entries, but it's used both %q and %q", + u.UID, u.Name, old) + uniqueUIDs[u.UID] = u + require.Equal(t, int(u.UID), int(u.GID), "User %q UID should match its GID", u.Name) + + if slices.ContainsFunc(originalDBUsers, func(dbU types.UserEntry) bool { + return dbU.UID == u.UID && dbU.Name == u.Name + }) { + // Ignore the local user checks for users already in the DB. + continue + } + + require.GreaterOrEqual(t, u.UID, idGenerator.UIDMin, + "Generated UID should be an ID greater or equal to the minimum") + require.LessOrEqual(t, u.UID, idGenerator.UIDMax, + "Generate UID should be an ID less or equal to the maximum") + + localgroups, err := m.DB().UserLocalGroups(u.UID) + require.NoError(t, err, "UserLocalGroups for %q should not fail but it did", u.Name) + require.Len(t, localgroups, 1, + "Number of registered local groups for %q mismatch", u.Name) + + isLocal := slices.ContainsFunc(localPasswd, func(lu types.UserEntry) bool { + return lu.UID == u.UID + }) + require.False(t, isLocal, "UID %d for user %q should not be a local user ID but it is", + u.UID, u.Name) + } + + for _, g := range groups { + require.NotZero(t, g.GID, "No group should have the GID equal to zero, but %q has", g.Name) + + old, ok := uniqueGIDs[g.GID] + require.False(t, ok, "GID %d must be unique across entries, but it's used both %q and %q", + g.GID, g.Name, old) + uniqueGIDs[g.GID] = g.Name + + u, ok := uniqueUIDs[g.GID] + if ok { + require.Equal(t, int(g.GID), int(u.GID), + "Group %q can only match its user, not to %q", g.Name, u.Name) + } + + isLocal := slices.ContainsFunc(localGroups, func(lg types.GroupEntry) bool { + return lg.GID == g.GID + }) + require.False(t, isLocal, "GID %d for group %q should not be a local user GID but it is", + g.GID, g.Name) + + if slices.ContainsFunc(originalDBGroups, func(dbU types.GroupEntry) bool { + return dbU.GID == g.GID && dbU.Name == g.Name + }) { + // Ignore the local user checks for users already in the DB. + continue + } + + require.GreaterOrEqual(t, g.GID, idGenerator.GIDMin, + "Generated GID should be an ID greater or equal to the minimum") + require.LessOrEqual(t, g.GID, idGenerator.GIDMax, + "Generate GID should be an ID less or equal to the maximum") + } + }) +} + func TestBrokerForUser(t *testing.T) { t.Parallel() diff --git a/internal/users/testdata/db/one_user_and_group_with_matching_gid.db.yaml b/internal/users/testdata/db/one_user_and_group_with_matching_gid.db.yaml new file mode 100644 index 0000000000..ef1a1f3a26 --- /dev/null +++ b/internal/users/testdata/db/one_user_and_group_with_matching_gid.db.yaml @@ -0,0 +1,17 @@ +users: + - name: user1 + uid: 11111 + gid: 11111 + gecos: |- + User1 gecos + On multiple lines + dir: /home/user1 + shell: /bin/bash + broker_id: broker-id +groups: + - name: user1 + gid: 11111 + ugid: "12345678" +users_to_groups: + - uid: 11111 + gid: 11111 From 3080db47deda9d915d6b7dda2d26eebaf848bc26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 27 Jun 2025 17:26:29 +0200 Subject: [PATCH 0559/1670] users: Introduce localentries.UserDBLocked to simplify locking and caching Instead of having per-instance locking mechanism, use a generic locking structure that locks and caches the local entries (when required). This structure must now be passed to the other structures requiring locking and will ensure that the various operations are happening when a lock is currently ongoing. We still need to have some locking for the local group file though, since we are going to write on it, and we don't really want that concurrent goroutines may write on it, leading to potential system damage. As per this we can also now go back to the simpler users database locking mechanism without any refcounting, since UserDBLocked now takes care of it. We required to move the test implementation to be more dynamic though, so that we can use per-test instances instead of relying on the global locking, as this allows more tests to work in parallel and us to check more the locking machinery in multi-threaded tests. --- .../IsAuthenticated | 2 +- internal/users/db/migration.go | 10 +- internal/users/localentries/export_test.go | 20 ++ internal/users/localentries/localgroups.go | 153 +++--------- .../users/localentries/localgroups_test.go | 124 ++++++---- internal/users/localentries/lockedentries.go | 217 ++++++++++++++++++ .../users/localentries/lockedentries_test.go | 98 ++++++++ internal/users/locking/locking.go | 69 ++---- internal/users/locking/locking_bwrap_test.go | 69 ++---- internal/users/locking/locking_test.go | 86 ++++--- .../users/locking/testlocker/testlocker.go | 2 +- internal/users/locking/testutils.go | 62 +++-- internal/users/manager.go | 32 ++- internal/users/manager_test.go | 13 +- internal/users/tempentries/tempentries.go | 125 +++------- .../users/tempentries/tempentries_test.go | 49 ++-- 16 files changed, 660 insertions(+), 471 deletions(-) create mode 100644 internal/users/localentries/lockedentries.go create mode 100644 internal/users/localentries/lockedentries_test.go diff --git a/internal/services/pam/testdata/golden/TestIsAuthenticated/Error_on_updating_local_groups_with_unexisting_file/IsAuthenticated b/internal/services/pam/testdata/golden/TestIsAuthenticated/Error_on_updating_local_groups_with_unexisting_file/IsAuthenticated index 8bf4930833..77877bd5de 100644 --- a/internal/services/pam/testdata/golden/TestIsAuthenticated/Error_on_updating_local_groups_with_unexisting_file/IsAuthenticated +++ b/internal/services/pam/testdata/golden/TestIsAuthenticated/Error_on_updating_local_groups_with_unexisting_file/IsAuthenticated @@ -1,4 +1,4 @@ FIRST CALL: access: msg: - err: can't check authentication: failed to update user "testisauthenticated/error_on_updating_local_groups_with_unexisting_file_separator_success_with_local_groups": could not lock local groups: could not fetch existing local group: open : no such file or directory + err: can't check authentication: failed to update user "testisauthenticated/error_on_updating_local_groups_with_unexisting_file_separator_success_with_local_groups": could not update local groups for user "testisauthenticated/error_on_updating_local_groups_with_unexisting_file_separator_success_with_local_groups": could not fetch existing local group: open : no such file or directory diff --git a/internal/users/db/migration.go b/internal/users/db/migration.go index af3d8af73a..db04591630 100644 --- a/internal/users/db/migration.go +++ b/internal/users/db/migration.go @@ -248,13 +248,17 @@ func renameUsersInGroupFile(oldNames, newNames []string) (err error) { return nil } - lockedGroups, unlock, err := localentries.GetGroupsWithLock() + lockedEntries, entriesUnlock, err := localentries.NewUserDBLocked() if err != nil { return err } - defer func() { err = errors.Join(err, unlock()) }() + defer func() { err = errors.Join(err, entriesUnlock()) }() - groups := lockedGroups.GetEntries() + lockedGroups := localentries.GetGroupsWithLock(lockedEntries) + groups, err := lockedGroups.GetEntries() + if err != nil { + return err + } for idx, group := range groups { for j, user := range group.Users { for k, oldName := range oldNames { diff --git a/internal/users/localentries/export_test.go b/internal/users/localentries/export_test.go index b7e921f7d7..2c3a2ef38d 100644 --- a/internal/users/localentries/export_test.go +++ b/internal/users/localentries/export_test.go @@ -1,5 +1,7 @@ package localentries +import userslocking "github.com/ubuntu/authd/internal/users/locking" + // WithGroupPath overrides the default /etc/group path for tests. func WithGroupPath(p string) Option { return func(o *options) { @@ -22,6 +24,24 @@ func WithGroupOutputPath(p string) Option { } } +// WithMockUserDBLocking uses a mock implementation to lock the users database. +func WithMockUserDBLocking() Option { + return func(o *options) { + mock := userslocking.SimpleMock{} + o.writeLockFunc = mock.WriteLock + o.writeUnlockFunc = mock.WriteUnlock + } +} + +// WithUserDBLockedInstance allows to use a test-provided locked instance that +// can be used to verify the refcounting behavior instead of relying on the +// default instance that is used normally. +func WithUserDBLockedInstance(userDBLocked *UserDBLocked) Option { + return func(o *options) { + o.userDBLocked = userDBLocked + } +} + // GroupFileBackupPath exposes the path to the group file backup for testing. func GroupFileBackupPath(groupFilePath string) string { return groupFileBackupPath(groupFilePath) diff --git a/internal/users/localentries/localgroups.go b/internal/users/localentries/localgroups.go index 4f2d982c6b..7e31fc753d 100644 --- a/internal/users/localentries/localgroups.go +++ b/internal/users/localentries/localgroups.go @@ -11,146 +11,52 @@ import ( "slices" "strconv" "strings" - "sync" "github.com/ubuntu/authd/internal/fileutils" "github.com/ubuntu/authd/internal/sliceutils" - "github.com/ubuntu/authd/internal/testsdetection" - userslocking "github.com/ubuntu/authd/internal/users/locking" "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/authd/log" "github.com/ubuntu/decorate" ) -// GroupFile is the default local group file. -const GroupFile = "/etc/group" - -var defaultOptions = options{ - groupInputPath: GroupFile, - groupOutputPath: GroupFile, -} - -type options struct { - groupInputPath string - groupOutputPath string -} - -// Option represents an optional function to override UpdateLocalGroups default values. -type Option func(*options) - // GroupsWithLock is a struct that holds the current user groups and provides methods to // retrieve and update them while ensuring that the system's user database is locked // to prevent concurrent modifications. type GroupsWithLock struct { - // mu is a mutex that protects the refCount and entries fields. - mu sync.RWMutex - // refCount is used to track how many times the GroupsWithLock instance has been returned by GetGroupsWithLock. - refCount uint64 - // entries holds the current group entries. - entries []types.GroupEntry - // options for testing purposes. - options options + l *UserDBLocked } -// defaultGroupsWithLock is used as the instance for locked groups when -// no test options are provided. -var defaultGroupsWithLock = &GroupsWithLock{} - // GetGroupsWithLock gets a GroupsWithLock instance with a lock on the system's user database. -// It returns a cleanup function that should be called to unlock system's user database. -func GetGroupsWithLock(args ...Option) (groups *GroupsWithLock, cleanup func() error, err error) { - defer decorate.OnError(&err, "could not lock local groups") - - if err := userslocking.WriteRecLock(); err != nil { - return nil, nil, err - } - - cleanupUnlocked := func() error { - if groups.refCount == 0 { - return fmt.Errorf("groups were already unlocked") - } - - groups.refCount-- - if groups.refCount == 0 { - groups.entries = nil - } - return userslocking.WriteRecUnlock() - } - - cleanup = func() error { - groups.mu.Lock() - defer groups.mu.Unlock() - - return cleanupUnlocked() - } - - groups = defaultGroupsWithLock - testingMode := len(args) != 0 +func GetGroupsWithLock(entriesWithLock *UserDBLocked) (groups *GroupsWithLock) { + entriesWithLock.MustBeLocked() - if testingMode { - testsdetection.MustBeTesting() - groups = &GroupsWithLock{} - } - - groups.mu.Lock() - defer groups.mu.Unlock() - - groups.refCount++ - if groups.refCount > 1 { - return groups, cleanup, nil - } - - opts := defaultOptions - for _, arg := range args { - arg(&opts) - } - - groups.options = opts - groups.entries, err = parseLocalGroups(opts.groupInputPath) - if err != nil { - return nil, nil, errors.Join(err, cleanupUnlocked()) - } - - return groups, cleanup, nil + return &GroupsWithLock{entriesWithLock} } -func (g *GroupsWithLock) mustLock() (cleanup func()) { - g.mu.Lock() - cleanup = g.mu.Unlock +// GetEntries returns a copy of the current group entries. +func (g *GroupsWithLock) GetEntries() (entries []types.GroupEntry, err error) { + defer decorate.OnError(&err, "could not get groups") - if g.refCount == 0 { - defer cleanup() - panic("locked groups are not locked!") - } + unlock := g.l.lockGroupFile() + defer unlock() - return cleanup + return g.getEntries() } -func (g *GroupsWithLock) mustRLock() (cleanup func()) { - g.mu.RLock() - cleanup = g.mu.RUnlock - - if g.refCount == 0 { - defer cleanup() - panic("locked groups are not locked!") +func (g *GroupsWithLock) getEntries() (entries []types.GroupEntry, err error) { + entries, err = g.l.GetLocalGroupEntries() + if err != nil { + return nil, err } - return cleanup -} - -// GetEntries returns a copy of the current group entries. -func (g *GroupsWithLock) GetEntries() (entries []types.GroupEntry) { - unlock := g.mustRLock() - defer unlock() - - return types.DeepCopyGroupEntries(g.entries) + return types.DeepCopyGroupEntries(entries), nil } // SaveEntries saves the provided group entries to the local group file. func (g *GroupsWithLock) SaveEntries(entries []types.GroupEntry) (err error) { defer decorate.OnError(&err, "could not save groups") - unlock := g.mustLock() + unlock := g.l.lockGroupFile() defer unlock() return g.saveLocalGroups(entries) @@ -163,11 +69,15 @@ func (g *GroupsWithLock) Update(username string, newGroups []string, oldGroups [ log.Debugf(context.TODO(), "Updating local groups for user %q, new groups: %v, old groups: %v", username, newGroups, oldGroups) defer decorate.OnError(&err, "could not update local groups for user %q", username) - unlock := g.mustLock() + unlock := g.l.lockGroupFile() defer unlock() - allGroups := types.DeepCopyGroupEntries(g.entries) - userGroups := g.userLocalGroups(username) + allGroups, err := g.getEntries() + if err != nil { + return err + } + + userGroups := userLocalGroups(allGroups, username) currentGroupsNames := sliceutils.Map(userGroups, func(g types.GroupEntry) string { return g.Name }) @@ -286,12 +196,17 @@ func formatGroupEntries(groups []types.GroupEntry) string { } func (g *GroupsWithLock) saveLocalGroups(groups []types.GroupEntry) (err error) { - inputPath := g.options.groupInputPath - groupPath := g.options.groupOutputPath + inputPath := g.l.options.groupInputPath + groupPath := g.l.options.groupOutputPath defer decorate.OnError(&err, "could not write local groups to %q", groupPath) - if slices.EqualFunc(g.entries, groups, types.GroupEntry.Equals) { + currentGroups, err := g.getEntries() + if err != nil { + return err + } + + if slices.EqualFunc(currentGroups, groups, types.GroupEntry.Equals) { log.Debugf(context.TODO(), "Nothing to do, groups are equal") return nil } @@ -327,13 +242,13 @@ func (g *GroupsWithLock) saveLocalGroups(groups []types.GroupEntry) (err error) return fmt.Errorf("error renaming %s to %s: %w", tempPath, groupPath, err) } - g.entries = types.DeepCopyGroupEntries(groups) + g.l.updateLocalGroupEntriesCache(groups) return nil } // userLocalGroups returns all groups the user is part of. -func (g *GroupsWithLock) userLocalGroups(user string) (userGroups []types.GroupEntry) { - return slices.DeleteFunc(slices.Clone(g.entries), func(g types.GroupEntry) bool { +func userLocalGroups(entries []types.GroupEntry, user string) (userGroups []types.GroupEntry) { + return slices.DeleteFunc(slices.Clone(entries), func(g types.GroupEntry) bool { return !slices.Contains(g.Users, user) }) } diff --git a/internal/users/localentries/localgroups_test.go b/internal/users/localentries/localgroups_test.go index 6f59f2b09e..b4885a4010 100644 --- a/internal/users/localentries/localgroups_test.go +++ b/internal/users/localentries/localgroups_test.go @@ -29,8 +29,7 @@ func TestUpdatelocalentries(t *testing.T) { oldGroups []string groupFilePath string - wantLockErr bool - wantUpdateErr bool + wantErr bool }{ // First insertion cases "Insert_new_user_in_existing_files_with_no_users_in_our_group": {groupFilePath: "no_users_in_our_groups.group"}, @@ -62,28 +61,28 @@ func TestUpdatelocalentries(t *testing.T) { // Error cases "Error_on_missing_groups_file": { groupFilePath: "does_not_exists.group", - wantLockErr: true, + wantErr: true, }, "Error_on_invalid_user_name": { groupFilePath: "no_users_in_our_groups.group", username: "no,commas,please", - wantUpdateErr: true, + wantErr: true, }, "Error_when_groups_file_has_missing_fields": { groupFilePath: "malformed_file_missing_field.group", - wantLockErr: true, + wantErr: true, }, "Error_when_groups_file_has_invalid_gid": { groupFilePath: "malformed_file_invalid_gid.group", - wantLockErr: true, + wantErr: true, }, "Error_when_groups_file_has_no_group_name": { groupFilePath: "malformed_file_no_group_name.group", - wantLockErr: true, + wantErr: true, }, "Error_when_groups_file_has_a_duplicated_group": { groupFilePath: "malformed_file_duplicated.group", - wantLockErr: true, + wantErr: true, }, } for name, tc := range tests { @@ -113,19 +112,21 @@ func TestUpdatelocalentries(t *testing.T) { defer localentriestestutils.RequireGroupFile(t, outputGroupFilePath, golden.Path(t)) - lg, cleanup, err := localentries.GetGroupsWithLock( + entries, entriesUnlock, err := localentries.NewUserDBLocked( localentries.WithGroupInputPath(inputGroupFilePath), localentries.WithGroupOutputPath(outputGroupFilePath), + localentries.WithMockUserDBLocking(), ) - if tc.wantLockErr { - require.Error(t, err, "GetGroupsWithLock should have failed") - return - } - require.NoError(t, err, "Setup: failed to lock the users group") - t.Cleanup(func() { require.NoError(t, cleanup(), "Releasing unlocked groups") }) + require.NoError(t, err, "Failed to lock the local entries") + t.Cleanup(func() { + err := entriesUnlock() + require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") + }) + + lg := localentries.GetGroupsWithLock(entries) err = lg.Update(tc.username, tc.newGroups, tc.oldGroups) - if tc.wantUpdateErr { + if tc.wantErr { require.Error(t, err, "Updatelocalentries should have failed") } else { require.NoError(t, err, "Updatelocalentries should not have failed") @@ -295,19 +296,24 @@ func TestGetAndSaveLocalGroups(t *testing.T) { defer localentriestestutils.RequireGroupFile(t, outputGroupFilePath, golden.Path(t)) - lg, cleanup, err := localentries.GetGroupsWithLock( + entries, entriesUnlock, err := localentries.NewUserDBLocked( localentries.WithGroupInputPath(inputGroupFilePath), localentries.WithGroupOutputPath(outputGroupFilePath), + localentries.WithMockUserDBLocking(), ) + require.NoError(t, err, "Failed to lock the local entries") + defer func() { + err := entriesUnlock() + require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") + }() + + lg := localentries.GetGroupsWithLock(entries) + groups, err := lg.GetEntries() if tc.wantGetErr { - require.Error(t, err, "Locking should have failed, but it did not") + require.Error(t, err, "GetEntries should return an error, but did not") return } - - require.NoError(t, err, "Setup: failed to lock the users group") - defer func() { require.NoError(t, cleanup(), "Releasing unlocked groups") }() - - groups := lg.GetEntries() + require.NoError(t, err, "GetEntries should not return an error, but did") initialGroups := slices.Clone(groups) groups = append(groups, tc.addGroups...) @@ -324,7 +330,8 @@ func TestGetAndSaveLocalGroups(t *testing.T) { err = lg.SaveEntries(groups) if tc.wantSetErr { require.Error(t, err, "SaveEntries should have failed") - updatedGroups := lg.GetEntries() + updatedGroups, err := lg.GetEntries() + require.NoError(t, err, "GetEntries should not return an error, but did") require.Equal(t, initialGroups, updatedGroups, "Cached groups have been changed") return } @@ -335,7 +342,8 @@ func TestGetAndSaveLocalGroups(t *testing.T) { require.NoError(t, err, "SaveEntries should not have failed") // Ensure we also saved the cached version of the groups... - updatedGroups := lg.GetEntries() + updatedGroups, err := lg.GetEntries() + require.NoError(t, err, "GetEntries should not return an error, but did") require.Equal(t, groups, updatedGroups, "Cached groups are not saved") if tc.wantNoOp { @@ -350,10 +358,11 @@ func TestGetAndSaveLocalGroups(t *testing.T) { } //nolint:tparallel // This can't be parallel, but subtests can. -func TestRacingLockingActions(t *testing.T) { +func TestRacingGroupsLockingActions(t *testing.T) { const nIterations = 50 testFilePath := filepath.Join("testdata", "no_users_in_our_groups.group") + testUserDBLocked := &localentries.UserDBLocked{} wg := sync.WaitGroup{} wg.Add(nIterations) @@ -371,17 +380,26 @@ func TestRacingLockingActions(t *testing.T) { if useTestGroupFile { // Mix the requests with test-only code paths... - opts = append(opts, localentries.WithGroupPath(testFilePath)) + opts = append(opts, + localentries.WithGroupPath(testFilePath), + localentries.WithUserDBLockedInstance(testUserDBLocked), + localentries.WithMockUserDBLocking(), + ) wantGroup = types.GroupEntry{Name: "localgroup1", GID: 41, Passwd: "x"} } - lg, unlock, err := localentries.GetGroupsWithLock(opts...) - require.NoError(t, err, "Failed to lock the users group (test groups: %v)", useTestGroupFile) - groups := lg.GetEntries() + entries, entriesUnlock, err := localentries.NewUserDBLocked(opts...) + require.NoError(t, err, "Failed to lock the local entries") + t.Cleanup(func() { + err := entriesUnlock() + require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") + }) + + lg := localentries.GetGroupsWithLock(entries) + groups, err := lg.GetEntries() + require.NoError(t, err, "GetEntries should not return an error, but did") require.NotEmpty(t, groups, "Got empty groups (test groups: %v)", useTestGroupFile) require.Contains(t, groups, wantGroup, "Expected group was not found (test groups: %v)", useTestGroupFile) - err = unlock() - require.NoError(t, err, "Unlock should not fail to lock the users group (test groups: %v)", useTestGroupFile) }) } @@ -390,43 +408,59 @@ func TestRacingLockingActions(t *testing.T) { wg.Wait() // Get a last unlock function, to see if we're all good... - lg, unlock, err := localentries.GetGroupsWithLock() - require.NoError(t, err, "Failed to lock the users group") - err = unlock() + entries, entriesUnlock, err := localentries.NewUserDBLocked() + require.NoError(t, err, "Failed to lock the local entries") + + lg := localentries.GetGroupsWithLock(entries) require.NoError(t, err, "Unlock should not fail to lock the users group") + err = entriesUnlock() + require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") + // Ensure that we had cleaned up all the locks correctly! - require.Panics(t, func() { _ = lg.GetEntries() }) + require.Panics(t, func() { _, _ = lg.GetEntries() }) }) } func TestLockedInvalidActions(t *testing.T) { // This cannot be parallel - lg, unlock, err := localentries.GetGroupsWithLock() + require.Panics(t, func() { localentries.GetGroupsWithLock((&localentries.UserDBLocked{})) }, + "GetGroupsWithLock should panic but did not") + require.Panics(t, func() { _ = (&localentries.GroupsWithLock{}).Update("", nil, nil) }, + "Update should panic but did not") + require.Panics(t, func() { _, _ = (&localentries.GroupsWithLock{}).GetEntries() }, + "GetEntries should panic but did not") + require.Panics(t, func() { _ = (&localentries.GroupsWithLock{}).SaveEntries(nil) }, + "SaveEntries should panic but did not") + + entries, entriesUnlock, err := localentries.NewUserDBLocked() + require.NoError(t, err, "Failed to lock the local entries") - require.NoError(t, err, "Setup: failed to lock the users group") - err = unlock() + lg := localentries.GetGroupsWithLock(entries) + err = entriesUnlock() require.NoError(t, err, "Unlock should not fail to lock the users group") - err = unlock() + err = entriesUnlock() require.Error(t, err, "Unlocking twice should fail") require.Panics(t, func() { _ = lg.Update("", nil, nil) }, "Update should panic but did not") - require.Panics(t, func() { _ = lg.GetEntries() }, + require.Panics(t, func() { _, _ = lg.GetEntries() }, "GetEntries should panic but did not") require.Panics(t, func() { _ = lg.SaveEntries(nil) }, "SaveEntries should panic but did not") // This is to ensure that we're in a good state, despite the actions above for range 10 { - lg, unlock, err = localentries.GetGroupsWithLock() - require.NoError(t, err, "Failed to lock the users group") + entries, entriesUnlock, err := localentries.NewUserDBLocked() + require.NoError(t, err, "Failed to lock the local entries") defer func() { - err := unlock() - require.NoError(t, err, "Unlock should not fail to lock the users group") + err := entriesUnlock() + require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") }() + + lg = localentries.GetGroupsWithLock(entries) } } diff --git a/internal/users/localentries/lockedentries.go b/internal/users/localentries/lockedentries.go new file mode 100644 index 0000000000..ce25121ef0 --- /dev/null +++ b/internal/users/localentries/lockedentries.go @@ -0,0 +1,217 @@ +package localentries + +import ( + "context" + "fmt" + "sync" + + "github.com/ubuntu/authd/internal/testsdetection" + userslocking "github.com/ubuntu/authd/internal/users/locking" + "github.com/ubuntu/authd/internal/users/types" + "github.com/ubuntu/authd/log" + "github.com/ubuntu/decorate" +) + +// GroupFile is the default local group fill. +const GroupFile = "/etc/group" + +type options struct { + // groupInputPath is the path used to read the group file. Defaults to + // [GroupFile], but can be overwritten in tests. + groupInputPath string + // groupOutputPath is the path used to write the group file. Defaults to + // [GroupFile], but can be overwritten in tests. + groupOutputPath string + + // These are the lock and unlock functions to be used that can be overridden + // for testing purposes. + writeLockFunc func() error + writeUnlockFunc func() error + + // userDBLocked is the userDBLocked instance to use and that can be + // replaced for testing purposes. + userDBLocked *UserDBLocked +} + +var defaultOptions = options{ + // userDBLocked is used as the instance for locked groups when + // no test options are provided. + userDBLocked: &UserDBLocked{}, + + groupInputPath: GroupFile, + groupOutputPath: GroupFile, + + writeLockFunc: userslocking.WriteLock, + writeUnlockFunc: userslocking.WriteUnlock, +} + +// Option represents an optional function to override [NewUserDBLocked] default values. +type Option func(*options) + +// UserDBLocked is a struct that holds the current users and groups while +// ensuring that the system's user database is locked to prevent concurrent +// modifications. +type UserDBLocked struct { + // mu is a mutex that protects the refCount and entries fields. + mu sync.Mutex + // refCount is used to track how many times the GroupsWithLock instance has + // been returned by [NewWithLock]. + refCount uint64 + + // localGroupsMu is the mutex that protects us globally from concurrent + // reads and writes on the group file. + // We need this to ensure that we don't write to the file while we're + // parsing it to prevent that we may do concurrent writes on it. + // The mutex is tied to the lock instance since it's where different file + // paths can be defined (through options), and avoids us to have a shared + // global mutex when the locked instances are different from the default. + localGroupsMu sync.Mutex + + // userEntries holds the current group entries. + userEntries []types.UserEntry + // groupEntries holds the current group entries. + groupEntries []types.GroupEntry + // localGroupEntries holds the current group entries. + localGroupEntries []types.GroupEntry + + // options to set the local entries context. + options options +} + +// NewUserDBLocked gets a [UserDBLocked] instance with a lock on the system's user database. +// It returns an unlock function that should be called to unlock system's user database. +func NewUserDBLocked(args ...Option) (entries *UserDBLocked, unlock func() error, err error) { + defer decorate.OnError(&err, "could not lock local groups") + + unlock = func() error { + entries.mu.Lock() + defer entries.mu.Unlock() + + if entries.refCount == 0 { + return fmt.Errorf("groups were already unlocked") + } + + entries.refCount-- + if entries.refCount != 0 { + return nil + } + + log.Debug(context.Background(), "Unlocking local entries") + entries.userEntries = nil + entries.localGroupEntries = nil + entries.groupEntries = nil + + return entries.options.writeUnlockFunc() + } + + opts := defaultOptions + testingMode := args != nil + + if testingMode { + testsdetection.MustBeTesting() + opts.userDBLocked = &UserDBLocked{} + + for _, arg := range args { + arg(&opts) + } + } + + entries = opts.userDBLocked + + entries.mu.Lock() + defer entries.mu.Unlock() + + if entries.refCount != 0 { + entries.refCount++ + return entries, unlock, nil + } + + log.Debug(context.Background(), "Locking local entries") + + if err := opts.writeLockFunc(); err != nil { + return nil, nil, err + } + + entries.options = opts + entries.refCount++ + + return entries, unlock, nil +} + +// MustBeLocked ensures wether the entries are locked. +func (l *UserDBLocked) MustBeLocked() { + l.mu.Lock() + defer l.mu.Unlock() + + l.mustBeLocked() +} + +func (l *UserDBLocked) mustBeLocked() { + if l.refCount == 0 { + panic("locked entries are not locked!") + } +} + +// GetUserEntries gets the user entries. +func (l *UserDBLocked) GetUserEntries() (entries []types.UserEntry, err error) { + l.mu.Lock() + defer l.mu.Unlock() + + l.mustBeLocked() + + if l.userEntries != nil { + return l.userEntries, nil + } + + l.userEntries, err = GetPasswdEntries() + return l.userEntries, err +} + +// GetGroupEntries gets the group entries. +func (l *UserDBLocked) GetGroupEntries() (entries []types.GroupEntry, err error) { + l.mu.Lock() + defer l.mu.Unlock() + + l.mustBeLocked() + + if l.groupEntries != nil { + return l.groupEntries, nil + } + + l.groupEntries, err = GetGroupEntries() + return l.groupEntries, err +} + +// GetLocalGroupEntries gets the local group entries. +func (l *UserDBLocked) GetLocalGroupEntries() (entries []types.GroupEntry, err error) { + l.mu.Lock() + defer l.mu.Unlock() + + l.mustBeLocked() + + if l.localGroupEntries != nil { + return l.localGroupEntries, nil + } + + l.localGroupEntries, err = parseLocalGroups(l.options.groupInputPath) + return l.localGroupEntries, err +} + +// updateLocalGroupEntriesCache updates the local group entries. +func (l *UserDBLocked) updateLocalGroupEntriesCache(entries []types.GroupEntry) { + l.mu.Lock() + defer l.mu.Unlock() + + l.mustBeLocked() + + l.localGroupEntries = types.DeepCopyGroupEntries(entries) +} + +// lockGroupFile locks the read/write operation on the group file and returns +// an unlock function. +func (l *UserDBLocked) lockGroupFile() (unlock func()) { + l.MustBeLocked() + + l.localGroupsMu.Lock() + return l.localGroupsMu.Unlock +} diff --git a/internal/users/localentries/lockedentries_test.go b/internal/users/localentries/lockedentries_test.go new file mode 100644 index 0000000000..ee3c8d32ca --- /dev/null +++ b/internal/users/localentries/lockedentries_test.go @@ -0,0 +1,98 @@ +package localentries_test + +import ( + "fmt" + "path/filepath" + "sync" + "testing" + + "github.com/stretchr/testify/require" + "github.com/ubuntu/authd/internal/users/localentries" + "github.com/ubuntu/authd/internal/users/types" +) + +func TestEntriesWithLockInvalidActions(t *testing.T) { + // This cannot be parallel + + require.Panics(t, func() { (&localentries.UserDBLocked{}).MustBeLocked() }, + "MustBeLocked should panic but did not") + require.Panics(t, func() { _, _ = (&localentries.UserDBLocked{}).GetUserEntries() }, + "GetUserEntries should panic but did not") + require.Panics(t, func() { _, _ = (&localentries.UserDBLocked{}).GetGroupEntries() }, + "GetGroupEntries should panic but did not") + require.Panics(t, func() { _, _ = (&localentries.UserDBLocked{}).GetLocalGroupEntries() }, + "GetLocalGroupEntries should panic but did not") + + le, unlock, err := localentries.NewUserDBLocked() + + require.NoError(t, err, "Setup: failed to lock the users group") + err = unlock() + require.NoError(t, err, "Unlock should not fail to lock the users group") + + err = unlock() + require.Error(t, err, "Unlocking twice should fail") + + require.Panics(t, func() { le.MustBeLocked() }, + "MustBeLocked should panic but did not") + require.Panics(t, func() { _, _ = le.GetUserEntries() }, + "GetUserEntries should panic but did not") + require.Panics(t, func() { _, _ = le.GetGroupEntries() }, + "GetGroupEntries should panic but did not") + require.Panics(t, func() { _, _ = le.GetLocalGroupEntries() }, + "GetLocalGroupEntries should panic but did not") + + // This is to ensure that we're in a good state, despite the actions above + for range 10 { + le, unlock, err = localentries.NewUserDBLocked() + require.NoError(t, err, "Failed to lock the users group") + defer func() { + err := unlock() + require.NoError(t, err, "Unlock should not fail to lock the users group") + }() + } +} + +//nolint:tparallel // This can't be parallel, but subtests can. +func TestRacingEntriesLockingActions(t *testing.T) { + const nIterations = 50 + + testFilePath := filepath.Join("testdata", "no_users_in_our_groups.group") + testUserDBLocked := &localentries.UserDBLocked{} + + wg := sync.WaitGroup{} + wg.Add(nIterations) + + // Lock and get the values in parallel. + for idx := range nIterations { + t.Run(fmt.Sprintf("iteration_%d", idx), func(t *testing.T) { + t.Parallel() + + t.Cleanup(wg.Done) + + var opts []localentries.Option + wantGroup := types.GroupEntry{Name: "root", GID: 0, Passwd: "x"} + useTestGroupFile := idx%3 == 0 + + if useTestGroupFile { + // Mix the requests with test-only code paths... + opts = append(opts, + localentries.WithGroupPath(testFilePath), + localentries.WithMockUserDBLocking(), + localentries.WithUserDBLockedInstance(testUserDBLocked), + ) + wantGroup = types.GroupEntry{Name: "localgroup1", GID: 41, Passwd: "x"} + } + + lockedEntries, entriesUnlock, err := localentries.NewUserDBLocked(opts...) + require.NoError(t, err, "Failed to lock the local entries") + + lg := localentries.GetGroupsWithLock(lockedEntries) + groups, err := lg.GetEntries() + require.NoError(t, err, "GetEntries should not return an error, but did") + require.NotEmpty(t, groups, "Got empty groups (test groups: %v)", useTestGroupFile) + require.Contains(t, groups, wantGroup, "Expected group was not found (test groups: %v)", useTestGroupFile) + err = entriesUnlock() + require.NoError(t, err, "EntriesUnlock() should not fail to lock the users group (test groups: %v)", useTestGroupFile) + }) + } +} diff --git a/internal/users/locking/locking.go b/internal/users/locking/locking.go index 066a8f857e..408c2704ce 100644 --- a/internal/users/locking/locking.go +++ b/internal/users/locking/locking.go @@ -10,7 +10,6 @@ package userslocking import ( "errors" "fmt" - "sync" "time" ) @@ -18,9 +17,6 @@ var ( writeLockImpl = writeLock writeUnlockImpl = writeUnlock - writeLocksCount uint64 - writeLocksCountMu sync.RWMutex - // maxWait is the maximum wait time for a lock to happen. // We mimic the libc behavior, in case we don't get SIGALRM'ed. maxWait = 16 * time.Second @@ -37,7 +33,14 @@ var ( ErrLockTimeout = fmt.Errorf("%w: timeout", ErrLock) ) -func writeLockInternal() error { +// WriteLock locks for writing the the local user entries database by using +// the standard libc lckpwdf() function. +// While the database is locked read operations can happen, but no other process +// is allowed to write. +// Note that this call will block all the other processes trying to access the +// database in write mode, while it will return an error if called while the +// lock is already hold by this process. +func WriteLock() error { done := make(chan error) writeLockImpl := writeLockImpl @@ -57,54 +60,10 @@ func writeLockInternal() error { } } -// WriteRecLock locks the system's user database for writing. -// While the lock is held, all other processes trying to lock the database -// will block until the lock is released (or a timeout of 15 seconds is reached). -// Note that this implies that if some other process owns the lock when -// this function is called, it will block until the other process releases the -// lock. -// -// This function is recursive, it can be called multiple times without -// deadlocking even by different goroutines - the system user database is locked -// only once, when the reference count is 0, else it just increments the -// reference count. -// -// [WriteRecUnlock] must be called the same number of times as [WriteRecLock] to -// release the lock. -func WriteRecLock() error { - writeLocksCountMu.Lock() - defer writeLocksCountMu.Unlock() - - if writeLocksCount == 0 { - if err := writeLockInternal(); err != nil { - return err - } - } - - writeLocksCount++ - return nil -} - -// WriteRecUnlock decreases the reference count of the lock acquired by. -// [WriteRecLock]. If the reference count reaches 0, it releases the lock -// on the system's user database. -func WriteRecUnlock() error { - writeLocksCountMu.Lock() - defer writeLocksCountMu.Unlock() - - if writeLocksCount == 0 { - return fmt.Errorf("%w: no locks found", ErrUnlock) - } - - if writeLocksCount > 1 { - writeLocksCount-- - return nil - } - - if err := writeUnlockImpl(); err != nil { - return err - } - - writeLocksCount-- - return nil +// WriteUnlock unlocks for writing the local user entries database by using +// the standard libc ulckpwdf() function. +// As soon as this function is called all the other waiting processes will be +// allowed to take the lock. +func WriteUnlock() error { + return writeUnlockImpl() } diff --git a/internal/users/locking/locking_bwrap_test.go b/internal/users/locking/locking_bwrap_test.go index 0dc9c4f37f..da0d1dffff 100644 --- a/internal/users/locking/locking_bwrap_test.go +++ b/internal/users/locking/locking_bwrap_test.go @@ -16,26 +16,6 @@ import ( userslocking "github.com/ubuntu/authd/internal/users/locking" ) -var ( - writeLock = func() error { - for i := 0; i < 5; i++ { - if err := userslocking.WriteRecLock(); err != nil { - return err - } - } - return nil - } - - writeUnlock = func() error { - for i := 0; i < 5; i++ { - if err := userslocking.WriteRecUnlock(); err != nil { - return err - } - } - return nil - } -) - func TestLockAndWriteUnlock(t *testing.T) { require.Zero(t, os.Geteuid(), "Not root") @@ -51,7 +31,7 @@ func TestLockAndWriteUnlock(t *testing.T) { require.NoError(t, err, "Output: %s", output) // Lock the group file - err = writeLock() + err = userslocking.WriteLock() require.NoError(t, err, "Locking database") output, err = runCmd(t, "getent", "group", "testgroup") @@ -70,7 +50,7 @@ func TestLockAndWriteUnlock(t *testing.T) { require.Equal(t, output, newGroupContents+",root", "Group not found") // Unlock the group file - err = writeUnlock() + err = userslocking.WriteUnlock() require.NoError(t, err, "Unlocking database") // Try using gpasswd to modify the group file again. This should succeed, @@ -94,10 +74,10 @@ testgroup:x:1001:testuser` err := os.WriteFile(groupFile, []byte(groupContents), 0644) require.NoError(t, err, "Writing group file") - err = writeLock() + err = userslocking.WriteLock() require.NoError(t, err, "Locking once it is allowed") t.Cleanup(func() { - err := writeUnlock() + err := userslocking.WriteUnlock() require.NoError(t, err, "Unlocking should be allowed") }) @@ -111,10 +91,10 @@ func TestLockAndLockAgainGroupFileOverridden(t *testing.T) { restoreFunc := userslocking.Z_ForTests_RestoreLocking t.Cleanup(func() { restoreFunc() }) - err := userslocking.WriteRecLock() + err := userslocking.WriteLock() require.NoError(t, err, "Locking once it is allowed") - err = userslocking.WriteRecUnlock() + err = userslocking.WriteUnlock() require.NoError(t, err, "Unlocking should be allowed") // Ensure restoring works as expected. @@ -128,12 +108,12 @@ func TestLockAndLockAgainGroupFileOverridden(t *testing.T) { err = os.WriteFile(groupFile, []byte(groupContents), 0644) require.NoError(t, err, "Writing group file") - err = userslocking.WriteRecLock() + err = userslocking.WriteLock() require.NoError(t, err, "Locking once it is allowed") t.Cleanup(func() { // Ignore the error here, as it's expected to return an error if the - // WriteRecUnlock further below is called first. - _ = userslocking.WriteRecUnlock() + // userslocking.WriteUnlock further below is called first. + _ = userslocking.WriteUnlock() }) gPasswdExited := make(chan error) @@ -149,38 +129,35 @@ func TestLockAndLockAgainGroupFileOverridden(t *testing.T) { require.ErrorIs(t, err, userslocking.ErrLock, "GPasswd should fail") } - require.NoError(t, userslocking.WriteRecUnlock()) + require.NoError(t, userslocking.WriteUnlock()) <-gPasswdExited } func TestUnlockUnlockedOverridden(t *testing.T) { userslocking.Z_ForTests_OverrideLockingWithCleanup(t) - err := userslocking.WriteRecUnlock() + err := userslocking.WriteUnlock() require.ErrorIs(t, err, userslocking.ErrUnlock, "Unlocking unlocked should not be allowed") } func TestUnlockUnlocked(t *testing.T) { require.Zero(t, os.Geteuid(), "Not root") - err := writeUnlock() + err := userslocking.WriteUnlock() require.ErrorIs(t, err, userslocking.ErrUnlock, "Unlocking unlocked should not be allowed") } -func TestRecLockAndRecLockAgainGroupFile(t *testing.T) { +func TestLockAndLockAgainGroupFile(t *testing.T) { require.Zero(t, os.Geteuid(), "Not root") - err := userslocking.WriteRecLock() + err := userslocking.WriteLock() require.NoError(t, err, "Locking once it is allowed") - err = userslocking.WriteRecLock() - require.NoError(t, err, "Locking twice it is allowed") - - err = userslocking.WriteRecUnlock() - require.NoError(t, err, "Unlocking should be allowed once") + err = userslocking.WriteLock() + require.ErrorIs(t, err, userslocking.ErrLock, "Locking again should not be allowed") - err = userslocking.WriteRecUnlock() - require.NoError(t, err, "Unlocking should be allowed twice") + err = userslocking.WriteUnlock() + require.NoError(t, err, "Unlocking should be allowed") } func TestLockingLockedDatabase(t *testing.T) { @@ -211,7 +188,7 @@ func TestLockingLockedDatabase(t *testing.T) { syscall.Kill(lockerProcess.Pid, syscall.SIGKILL) require.Error(t, <-lockerExited, "Stopping locking process") require.NoError(t, <-writeLockExited, "Final locking") - require.NoError(t, writeUnlock(), "Final unlocking") + require.NoError(t, userslocking.WriteUnlock(), "Final unlocking") }) go func() { @@ -240,7 +217,7 @@ func TestLockingLockedDatabase(t *testing.T) { } go func() { - writeLockExited <- writeLock() + writeLockExited <- userslocking.WriteLock() }() select { @@ -290,7 +267,7 @@ func TestLockingLockedDatabaseFailsAfterTimeout(t *testing.T) { t.Log("Waiting for lock") writeLockExited := make(chan error) go func() { - writeLockExited <- writeLock() + writeLockExited <- userslocking.WriteLock() }() err = <-writeLockExited @@ -332,7 +309,7 @@ func TestLockingLockedDatabaseWorksAfterUnlock(t *testing.T) { writeLockExited := make(chan error) go func() { - writeLockExited <- writeLock() + writeLockExited <- userslocking.WriteLock() }() select { @@ -345,7 +322,7 @@ func TestLockingLockedDatabaseWorksAfterUnlock(t *testing.T) { writeUnLockExited := make(chan error) go func() { - writeUnLockExited <- writeUnlock() + writeUnLockExited <- userslocking.WriteUnlock() }() select { diff --git a/internal/users/locking/locking_test.go b/internal/users/locking/locking_test.go index 44780668f7..563835a2d5 100644 --- a/internal/users/locking/locking_test.go +++ b/internal/users/locking/locking_test.go @@ -9,7 +9,6 @@ import ( "path/filepath" "regexp" "strings" - "sync" "testing" "time" @@ -118,82 +117,81 @@ func compileLockerBinary(t *testing.T) string { return testLocker } -func TestUsersLockingRecLockingOverride(t *testing.T) { +func TestUsersLockingOverride(t *testing.T) { // This cannot be parallel. userslocking.Z_ForTests_OverrideLockingWithCleanup(t) - err := userslocking.WriteRecLock() + err := userslocking.WriteLock() require.NoError(t, err, "Locking should be allowed") - err = userslocking.WriteRecLock() - require.NoError(t, err, "Locking again should be allowed") + err = userslocking.WriteLock() + require.ErrorIs(t, err, userslocking.ErrLock, "Locking again should not be allowed") - err = userslocking.WriteRecUnlock() + err = userslocking.WriteUnlock() require.NoError(t, err, "Unlocking should be allowed") - err = userslocking.WriteRecUnlock() - require.NoError(t, err, "Unlocking again should be allowed") - - err = userslocking.WriteRecUnlock() + err = userslocking.WriteUnlock() require.ErrorIs(t, err, userslocking.ErrUnlock, "Unlocking unlocked should not be allowed") } -func TestUsersLockingRecLockingOverrideAsLockedExternally(t *testing.T) { +func TestUsersLockingOverrideAsLockedExternally(t *testing.T) { // This cannot be parallel. - lockCtx, lockCancel := context.WithCancel(context.Background()) - userslocking.Z_ForTests_OverrideLockingAsLockedExternally(t, lockCtx) + userslocking.Z_ForTests_OverrideLockingAsLockedExternally(t, context.Background()) - wg := sync.WaitGroup{} - wg.Add(2) + lockingExited := make(chan error) go func() { - err := userslocking.WriteRecLock() - require.NoError(t, err, "Locking should not fail") - wg.Done() - }() - go func() { - err := userslocking.WriteRecLock() - require.NoError(t, err, "Locking should not fail") - wg.Done() - }() - - doneWaiting := make(chan struct{}) - go func() { - wg.Wait() - close(doneWaiting) + lockingExited <- userslocking.WriteLock() }() select { case <-time.After(1 * time.Second): // If we're time-outing: it's fine, it means we were locked! - case <-doneWaiting: - t.Error("We should not be unlocked, but we are") + case err := <-lockingExited: + t.Errorf("We should have not been exited, but we did with error %v", err) t.FailNow() } - wg.Add(2) - go func() { - err := userslocking.WriteRecUnlock() - require.NoError(t, err, "Unlocking should not fail") - wg.Done() - }() + err := userslocking.WriteUnlock() + require.NoError(t, err, "Unlocking should be allowed") + + err = <-lockingExited + require.NoError(t, err, "Previous concurrent locking should have been allowed now") + + err = userslocking.WriteUnlock() + require.NoError(t, err, "Unlocking should be allowed") + + err = userslocking.WriteUnlock() + require.ErrorIs(t, err, userslocking.ErrUnlock, "Unlocking unlocked should not be allowed") +} + +func TestUsersLockingOverrideAsLockedExternallyWithContext(t *testing.T) { + // This cannot be parallel. + lockCtx, lockCancel := context.WithCancel(context.Background()) + userslocking.Z_ForTests_OverrideLockingAsLockedExternally(t, lockCtx) + + lockingExited := make(chan error) go func() { - err := userslocking.WriteRecUnlock() - require.NoError(t, err, "Unlocking should not fail") - wg.Done() + lockingExited <- userslocking.WriteLock() }() - t.Cleanup(wg.Wait) select { case <-time.After(1 * time.Second): // If we're time-outing: it's fine, it means we were locked! - case <-doneWaiting: - t.Error("We should not be unlocked, but we are") + case err := <-lockingExited: + t.Errorf("We should have not been exited, but we did with error %v", err) t.FailNow() } // Remove the "external" lock now. lockCancel() - <-doneWaiting + err := <-lockingExited + require.NoError(t, err, "Previous concurrent locking should have been allowed now") + + err = userslocking.WriteUnlock() + require.NoError(t, err, "Unlocking should be allowed") + + err = userslocking.WriteUnlock() + require.ErrorIs(t, err, userslocking.ErrUnlock, "Unlocking unlocked should not be allowed") } diff --git a/internal/users/locking/testlocker/testlocker.go b/internal/users/locking/testlocker/testlocker.go index d134cb2535..df36e50585 100644 --- a/internal/users/locking/testlocker/testlocker.go +++ b/internal/users/locking/testlocker/testlocker.go @@ -12,7 +12,7 @@ import ( func main() { log.Println("Locking database...") - err := userslocking.WriteRecLock() + err := userslocking.WriteLock() if err != nil { log.Fatal(err) } diff --git a/internal/users/locking/testutils.go b/internal/users/locking/testutils.go index f660b80b86..2c2405cffb 100644 --- a/internal/users/locking/testutils.go +++ b/internal/users/locking/testutils.go @@ -14,7 +14,7 @@ import ( ) var ( - overrideLocked atomic.Bool + defaultMock SimpleMock overrideMaxWait atomic.Int64 overriddenMu sync.Mutex @@ -26,6 +26,36 @@ func init() { overrideMaxWait.Store(int64(maxWait)) } +// SimpleMock is a structure that can be used to simulate the users database +// lock without relying on the actual file locking. +type SimpleMock struct { + overrideLocked atomic.Bool +} + +// WriteLock locks the mock. +func (t *SimpleMock) WriteLock() error { + testsdetection.MustBeTesting() + + if !t.overrideLocked.CompareAndSwap(false, true) { + return fmt.Errorf("%w: already locked", ErrLock) + } + + log.Debug(context.Background(), "TestOverride: Local entries locked!") + return nil +} + +// WriteUnlock unlocks the mock. +func (t *SimpleMock) WriteUnlock() error { + testsdetection.MustBeTesting() + + if !t.overrideLocked.CompareAndSwap(true, false) { + return fmt.Errorf("%w: already unlocked", ErrUnlock) + } + + log.Debug(context.Background(), "TestOverride: Local entries unlocked!") + return nil +} + // Z_ForTests_OverrideLocking is a function to override the locking functions // for testing purposes. // It simulates the real behavior but without actual file locking. @@ -39,24 +69,10 @@ func Z_ForTests_OverrideLocking() { defer overriddenMu.Unlock() overriddenWriteLockImpl = append(overriddenWriteLockImpl, writeLockImpl) - writeLockImpl = func() error { - if !overrideLocked.CompareAndSwap(false, true) { - return fmt.Errorf("%w: already locked", ErrLock) - } - - log.Debug(context.Background(), "TestOverride: Local entries locked!") - return nil - } + writeLockImpl = defaultMock.WriteLock overriddenWriteUnlockImpl = append(overriddenWriteUnlockImpl, writeUnlockImpl) - writeUnlockImpl = func() error { - if !overrideLocked.CompareAndSwap(true, false) { - return fmt.Errorf("%w: already unlocked", ErrUnlock) - } - - log.Debug(context.Background(), "TestOverride: Local entries unlocked!") - return nil - } + writeUnlockImpl = defaultMock.WriteUnlock } // Z_ForTests_OverrideLockingWithCleanup is a function to override the locking @@ -78,7 +94,7 @@ func Z_ForTests_OverrideLockingWithCleanup(t *testing.T) { // user database is locked by an external process. // // When called, it marks the user database as locked, causing any subsequent -// locking attempts by authd (via [WriteRecLock]) to block until the provided +// locking attempts by authd (via [WriteLock]) to block until the provided // context is cancelled. // // This does not use real file locking. The lock can be released either @@ -113,7 +129,7 @@ func Z_ForTests_OverrideLockingAsLockedExternally(t *testing.T, ctx context.Cont return fmt.Errorf("failed waiting for %v: %w", maxWait, ErrLockTimeout) } - if overrideLocked.CompareAndSwap(false, true) { + if defaultMock.overrideLocked.CompareAndSwap(false, true) { log.Debug(ctx, "TestOverrideExternallyLocked: Local entries locked!") break } @@ -123,7 +139,7 @@ func Z_ForTests_OverrideLockingAsLockedExternally(t *testing.T, ctx context.Cont overriddenWriteUnlockImpl = append(overriddenWriteUnlockImpl, writeUnlockImpl) writeUnlockImpl = func() error { - if !overrideLocked.CompareAndSwap(true, false) { + if !defaultMock.overrideLocked.CompareAndSwap(true, false) { return ErrUnlock } @@ -138,7 +154,7 @@ func Z_ForTests_OverrideLockingAsLockedExternally(t *testing.T, ctx context.Cont if !done.CompareAndSwap(false, true) { return } - if !overrideLocked.Load() { + if !defaultMock.overrideLocked.Load() { return } err := writeUnlockImpl() @@ -163,7 +179,7 @@ func Z_ForTests_OverrideLockingAsLockedExternally(t *testing.T, ctx context.Cont func Z_ForTests_RestoreLocking() { testsdetection.MustBeTesting() - if overrideLocked.Load() { + if defaultMock.overrideLocked.Load() { panic("Lock has not been released before restoring!") } @@ -181,7 +197,7 @@ func Z_ForTests_RestoreLocking() { } // Z_ForTests_SetMaxWaitTime sets the max time that we should wait before -// returning a failure in [WriteRecLock]. +// returning a failure in [WriteLock]. // // nolint:revive,nolintlint // We want to use underscores in the function name here. func Z_ForTests_SetMaxWaitTime(t *testing.T, maxWaitTime time.Duration) { diff --git a/internal/users/manager.go b/internal/users/manager.go index 03e1071dac..8f1ccf4268 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -151,6 +151,13 @@ func (m *Manager) updateUser(u types.UserInfo) (err error) { var uid uint32 var isNewUser bool + // TODO: only do this when we actually have to make changes. + localEntries, unlockEntries, err := localentries.NewUserDBLocked() + if err != nil { + return err + } + defer func() { err = errors.Join(err, unlockEntries()) }() + // Check if the user already exists in the database oldUser, err := m.db.UserByName(u.Name) if err != nil && !errors.Is(err, db.NoDataFoundError{}) { @@ -158,11 +165,7 @@ func (m *Manager) updateUser(u types.UserInfo) (err error) { } if errors.Is(err, db.NoDataFoundError{}) { isNewUser = true - records, recordsUnlock, err := m.temporaryRecords.LockForChanges() - if err != nil { - return err - } - defer func() { err = errors.Join(err, recordsUnlock()) }() + records := m.temporaryRecords.LockForChanges(localEntries) var cleanup func() uid, cleanup, err = records.RegisterUser(u.Name) @@ -223,11 +226,7 @@ func (m *Manager) updateUser(u types.UserInfo) (err error) { } if len(newGroups) > 0 { - records, recordsUnlock, err := m.temporaryRecords.LockForChanges() - if err != nil { - return err - } - defer func() { err = errors.Join(err, recordsUnlock()) }() + records := m.temporaryRecords.LockForChanges(localEntries) for _, g := range newGroups { gid, cleanup, err := records.RegisterGroupForUser(uid, g.Name) @@ -264,13 +263,8 @@ func (m *Manager) updateUser(u types.UserInfo) (err error) { return err } - lockedGroups, cleanup, err := localentries.GetGroupsWithLock() - if err != nil { - return err - } - defer func() { err = errors.Join(err, cleanup()) }() - // Update local groups. + lockedGroups := localentries.GetGroupsWithLock(localEntries) if err := lockedGroups.Update(u.Name, localGroups, oldLocalGroups); err != nil { return err } @@ -487,11 +481,11 @@ func (m *Manager) RegisterUserPreAuth(name string) (uid uint32, err error) { return userRow.UID, nil } - records, recordsUnlock, err := m.temporaryRecords.LockForChanges() + localEntries, unlockEntries, err := localentries.NewUserDBLocked() if err != nil { return 0, err } - defer func() { err = errors.Join(err, recordsUnlock()) }() + defer func() { err = errors.Join(err, unlockEntries()) }() - return records.RegisterPreAuthUser(name) + return m.temporaryRecords.LockForChanges(localEntries).RegisterPreAuthUser(name) } diff --git a/internal/users/manager_test.go b/internal/users/manager_test.go index 5e73870e8c..e27ccdc68a 100644 --- a/internal/users/manager_test.go +++ b/internal/users/manager_test.go @@ -694,12 +694,13 @@ func TestUserByIDAndName(t *testing.T) { m := newManagerForTests(t, dbDir) if tc.isTempUser { - records, recordsUnlock, err := m.TemporaryRecords().LockForChanges() - require.NoError(t, err, "LockForChanges should not return an error, but did") + entries, entriesUnlock, err := localentries.NewUserDBLocked() + require.NoError(t, err, "Setup: failed to lock the locale entries") t.Cleanup(func() { - err := recordsUnlock() - require.NoError(t, err, "recordsCleanup should not return an error, but did") + err = entriesUnlock() + require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") }) + records := m.TemporaryRecords().LockForChanges(entries) tc.uid, err = records.RegisterPreAuthUser("tempuser1") require.NoError(t, err, "RegisterUser should not return an error, but did") @@ -939,7 +940,7 @@ func TestRegisterUserPreAuthAfterUnlock(t *testing.T) { userslocking.Z_ForTests_OverrideLockingAsLockedExternally(t, lockCtx) userslocking.Z_ForTests_SetMaxWaitTime(t, waitTime) - t.Cleanup(func() { _ = userslocking.WriteRecUnlock() }) + t.Cleanup(func() { _ = userslocking.WriteUnlock() }) dbFile := "one_user_and_group" dbDir := t.TempDir() @@ -980,7 +981,7 @@ func TestUpdateUserAfterUnlock(t *testing.T) { userslocking.Z_ForTests_OverrideLockingAsLockedExternally(t, lockCtx) userslocking.Z_ForTests_SetMaxWaitTime(t, waitTime) - t.Cleanup(func() { _ = userslocking.WriteRecUnlock() }) + t.Cleanup(func() { _ = userslocking.WriteUnlock() }) dbFile := "one_user_and_group" dbDir := t.TempDir() diff --git a/internal/users/tempentries/tempentries.go b/internal/users/tempentries/tempentries.go index 378e4654b9..b0308df48e 100644 --- a/internal/users/tempentries/tempentries.go +++ b/internal/users/tempentries/tempentries.go @@ -12,7 +12,6 @@ import ( "github.com/ubuntu/authd/internal/testsdetection" "github.com/ubuntu/authd/internal/users/db" "github.com/ubuntu/authd/internal/users/localentries" - userslocking "github.com/ubuntu/authd/internal/users/locking" "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/authd/log" "github.com/ubuntu/decorate" @@ -45,28 +44,32 @@ type TemporaryRecords struct { // changes to [TemporaryRecords] entries while the local entries database is // locked. type TemporaryRecordsWithLock struct { - tr *TemporaryRecords - - locksMu sync.RWMutex - locks uint64 + tr *TemporaryRecords + entries *localentries.UserDBLocked + entriesMu sync.RWMutex cachedLocalPasswd []types.UserEntry cachedLocalGroup []types.GroupEntry } -func (l *TemporaryRecordsWithLock) mustBeLocked() (cleanup func()) { +func (l *TemporaryRecordsWithLock) updateLocalEntries() (err error) { // While all the [temporaryRecordsLocked] operations are happening // we need to keep a read lock on it, to prevent it being unlocked // while some action is still ongoing. - l.locksMu.RLock() - cleanup = l.locksMu.RUnlock + l.entriesMu.Lock() + defer l.entriesMu.Unlock() + + l.cachedLocalPasswd, err = l.entries.GetUserEntries() + if err != nil { + return err + } - if l.locks == 0 { - defer cleanup() - panic("locked groups are not locked!") + l.cachedLocalGroup, err = l.entries.GetGroupEntries() + if err != nil { + return err } - return cleanup + return nil } // RegisterUser registers a temporary user with a unique UID. @@ -74,8 +77,9 @@ func (l *TemporaryRecordsWithLock) mustBeLocked() (cleanup func()) { // Returns the generated UID and a cleanup function that should be called to // remove the temporary user once the user is added to the database. func (l *TemporaryRecordsWithLock) RegisterUser(name string) (uid uint32, cleanup func(), err error) { - unlock := l.mustBeLocked() - defer unlock() + if err := l.updateLocalEntries(); err != nil { + return 0, nil, err + } return l.tr.registerUser(name) } @@ -95,8 +99,9 @@ func (l *TemporaryRecordsWithLock) RegisterUser(name string) (uid uint32, cleanu // // Returns the generated UID. func (l *TemporaryRecordsWithLock) RegisterPreAuthUser(loginName string) (uid uint32, err error) { - unlock := l.mustBeLocked() - defer unlock() + if err := l.updateLocalEntries(); err != nil { + return 0, err + } return l.tr.registerPreAuthUser(loginName) } @@ -107,69 +112,11 @@ func (l *TemporaryRecordsWithLock) RegisterPreAuthUser(loginName string) (uid ui // Returns the generated GID and a cleanup function that should be called to // remove the temporary group once the group was added to the database. func (l *TemporaryRecordsWithLock) RegisterGroupForUser(uid uint32, name string) (gid uint32, cleanup func(), err error) { - unlock := l.mustBeLocked() - defer unlock() - - return l.tr.registerGroupForUser(uid, name) -} - -func (l *TemporaryRecordsWithLock) lock() (err error) { - defer decorate.OnError(&err, "could not lock temporary records") - - l.locksMu.Lock() - defer l.locksMu.Unlock() - - if l.locks != 0 { - l.locks++ - return nil - } - - log.Debug(context.Background(), "Locking temporary records ") - - l.cachedLocalPasswd, err = localentries.GetPasswdEntries() - if err != nil { - return fmt.Errorf("failed to get passwd entries: %w", err) - } - l.cachedLocalGroup, err = localentries.GetGroupEntries() - if err != nil { - return fmt.Errorf("failed to get group entries: %w", err) - } - - if err := userslocking.WriteRecLock(); err != nil { - return err - } - - l.locks++ - - return nil -} - -func (l *TemporaryRecordsWithLock) unlock() (err error) { - defer decorate.OnError(&err, "could not unlock temporary records") - - l.locksMu.Lock() - defer l.locksMu.Unlock() - - if l.locks == 0 { - return errors.New("temporary records are already unlocked") - } - - if l.locks != 1 { - l.locks-- - return nil - } - - log.Debug(context.Background(), "Unlocking temporary records") - - if err := userslocking.WriteRecUnlock(); err != nil { - return err + if err := l.updateLocalEntries(); err != nil { + return 0, nil, err } - l.cachedLocalPasswd = nil - l.cachedLocalGroup = nil - l.locks-- - - return nil + return l.tr.registerGroupForUser(uid, name) } // NewTemporaryRecords creates a new TemporaryRecords. @@ -198,10 +145,9 @@ func (r *TemporaryRecords) UserByName(name string) (types.UserEntry, error) { // with other NSS modules. // //nolint:revive,nolintlint // [temporaryRecordsLocked] is not a type we want to be able to use outside of this package -func (r *TemporaryRecords) LockForChanges() (tra *TemporaryRecordsWithLock, unlock func() error, err error) { - defer decorate.OnError(&err, "failed to lock for changes") - - lockedRecords := &TemporaryRecordsWithLock{tr: r} +func (r *TemporaryRecords) LockForChanges(entries *localentries.UserDBLocked) (tra *TemporaryRecordsWithLock) { + entries.MustBeLocked() + lockedRecords := &TemporaryRecordsWithLock{tr: r, entries: entries} for { if r.lockedRecords.CompareAndSwap(nil, lockedRecords) { @@ -213,17 +159,10 @@ func (r *TemporaryRecords) LockForChanges() (tra *TemporaryRecordsWithLock, unlo if l == nil { continue } - if err := l.lock(); err != nil { - return nil, nil, err - } - return l, l.unlock, nil - } - - if err := lockedRecords.lock(); err != nil { - return nil, nil, err + return l } - return lockedRecords, lockedRecords.unlock, nil + return lockedRecords } func (r *TemporaryRecords) registerUser(name string) (uid uint32, cleanup func(), err error) { @@ -355,6 +294,9 @@ func (r *TemporaryRecords) passwdEntries() []types.UserEntry { return entries } + l.entriesMu.RLock() + defer l.entriesMu.RUnlock() + return l.cachedLocalPasswd } @@ -370,6 +312,9 @@ func (r *TemporaryRecords) groupEntries() []types.GroupEntry { return entries } + l.entriesMu.RLock() + defer l.entriesMu.RUnlock() + return l.cachedLocalGroup } diff --git a/internal/users/tempentries/tempentries_test.go b/internal/users/tempentries/tempentries_test.go index e899f807c6..bbe440b49f 100644 --- a/internal/users/tempentries/tempentries_test.go +++ b/internal/users/tempentries/tempentries_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ubuntu/authd/internal/testutils/golden" "github.com/ubuntu/authd/internal/users/idgenerator" + "github.com/ubuntu/authd/internal/users/localentries" userslocking "github.com/ubuntu/authd/internal/users/locking" "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/authd/log" @@ -25,14 +26,21 @@ func TestLockedInvalidActions(t *testing.T) { require.Panics(t, func() { _, _, _ = (&TemporaryRecordsWithLock{}).RegisterGroupForUser(123, "foobar") }, "RegisterGroupForUser should panic but did not") - tmpRecords := NewTemporaryRecords(&idgenerator.IDGenerator{}) - records, recordsUnlock, err := tmpRecords.LockForChanges() - require.NoError(t, err, "Setup: failed to lock the temporary records") - err = recordsUnlock() - require.NoError(t, err, "recordsUnlock should not fail to unlock the temporary records") + require.Panics(t, func() { NewTemporaryRecords(nil).LockForChanges(&localentries.UserDBLocked{}) }, + "LockForChanges should panic but did not") - err = recordsUnlock() - require.Error(t, err, "Cleaning up twice should fail") + tmpRecords := NewTemporaryRecords(&idgenerator.IDGenerator{UIDMax: 100}) + + entries, entriesUnlock, err := localentries.NewUserDBLocked(([]localentries.Option{})...) + require.NoError(t, err, "Setup: failed to lock the locale entries") + + records := tmpRecords.LockForChanges(entries) + + err = entriesUnlock() + require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") + + err = entriesUnlock() + require.Error(t, err, "Unlocking twice should fail") require.Panics(t, func() { _, _ = records.RegisterPreAuthUser("foobar") }, "RegisterPreAuthUser should panic but did not") @@ -43,11 +51,12 @@ func TestLockedInvalidActions(t *testing.T) { // This is to ensure that we're in a good state, despite the actions above for range 10 { - _, recordsUnlock, err := tmpRecords.LockForChanges() - require.NoError(t, err, "Failed to lock the temporary records") + entries, entriesUnlock, err := localentries.NewUserDBLocked() + require.NoError(t, err, "Failed to lock the local entries") + _ = tmpRecords.LockForChanges(entries) defer func() { - err := recordsUnlock() - require.NoError(t, err, "recordsUnlock should not fail to unlock the temporary records") + err := entriesUnlock() + require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") }() } } @@ -90,13 +99,14 @@ func TestRacingLockingActions(t *testing.T) { t.Run(fmt.Sprintf("iteration_%d", idx), func(t *testing.T) { t.Parallel() - records, recordsUnlock, err := tmpRecords.LockForChanges() - require.NoError(t, err, "Setup: failed to lock the temporary records") + entries, entriesUnlock, err := localentries.NewUserDBLocked() + require.NoError(t, err, "Setup: failed to lock the locale entries") t.Cleanup(func() { - err = recordsUnlock() - require.NoError(t, err, "recordsUnlock should not fail to unlock the temporary records") + err = entriesUnlock() + require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") }) + records := tmpRecords.LockForChanges(entries) doPreAuth := idx%3 == 0 userName := fmt.Sprintf("authd-test-user%d", idx) @@ -576,13 +586,14 @@ func TestRegisterUserAndGroupForUser(t *testing.T) { replacingPreAuthUser = err == nil } - records, recordsUnlock, err := records.LockForChanges() - require.NoError(t, err, "LockForChanges should not return an error, but did") + entries, entriesUnlock, err := localentries.NewUserDBLocked() + require.NoError(t, err, "Setup: failed to lock the locale entries") t.Cleanup(func() { - err := recordsUnlock() - require.NoError(t, err, "recordsCleanup should not return an error, but did") + err = entriesUnlock() + require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") }) + records := records.LockForChanges(entries) internalRecords := records.tr var uid uint32 From f757c938c735be79ecd6663604ead27d3b5196a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 27 Jun 2025 18:49:59 +0200 Subject: [PATCH 0560/1670] users/tempentries: Drop cached entries They are now cached in the locked entries so we don't need them anymore. This implies more error checking, but that's go life! --- internal/users/localentries/lockedentries.go | 2 +- internal/users/tempentries/tempentries.go | 168 ++++++++++--------- 2 files changed, 94 insertions(+), 76 deletions(-) diff --git a/internal/users/localentries/lockedentries.go b/internal/users/localentries/lockedentries.go index ce25121ef0..67571330b9 100644 --- a/internal/users/localentries/lockedentries.go +++ b/internal/users/localentries/lockedentries.go @@ -55,7 +55,7 @@ type UserDBLocked struct { // mu is a mutex that protects the refCount and entries fields. mu sync.Mutex // refCount is used to track how many times the GroupsWithLock instance has - // been returned by [NewWithLock]. + // been returned by [NewUserDBLocked]. refCount uint64 // localGroupsMu is the mutex that protects us globally from concurrent diff --git a/internal/users/tempentries/tempentries.go b/internal/users/tempentries/tempentries.go index b0308df48e..cca18a6802 100644 --- a/internal/users/tempentries/tempentries.go +++ b/internal/users/tempentries/tempentries.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "slices" - "sync" "sync/atomic" "github.com/ubuntu/authd/internal/testsdetection" @@ -46,30 +45,6 @@ type TemporaryRecords struct { type TemporaryRecordsWithLock struct { tr *TemporaryRecords entries *localentries.UserDBLocked - - entriesMu sync.RWMutex - cachedLocalPasswd []types.UserEntry - cachedLocalGroup []types.GroupEntry -} - -func (l *TemporaryRecordsWithLock) updateLocalEntries() (err error) { - // While all the [temporaryRecordsLocked] operations are happening - // we need to keep a read lock on it, to prevent it being unlocked - // while some action is still ongoing. - l.entriesMu.Lock() - defer l.entriesMu.Unlock() - - l.cachedLocalPasswd, err = l.entries.GetUserEntries() - if err != nil { - return err - } - - l.cachedLocalGroup, err = l.entries.GetGroupEntries() - if err != nil { - return err - } - - return nil } // RegisterUser registers a temporary user with a unique UID. @@ -77,10 +52,6 @@ func (l *TemporaryRecordsWithLock) updateLocalEntries() (err error) { // Returns the generated UID and a cleanup function that should be called to // remove the temporary user once the user is added to the database. func (l *TemporaryRecordsWithLock) RegisterUser(name string) (uid uint32, cleanup func(), err error) { - if err := l.updateLocalEntries(); err != nil { - return 0, nil, err - } - return l.tr.registerUser(name) } @@ -99,10 +70,6 @@ func (l *TemporaryRecordsWithLock) RegisterUser(name string) (uid uint32, cleanu // // Returns the generated UID. func (l *TemporaryRecordsWithLock) RegisterPreAuthUser(loginName string) (uid uint32, err error) { - if err := l.updateLocalEntries(); err != nil { - return 0, err - } - return l.tr.registerPreAuthUser(loginName) } @@ -112,10 +79,6 @@ func (l *TemporaryRecordsWithLock) RegisterPreAuthUser(loginName string) (uid ui // Returns the generated GID and a cleanup function that should be called to // remove the temporary group once the group was added to the database. func (l *TemporaryRecordsWithLock) RegisterGroupForUser(uid uint32, name string) (gid uint32, cleanup func(), err error) { - if err := l.updateLocalEntries(); err != nil { - return 0, nil, err - } - return l.tr.registerGroupForUser(uid, name) } @@ -168,7 +131,11 @@ func (r *TemporaryRecords) LockForChanges(entries *localentries.UserDBLocked) (t func (r *TemporaryRecords) registerUser(name string) (uid uint32, cleanup func(), err error) { defer decorate.OnError(&err, "failed to register user %q", name) - if !r.isUniqueSystemUserName(name) { + unique, err := r.isUniqueSystemUserName(name) + if err != nil { + return 0, nil, err + } + if !unique { return 0, nil, fmt.Errorf("user %q already exists", name) } @@ -197,7 +164,12 @@ func (r *TemporaryRecords) registerUser(name string) (uid uint32, cleanup func() // if the UID is unique, since that's something we did while registering it, and we're // currently locked, so nothing else can add another user with such ID, but we do to // double check it, just in case. - if !r.isUniqueSystemID(user.UID) { + unique, err := r.isUniqueSystemID(user.UID) + if err != nil { + cleanup() + return 0, nil, err + } + if !unique { cleanup() return 0, nil, fmt.Errorf("UID (%d) or name (%q) from pre-auth user are not unique", user.UID, name) } @@ -212,7 +184,11 @@ func (r *TemporaryRecords) registerUser(name string) (uid uint32, cleanup func() return 0, nil, err } - if unique := r.maybeTrackUniqueID(uid); !unique { + unique, err := r.maybeTrackUniqueID(uid) + if err != nil { + return 0, nil, err + } + if !unique { // If the UID is not unique, generate a new one in the next iteration. continue } @@ -245,7 +221,11 @@ func (r *TemporaryRecords) registerPreAuthUser(loginName string) (uid uint32, er return uid, nil } - if !r.isUniqueSystemUserName(loginName) { + unique, err := r.isUniqueSystemUserName(loginName) + if err != nil { + return 0, err + } + if !unique { log.Errorf(context.Background(), "User already exists on the system: %+v", loginName) return 0, fmt.Errorf("user %q already exists on the system", loginName) } @@ -256,7 +236,11 @@ func (r *TemporaryRecords) registerPreAuthUser(loginName string) (uid uint32, er return 0, err } - if unique := r.maybeTrackUniqueID(uid); !unique { + unique, err := r.maybeTrackUniqueID(uid) + if err != nil { + return 0, err + } + if !unique { // If the UID is not unique, generate a new one in the next iteration. continue } @@ -282,46 +266,57 @@ func (r *TemporaryRecords) registerPreAuthUser(loginName string) (uid uint32, er maxIDGenerateIterations) } -func (r *TemporaryRecords) passwdEntries() []types.UserEntry { +func (r *TemporaryRecords) userEntries() ([]types.UserEntry, error) { l := r.lockedRecords.Load() if l == nil { testsdetection.MustBeTesting() - entries, err := localentries.GetPasswdEntries() + le, cleanup, err := localentries.NewUserDBLocked() if err != nil { - panic(fmt.Sprintf("Failed get local passwd: %v", err)) + panic(fmt.Sprintf("Failed get lock local entries: %v", err)) } - return entries - } + defer func() { + if err := cleanup(); err != nil { + panic(fmt.Sprintf("Failed get cleanup locked entries: %v", err)) + } + }() - l.entriesMu.RLock() - defer l.entriesMu.RUnlock() + l = &TemporaryRecordsWithLock{entries: le} + } - return l.cachedLocalPasswd + return l.entries.GetUserEntries() } -func (r *TemporaryRecords) groupEntries() []types.GroupEntry { +func (r *TemporaryRecords) groupEntries() ([]types.GroupEntry, error) { l := r.lockedRecords.Load() if l == nil { testsdetection.MustBeTesting() - entries, err := localentries.GetGroupEntries() + le, cleanup, err := localentries.NewUserDBLocked() if err != nil { - panic(fmt.Sprintf("Failed get local groups: %v", err)) + panic(fmt.Sprintf("Failed get lock local entries: %v", err)) } - return entries - } + defer func() { + if err := cleanup(); err != nil { + panic(fmt.Sprintf("Failed get cleanup locked entries: %v", err)) + } + }() - l.entriesMu.RLock() - defer l.entriesMu.RUnlock() + l = &TemporaryRecordsWithLock{entries: le} + } - return l.cachedLocalGroup + return l.entries.GetGroupEntries() } func (r *TemporaryRecords) registerGroupForUser(uid uint32, name string) (gid uint32, cleanup func(), err error) { defer decorate.OnError(&err, "failed to register group %q for user ID %d", name, uid) - if slices.ContainsFunc(r.groupEntries(), func(g types.GroupEntry) bool { + groupEntries, err := r.groupEntries() + if err != nil { + return 0, nil, err + } + + if slices.ContainsFunc(groupEntries, func(g types.GroupEntry) bool { return g.Name == name }) { return 0, nil, fmt.Errorf("group %q already exists", name) @@ -354,7 +349,11 @@ func (r *TemporaryRecords) registerGroupForUser(uid uint32, name string) (gid ui continue } - if unique := r.maybeTrackUniqueID(gid); !unique { + unique, err := r.maybeTrackUniqueID(gid) + if err != nil { + return 0, nil, err + } + if !unique { // If the GID is not unique, generate a new one in the next iteration. continue } @@ -368,42 +367,61 @@ func (r *TemporaryRecords) registerGroupForUser(uid uint32, name string) (gid ui maxIDGenerateIterations) } -func (r *TemporaryRecords) maybeTrackUniqueID(id uint32) (unique bool) { +func (r *TemporaryRecords) maybeTrackUniqueID(id uint32) (unique bool, err error) { defer func() { if !unique { log.Debugf(context.TODO(), "ID %d is not unique in this system", id) } }() - if !r.isUniqueSystemID(id) { - return false + unique, err = r.isUniqueSystemID(id) + if err != nil { + return false, err + } + if !unique { + return false, nil } - return r.idTracker.trackID(id) + return r.idTracker.trackID(id), nil } -func (r *TemporaryRecords) isUniqueSystemID(id uint32) bool { - if idx := slices.IndexFunc(r.passwdEntries(), func(p types.UserEntry) (found bool) { +func (r *TemporaryRecords) isUniqueSystemID(id uint32) (unique bool, err error) { + userEntries, err := r.userEntries() + if err != nil { + return false, err + } + + if idx := slices.IndexFunc(userEntries, func(p types.UserEntry) (found bool) { return p.UID == id }); idx != -1 { log.Debugf(context.Background(), "ID %d already in use by user %q", - id, r.passwdEntries()[idx].Name) - return false + id, userEntries[idx].Name) + return false, nil + } + + groupEntries, err := r.groupEntries() + if err != nil { + return false, err } - if idx := slices.IndexFunc(r.groupEntries(), func(g types.GroupEntry) (found bool) { + if idx := slices.IndexFunc(groupEntries, func(g types.GroupEntry) (found bool) { return g.GID == id }); idx != -1 { log.Debugf(context.Background(), "ID %d already in use by user %q", - id, r.groupEntries()[idx].Name) - return false + id, groupEntries[idx].Name) + return false, nil } - return true + return true, nil } -func (r *TemporaryRecords) isUniqueSystemUserName(name string) bool { - return !slices.ContainsFunc(r.passwdEntries(), func(p types.UserEntry) bool { +func (r *TemporaryRecords) isUniqueSystemUserName(name string) (unique bool, err error) { + userEntries, err := r.userEntries() + if err != nil { + return false, err + } + + return !slices.ContainsFunc(userEntries, func(p types.UserEntry) bool { return p.Name == name - }) + }), nil } From 1baaa676cf95e36c805a1baa9fbee7b013db9068 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 27 Jun 2025 19:39:38 +0200 Subject: [PATCH 0561/1670] users/manager: Only lock during update if we have to change something We only need to lock the users system database in case we are doing a change or if we need to compare system values with ours, so limit the locking to these cases. While the usage of `localentries.UpdateGroups()` may potentially now lead to a locking/unlocking dance, this is not yet a problem because we don't do anything else after it, but it may be an issue if we need to lock again afterwards --- internal/users/manager.go | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/internal/users/manager.go b/internal/users/manager.go index 8f1ccf4268..12499ef1e6 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -151,13 +151,6 @@ func (m *Manager) updateUser(u types.UserInfo) (err error) { var uid uint32 var isNewUser bool - // TODO: only do this when we actually have to make changes. - localEntries, unlockEntries, err := localentries.NewUserDBLocked() - if err != nil { - return err - } - defer func() { err = errors.Join(err, unlockEntries()) }() - // Check if the user already exists in the database oldUser, err := m.db.UserByName(u.Name) if err != nil && !errors.Is(err, db.NoDataFoundError{}) { @@ -165,6 +158,11 @@ func (m *Manager) updateUser(u types.UserInfo) (err error) { } if errors.Is(err, db.NoDataFoundError{}) { isNewUser = true + localEntries, unlockEntries, err := localentries.NewUserDBLocked() + if err != nil { + return err + } + defer func() { err = errors.Join(err, unlockEntries()) }() records := m.temporaryRecords.LockForChanges(localEntries) var cleanup func() @@ -226,6 +224,11 @@ func (m *Manager) updateUser(u types.UserInfo) (err error) { } if len(newGroups) > 0 { + localEntries, unlockEntries, err := localentries.NewUserDBLocked() + if err != nil { + return err + } + defer func() { err = errors.Join(err, unlockEntries()) }() records := m.temporaryRecords.LockForChanges(localEntries) for _, g := range newGroups { @@ -264,6 +267,12 @@ func (m *Manager) updateUser(u types.UserInfo) (err error) { } // Update local groups. + localEntries, unlockEntries, err := localentries.NewUserDBLocked() + if err != nil { + return err + } + defer func() { err = errors.Join(err, unlockEntries()) }() + lockedGroups := localentries.GetGroupsWithLock(localEntries) if err := lockedGroups.Update(u.Name, localGroups, oldLocalGroups); err != nil { return err From 1359dd6689b168d269fac729a8e69db6a40a48ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 23 Jun 2025 00:40:04 +0200 Subject: [PATCH 0562/1670] sliceutils: Add support to diff generic structures If slices contains non-comparable elements, support an equality function that can be to compare such types --- internal/sliceutils/sliceutils.go | 12 +++++++ internal/sliceutils/sliceutils_test.go | 48 ++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/internal/sliceutils/sliceutils.go b/internal/sliceutils/sliceutils.go index cc3d465227..7f8770d8a7 100644 --- a/internal/sliceutils/sliceutils.go +++ b/internal/sliceutils/sliceutils.go @@ -19,6 +19,18 @@ func Difference[T comparable](a, b []T) []T { return diff } +// DifferenceFunc returns a slice with the elements that are in a but not in b, +// supporting a function to compare the items. +func DifferenceFunc[S ~[]E, E any](a, b S, f func(E, E) bool) S { + var diff S + for _, aItem := range a { + if !slices.ContainsFunc(b, func(bItem E) bool { return f(aItem, bItem) }) { + diff = append(diff, aItem) + } + } + return diff +} + // Intersection returns a slice with the elements that are in both a and b. func Intersection[T comparable](a, b []T) []T { setB := make(map[T]struct{}, len(b)) diff --git a/internal/sliceutils/sliceutils_test.go b/internal/sliceutils/sliceutils_test.go index 72a8feecc5..41eba26c68 100644 --- a/internal/sliceutils/sliceutils_test.go +++ b/internal/sliceutils/sliceutils_test.go @@ -1,6 +1,7 @@ package sliceutils_test import ( + "slices" "testing" "github.com/stretchr/testify/require" @@ -45,6 +46,53 @@ func TestDifference(t *testing.T) { } } +func TestDifferenceFunc(t *testing.T) { + t.Parallel() + + type notComparable struct { + i int + ii []int + } + + notComparableCompareFunc := func(a notComparable, b notComparable) bool { + return a.i == b.i && slices.Equal(a.ii, b.ii) + } + + tests := map[string]struct { + a, b, want []notComparable + }{ + "test_difference_between_two_slices": { + a: []notComparable{{i: 1}, {i: 2}, {i: 3}, {i: 4}, {i: 5, ii: []int{1, 2}}}, + b: []notComparable{{i: 3}, {i: 4}, {i: 5}, {i: 6}, {i: 7}}, + want: []notComparable{{i: 1}, {i: 2}, {i: 5, ii: []int{1, 2}}}, + }, + "test_difference_between_an_empty_slice_and_a_non-empty_slice": { + a: []notComparable{}, + b: []notComparable{{i: 3}, {i: 4}, {i: 5}, {i: 6}, {i: 7}}, + want: []notComparable(nil), + }, + "test_difference_between_a_non-empty_slice_and_an_empty_slice": { + a: []notComparable{{i: 1}, {i: 2}, {i: 3}, {i: 4}, {i: 5}}, + b: []notComparable{}, + want: []notComparable{{i: 1}, {i: 2}, {i: 3}, {i: 4}, {i: 5}}, + }, + "test_difference_between_two_empty_slices": { + a: []notComparable{}, + b: []notComparable{}, + want: []notComparable(nil), + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := sliceutils.DifferenceFunc(tc.a, tc.b, notComparableCompareFunc) + require.Equal(t, tc.want, got) + }) + } +} + func TestIntersection(t *testing.T) { t.Parallel() From b3bb7d2fc1dd958836f40389999ef1d3607235f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Sat, 28 Jun 2025 04:04:16 +0200 Subject: [PATCH 0563/1670] users/localentries/localgroups: Improve groups validation Basically try to do this: - When getting the entries, just warn if some entry is not valid but still return it. - When writing the entries: * If the newly added or changed entries are invalid (alone): fail * If all the provided entries are valid: just continue normally * If the new provided entries are compatible with the valid entries that were previously saved: continue as it is --- internal/users/localentries/export_test.go | 10 +- internal/users/localentries/localgroups.go | 45 +++- .../users/localentries/localgroups_test.go | 196 ++++++++++++++++-- ...n_groups_file_has_a_duplicated_group.group | 8 + ...s_file_has_a_duplicated_group.group.backup | 7 + internal/users/types/groupentry.go | 48 +++++ internal/users/types/groupentry_test.go | 120 +++++++++++ 7 files changed, 416 insertions(+), 18 deletions(-) create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Warn_when_groups_file_has_a_duplicated_group.group create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Warn_when_groups_file_has_a_duplicated_group.group.backup diff --git a/internal/users/localentries/export_test.go b/internal/users/localentries/export_test.go index 2c3a2ef38d..bf7dadfb06 100644 --- a/internal/users/localentries/export_test.go +++ b/internal/users/localentries/export_test.go @@ -1,6 +1,9 @@ package localentries -import userslocking "github.com/ubuntu/authd/internal/users/locking" +import ( + userslocking "github.com/ubuntu/authd/internal/users/locking" + "github.com/ubuntu/authd/internal/users/types" +) // WithGroupPath overrides the default /etc/group path for tests. func WithGroupPath(p string) Option { @@ -46,3 +49,8 @@ func WithUserDBLockedInstance(userDBLocked *UserDBLocked) Option { func GroupFileBackupPath(groupFilePath string) string { return groupFileBackupPath(groupFilePath) } + +// ValidateChangedGroups validates the new groups given the current, changed and new groups. +func ValidateChangedGroups(currentGroups, newGroups []types.GroupEntry) error { + return validateChangedGroups(currentGroups, newGroups) +} diff --git a/internal/users/localentries/localgroups.go b/internal/users/localentries/localgroups.go index 7e31fc753d..d847e76675 100644 --- a/internal/users/localentries/localgroups.go +++ b/internal/users/localentries/localgroups.go @@ -170,7 +170,9 @@ func parseLocalGroups(groupPath string) (groups []types.GroupEntry, err error) { } if err := types.ValidateGroupEntries(groups); err != nil { - return nil, err + log.Warningf(context.Background(), + "The group file %q contains at least one invalid entry: %v", + groupPath, err) } return groups, nil @@ -207,20 +209,21 @@ func (g *GroupsWithLock) saveLocalGroups(groups []types.GroupEntry) (err error) } if slices.EqualFunc(currentGroups, groups, types.GroupEntry.Equals) { - log.Debugf(context.TODO(), "Nothing to do, groups are equal") + log.Debugf(context.Background(), "Nothing to do, groups are equal") return nil } - if err := types.ValidateGroupEntries(groups); err != nil { + if err := validateChangedGroups(currentGroups, groups); err != nil { + log.Debugf(context.Background(), "New groups are not valid: %v", err) return err } backupPath := groupFileBackupPath(groupPath) groupsEntries := formatGroupEntries(groups) - log.Debugf(context.TODO(), "Saving group entries %#v to %q", groups, groupPath) + log.Debugf(context.Background(), "Saving group entries %#v to %q", groups, groupPath) if len(groupsEntries) > 0 { - log.Debugf(context.TODO(), "Group file content:\n%s", groupsEntries) + log.Debugf(context.Background(), "Group file content:\n%s", groupsEntries) } if err := os.Remove(backupPath); err != nil && !errors.Is(err, os.ErrNotExist) { @@ -246,6 +249,38 @@ func (g *GroupsWithLock) saveLocalGroups(groups []types.GroupEntry) (err error) return nil } +func validateChangedGroups(currentGroups, newGroups []types.GroupEntry) error { + changedGroups := sliceutils.DifferenceFunc(newGroups, currentGroups, + types.GroupEntry.Equals) + if len(changedGroups) == 0 { + log.Debugf(context.Background(), "No new groups added to validate") + return nil + } + + log.Debugf(context.Background(), "Groups added or modified: %#v", + changedGroups) + + if err := types.ValidateGroupEntries(changedGroups); err != nil { + // One of the group that has been changed is not valid. + return fmt.Errorf("changed groups are not valid: %w", err) + } + + if err := types.ValidateGroupEntries(newGroups); err == nil { + // The groups we got are all good, no need to proceed further! + return nil + } + + validCurrentGroups := types.GetValidGroupEntries(currentGroups) + + // So, now we know that: + // 1) the changed groups alone are good + // 2) the whole set of the new groups are not good + // So let's try to check if the changed groups are compatible with the + // current valid groups that we have. + validGroupsWithChanged := append(validCurrentGroups, changedGroups...) + return types.ValidateGroupEntries(validGroupsWithChanged) +} + // userLocalGroups returns all groups the user is part of. func userLocalGroups(entries []types.GroupEntry, user string) (userGroups []types.GroupEntry) { return slices.DeleteFunc(slices.Clone(entries), func(g types.GroupEntry) bool { diff --git a/internal/users/localentries/localgroups_test.go b/internal/users/localentries/localgroups_test.go index b4885a4010..2f6b3c3946 100644 --- a/internal/users/localentries/localgroups_test.go +++ b/internal/users/localentries/localgroups_test.go @@ -58,6 +58,11 @@ func TestUpdatelocalentries(t *testing.T) { "User_is_removed_from_old_groups_but_not_from_other_groups": {newGroups: []string{}, oldGroups: []string{"localgroup3"}, groupFilePath: "user_in_both_groups.group"}, "User_is_not_removed_from_groups_they_are_not_part_of": {newGroups: []string{}, oldGroups: []string{"localgroup2"}, groupFilePath: "user_in_one_group.group"}, + "Warn_when_groups_file_has_no_group_name": { + groupFilePath: "malformed_file_no_group_name.group", + newGroups: []string{"localgroup6"}, + }, + // Error cases "Error_on_missing_groups_file": { groupFilePath: "does_not_exists.group", @@ -76,10 +81,6 @@ func TestUpdatelocalentries(t *testing.T) { groupFilePath: "malformed_file_invalid_gid.group", wantErr: true, }, - "Error_when_groups_file_has_no_group_name": { - groupFilePath: "malformed_file_no_group_name.group", - wantErr: true, - }, "Error_when_groups_file_has_a_duplicated_group": { groupFilePath: "malformed_file_duplicated.group", wantErr: true, @@ -226,6 +227,15 @@ func TestGetAndSaveLocalGroups(t *testing.T) { groupFilePath: "multiple_users_in_our_groups.group", removeGroups: []string{"localgroup1", "localgroup4"}, }, + "Warn_when_groups_file_has_a_duplicated_group": { + groupFilePath: "malformed_file_duplicated.group", + addGroups: []types.GroupEntry{ + {Name: "localgroup5", Passwd: "x", GID: 45}, + }, + }, + "Warn_when_groups_file_has_no_group_name": { + groupFilePath: "malformed_file_no_group_name.group", + }, // Error cases "Error_on_missing_groups_file": { @@ -240,14 +250,6 @@ func TestGetAndSaveLocalGroups(t *testing.T) { groupFilePath: "malformed_file_invalid_gid.group", wantGetErr: true, }, - "Error_when_groups_file_has_no_group_name": { - groupFilePath: "malformed_file_no_group_name.group", - wantGetErr: true, - }, - "Error_when_groups_file_has_a_duplicated_group": { - groupFilePath: "malformed_file_duplicated.group", - wantGetErr: true, - }, "Error_adding_duplicated_groups": { groupFilePath: "empty.group", addGroups: []types.GroupEntry{ @@ -464,6 +466,176 @@ func TestLockedInvalidActions(t *testing.T) { } } +func TestValidateChangedGroups(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + currentGroups []types.GroupEntry + changedGroups []types.GroupEntry + newGroups []types.GroupEntry + wantErr bool + }{ + "Empty_groups_no_error": {}, + "Add_valid_group": { + currentGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + }, + newGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + {Name: "group2", Passwd: "x", GID: 2, Users: []string{"user2"}}, + }, + }, + "No_changes_in_old_groups": { + currentGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + {Name: "group2", Passwd: "x", GID: 2, Users: []string{"user2"}}, + }, + newGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + {Name: "group2", Passwd: "x", GID: 2, Users: []string{"user2"}}, + }, + }, + "Replace_old_groups_with_same_name_valid": { + currentGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + {Name: "group2", Passwd: "x", GID: 2, Users: []string{"user2"}}, + }, + newGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + {Name: "group2", Passwd: "x", GID: 2, Users: []string{"user3"}}, + }, + }, + "Add_multiple_valid_groups": { + currentGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + }, + newGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + {Name: "group2", Passwd: "x", GID: 2, Users: []string{"user2"}}, + {Name: "group3", Passwd: "x", GID: 3, Users: []string{"user3"}}, + }, + }, + "Removed_group": { + currentGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + {Name: "group2", Passwd: "x", GID: 2, Users: []string{"user2"}}, + }, + newGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + // group2 removed + }, + }, + "Removed_multiple_groups": { + currentGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + {Name: "group2", Passwd: "x", GID: 2, Users: []string{"user2"}}, + {Name: "group3", Passwd: "x", GID: 3, Users: []string{"user3"}}, + }, + newGroups: []types.GroupEntry{ + {Name: "group3", Passwd: "x", GID: 3, Users: []string{"user3"}}, + // group1 and group2 removed + }, + }, + "All_groups_removed": { + currentGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + }, + newGroups: []types.GroupEntry{ + // All groups removed + }, + }, + "Add_valid_group_to_current_groups_with_invalid": { + currentGroups: []types.GroupEntry{ + {Name: "invalid1", Passwd: "x", GID: 1, Users: []string{"user,1"}}, + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + }, + newGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + {Name: "invalid1", Passwd: "x", GID: 1, Users: []string{"user,1"}}, + {Name: "group2", Passwd: "x", GID: 2, Users: []string{"user2"}}, + }, + }, + "Add_user_to_group": { + currentGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + }, + newGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1", "user2"}}, + }, + }, + + // Error cases. + "Error_changed_groups_invalid": { + currentGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + }, + newGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + {Name: "invalid,group", Passwd: "x", GID: 2, Users: []string{"user2"}}, + }, + wantErr: true, + }, + "Error_combined_groups_invalid": { + currentGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + }, + newGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + {Name: "group2", Passwd: "x", GID: 1, Users: []string{"user2"}}, + }, + wantErr: true, + }, + "Error_adding_multiple_groups_one_invalid": { + currentGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + }, + newGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + {Name: "group2", Passwd: "x", GID: 2, Users: []string{"user2"}}, + {Name: "invalid,group", Passwd: "x", GID: 4, Users: []string{"user4"}}, + }, + wantErr: true, + }, + "Error_adding_multiple_groups_with_duplicate_GID": { + currentGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + }, + newGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + {Name: "group2", Passwd: "x", GID: 2, Users: []string{"user2"}}, + {Name: "group3", Passwd: "x", GID: 2, Users: []string{"user3"}}, + }, + wantErr: true, + }, + "Error_adding_multiple_groups_with_duplicate_name": { + currentGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + }, + newGroups: []types.GroupEntry{ + {Name: "group1", Passwd: "x", GID: 1, Users: []string{"user1"}}, + {Name: "group2", Passwd: "x", GID: 2, Users: []string{"user2"}}, + {Name: "group2", Passwd: "x", GID: 22, Users: []string{"user2.2"}}, + }, + wantErr: true, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + err := localentries.ValidateChangedGroups(tc.currentGroups, + tc.newGroups) + if tc.wantErr { + require.Error(t, err, "expected error but got nil") + t.Logf("Validation failed with error: %v", err) + return + } + require.NoError(t, err, "expected no error but got: %v", err) + }) + } +} + func TestMain(m *testing.M) { log.SetLevel(log.DebugLevel) diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Warn_when_groups_file_has_a_duplicated_group.group b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Warn_when_groups_file_has_a_duplicated_group.group new file mode 100644 index 0000000000..42223c8ad5 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Warn_when_groups_file_has_a_duplicated_group.group @@ -0,0 +1,8 @@ +localgroup1:x:41:otheruser +localgroup2:x:42:otheruser2 +localgroup3:x:43:invalidGID +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 +localgroup4:x:123:again +localgroup5:x:45: diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Warn_when_groups_file_has_a_duplicated_group.group.backup b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Warn_when_groups_file_has_a_duplicated_group.group.backup new file mode 100644 index 0000000000..df29880934 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Warn_when_groups_file_has_a_duplicated_group.group.backup @@ -0,0 +1,7 @@ +localgroup1:x:41:otheruser +localgroup2:x:42:otheruser2 +localgroup3:x:43:invalidGID +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 +localgroup4:x:123:again diff --git a/internal/users/types/groupentry.go b/internal/users/types/groupentry.go index 040f2f1355..37bce05762 100644 --- a/internal/users/types/groupentry.go +++ b/internal/users/types/groupentry.go @@ -97,3 +97,51 @@ func ValidateGroupEntries(groups []GroupEntry) error { return nil } + +// GetValidGroupEntries returns the first valid group entries according to validates a list of group entries, ensuring they respect +// the [GroupEntry.Validate] constraints and that the names and the GID are unique. +func GetValidGroupEntries(groups []GroupEntry) (validEntries []GroupEntry) { + groupNames := make(map[string]*GroupEntry, len(groups)) + groupIDs := make(map[uint32]*GroupEntry, len(groups)) + + if groups == nil { + return nil + } + + validEntries = make([]GroupEntry, 0, len(groups)) + + for _, g := range groups { + if err := g.Validate(); err != nil { + log.Warningf(context.Background(), "Group %q is not valid: %v", g.Name, err) + continue + } + + if otherGroup, ok := groupNames[g.Name]; ok { + if !g.Equals(*otherGroup) { + log.Warningf(context.Background(), + "Skipping group %q, it's a duplicate!", g) + continue + } + + log.Infof(context.Background(), + "Group %q name (%q) is not unique (duplicates %q)", g.Name, g, *otherGroup) + + // We still consider it valid, not to miss track of it, while ideally + // it should be just dropped, but maybe it's not our duty. + validEntries = append(validEntries, g) + continue + } + if otherGroup, ok := groupIDs[g.GID]; ok { + log.Warningf(context.Background(), + "Group %q ID (%d) is not unique (duplicates %q)", g.Name, g.GID, *otherGroup) + continue + } + + groupNames[g.Name] = &g + groupIDs[g.GID] = &g + + validEntries = append(validEntries, g) + } + + return validEntries +} diff --git a/internal/users/types/groupentry_test.go b/internal/users/types/groupentry_test.go index 094a22d446..e1148d7c60 100644 --- a/internal/users/types/groupentry_test.go +++ b/internal/users/types/groupentry_test.go @@ -390,3 +390,123 @@ func TestDeepCopyGroupEntries(t *testing.T) { }) } } + +func TestGetValidGroupEntries(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + groups []GroupEntry + want []GroupEntry + }{ + { + name: "Nil_input", + }, + { + name: "Empty_input", + groups: []GroupEntry{}, + want: []GroupEntry{}, + }, + { + name: "All_valid_groups", + groups: []GroupEntry{ + {Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + {Name: "group2", GID: 1001, Passwd: "y", Users: []string{"user2"}}, + }, + want: []GroupEntry{ + {Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + {Name: "group2", GID: 1001, Passwd: "y", Users: []string{"user2"}}, + }, + }, + { + name: "Invalid_group_skipped", + groups: []GroupEntry{ + {Name: "invalid,group", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + {Name: "group2", GID: 1001, Passwd: "y", Users: []string{"user2"}}, + }, + want: []GroupEntry{ + {Name: "group2", GID: 1001, Passwd: "y", Users: []string{"user2"}}, + }, + }, + { + name: "Duplicate_name_with_equal_content", + groups: []GroupEntry{ + {Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + {Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + }, + want: []GroupEntry{ + {Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + {Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + }, + }, + { + name: "Duplicate_name_with_different_content", + groups: []GroupEntry{ + {Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + {Name: "group1", GID: 1001, Passwd: "y", Users: []string{"user2"}}, + {Name: "group2", GID: 1002, Passwd: "z", Users: []string{"user3"}}, + }, + want: []GroupEntry{ + {Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + {Name: "group2", GID: 1002, Passwd: "z", Users: []string{"user3"}}, + }, + }, + { + name: "Duplicate_GID", + groups: []GroupEntry{ + {Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + {Name: "group2", GID: 1000, Passwd: "y", Users: []string{"user2"}}, + {Name: "group3", GID: 1001, Passwd: "z", Users: []string{"user3"}}, + }, + want: []GroupEntry{ + {Name: "group1", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + {Name: "group3", GID: 1001, Passwd: "z", Users: []string{"user3"}}, + }, + }, + { + name: "Multiple_invalid_and_duplicates", + groups: []GroupEntry{ + {Name: "invalid,group", GID: 1000, Passwd: "x", Users: []string{"user1"}}, + {Name: "group1", GID: 1001, Passwd: "y", Users: []string{"user2"}}, + {Name: "group1", GID: 1001, Passwd: "y", Users: []string{"user2"}}, + {Name: "group2", GID: 1001, Passwd: "z", Users: []string{"user3"}}, + {Name: "group3", GID: 1002, Passwd: "w", Users: []string{"user4"}}, + }, + want: []GroupEntry{ + {Name: "group1", GID: 1001, Passwd: "y", Users: []string{"user2"}}, + {Name: "group1", GID: 1001, Passwd: "y", Users: []string{"user2"}}, + {Name: "group3", GID: 1002, Passwd: "w", Users: []string{"user4"}}, + }, + }, + { + name: "Root_group_GID_0_valid", + groups: []GroupEntry{ + {Name: "root", GID: 0, Passwd: "x"}, + {Name: "group1", GID: 1000, Passwd: "y"}, + }, + want: []GroupEntry{ + {Name: "root", GID: 0, Passwd: "x"}, + {Name: "group1", GID: 1000, Passwd: "y"}, + }, + }, + { + name: "Non_root_group_GID_0_invalid", + groups: []GroupEntry{ + {Name: "group1", GID: 0, Passwd: "x"}, + {Name: "group2", GID: 1001, Passwd: "y"}, + }, + want: []GroupEntry{ + {Name: "group2", GID: 1001, Passwd: "y"}, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + got := GetValidGroupEntries(tc.groups) + require.Equal(t, tc.want, got, "GetValidGroupEntries output mismatch") + }) + } +} From 3f1899cc9edd15ceeac8fd4061ae1d2740a67d4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Sat, 28 Jun 2025 04:22:46 +0200 Subject: [PATCH 0564/1670] users/types: Do not validate group entries values with colons It's the group file separator, so prevent it being used --- internal/users/types/groupentry.go | 17 +++++++++++++++-- internal/users/types/groupentry_test.go | 20 ++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/internal/users/types/groupentry.go b/internal/users/types/groupentry.go index 37bce05762..56ebb2a682 100644 --- a/internal/users/types/groupentry.go +++ b/internal/users/types/groupentry.go @@ -25,12 +25,25 @@ func (g GroupEntry) Validate() error { return fmt.Errorf("group %q cannot contain ',' character", g.Name) } + if strings.ContainsRune(g.Name, ':') { + return fmt.Errorf("group %q cannot contain ':' character", g.Name) + } + if strings.ContainsRune(g.Passwd, ',') { return fmt.Errorf("group %q passwd %q cannot contain ',' character", g.Name, g.Passwd) } - if slices.ContainsFunc(g.Users, func(u string) bool { return strings.ContainsRune(u, ',') }) { - return fmt.Errorf("group %q cannot contain users with ',' character (%v)", g, g.Users) + if strings.ContainsRune(g.Passwd, ':') { + return fmt.Errorf("group %q passwd %q cannot contain ':' character", g.Name, g.Passwd) + } + + for _, u := range g.Users { + if strings.ContainsRune(u, ',') { + return fmt.Errorf("group %q cannot contain users with ',' character (%v)", g, g.Users) + } + if strings.ContainsRune(u, ':') { + return fmt.Errorf("group %q cannot contain users with ':' character (%v)", g, g.Users) + } } return nil diff --git a/internal/users/types/groupentry_test.go b/internal/users/types/groupentry_test.go index e1148d7c60..2942ae467a 100644 --- a/internal/users/types/groupentry_test.go +++ b/internal/users/types/groupentry_test.go @@ -62,6 +62,26 @@ func TestValidateGroupEntry(t *testing.T) { group: GroupEntry{Name: "group1", GID: 1005, Passwd: "x", Users: []string{"user1", "b,ob"}}, wantErr: true, }, + { + name: "Error_on_name_contains_colon", + group: GroupEntry{Name: "ad:mins", GID: 1002, Passwd: "x", Users: []string{"user1"}}, + wantErr: true, + }, + { + name: "Error_on_passwd_contains_colon", + group: GroupEntry{Name: "group1", GID: 1003, Passwd: "x:", Users: []string{"user1"}}, + wantErr: true, + }, + { + name: "Error_on_user_contains_colon", + group: GroupEntry{Name: "group1", GID: 1004, Passwd: "x", Users: []string{"user:1"}}, + wantErr: true, + }, + { + name: "Error_on_multiple_users_one_with_colon", + group: GroupEntry{Name: "group1", GID: 1005, Passwd: "x", Users: []string{"user1", "user:2"}}, + wantErr: true, + }, } for _, tc := range tests { From 964b13913f2a9abdca70ceb0305be84352edaeff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Sat, 28 Jun 2025 04:24:52 +0200 Subject: [PATCH 0565/1670] users/localentries/localgroups: Do not break parsing on extra fields If the group file contains more than 4 fields, it's not a reason to stop the parsing, but we should just consider such group line invalid (and we do it now, as per the previous commit) --- internal/users/localentries/localgroups.go | 4 ++-- internal/users/localentries/localgroups_test.go | 6 ++++++ .../Warn_when_groups_file_has_an_extra_field.group | 7 +++++++ .../Warn_when_groups_file_has_an_extra_field.group.backup | 6 ++++++ .../localentries/testdata/malformed_file_extra_field.group | 6 ++++++ 5 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Warn_when_groups_file_has_an_extra_field.group create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Warn_when_groups_file_has_an_extra_field.group.backup create mode 100644 internal/users/localentries/testdata/malformed_file_extra_field.group diff --git a/internal/users/localentries/localgroups.go b/internal/users/localentries/localgroups.go index d847e76675..db2e77c910 100644 --- a/internal/users/localentries/localgroups.go +++ b/internal/users/localentries/localgroups.go @@ -140,8 +140,8 @@ func parseLocalGroups(groupPath string) (groups []types.GroupEntry, err error) { if t == "" { continue } - elems := strings.Split(t, ":") - if len(elems) != 4 { + elems := strings.SplitN(t, ":", 4) + if len(elems) < 4 { return nil, fmt.Errorf("malformed entry in group file (should have 4 separators, got %d): %q", len(elems), t) } diff --git a/internal/users/localentries/localgroups_test.go b/internal/users/localentries/localgroups_test.go index 2f6b3c3946..a91a681fe7 100644 --- a/internal/users/localentries/localgroups_test.go +++ b/internal/users/localentries/localgroups_test.go @@ -233,6 +233,12 @@ func TestGetAndSaveLocalGroups(t *testing.T) { {Name: "localgroup5", Passwd: "x", GID: 45}, }, }, + "Warn_when_groups_file_has_an_extra_field": { + groupFilePath: "malformed_file_extra_field.group", + addGroups: []types.GroupEntry{ + {Name: "localgroup5", Passwd: "x", GID: 45}, + }, + }, "Warn_when_groups_file_has_no_group_name": { groupFilePath: "malformed_file_no_group_name.group", }, diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Warn_when_groups_file_has_an_extra_field.group b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Warn_when_groups_file_has_an_extra_field.group new file mode 100644 index 0000000000..65fbd260f3 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Warn_when_groups_file_has_an_extra_field.group @@ -0,0 +1,7 @@ +localgroup1:x:41:otheruser +localgroup2:x:42:otheruser2 +localgroup3:x:43:otheruser3 +localgroup4:x:44:otheruser2:extrafield +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 +localgroup5:x:45: diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Warn_when_groups_file_has_an_extra_field.group.backup b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Warn_when_groups_file_has_an_extra_field.group.backup new file mode 100644 index 0000000000..3de5534245 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Warn_when_groups_file_has_an_extra_field.group.backup @@ -0,0 +1,6 @@ +localgroup1:x:41:otheruser +localgroup2:x:42:otheruser2 +localgroup3:x:43:otheruser3 +localgroup4:x:44:otheruser2:extrafield +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/malformed_file_extra_field.group b/internal/users/localentries/testdata/malformed_file_extra_field.group new file mode 100644 index 0000000000..3de5534245 --- /dev/null +++ b/internal/users/localentries/testdata/malformed_file_extra_field.group @@ -0,0 +1,6 @@ +localgroup1:x:41:otheruser +localgroup2:x:42:otheruser2 +localgroup3:x:43:otheruser3 +localgroup4:x:44:otheruser2:extrafield +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 From e05e1dcd3c7e686fb83a2aae8ea7809458bcb978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 1 Jul 2025 21:18:36 +0200 Subject: [PATCH 0566/1670] users/manager: Remove all temporary entries and rely only on database Avoid having temporary entries for groups and users, but just use normal locking to prevent addition of duplicated UIDs/GIDs. We can so drop lots of code that was making things multi-thread, but also more complicated. --- internal/users/db/update.go | 12 +- internal/users/export_test.go | 5 - internal/users/manager.go | 298 +++++-- internal/users/manager_test.go | 12 +- internal/users/tempentries/export_test.go | 30 - internal/users/tempentries/groups.go | 100 --- internal/users/tempentries/groups_test.go | 223 ----- internal/users/tempentries/preauth.go | 156 +++- internal/users/tempentries/preauth_test.go | 80 +- internal/users/tempentries/tempentries.go | 427 --------- .../users/tempentries/tempentries_test.go | 842 ------------------ .../Successfully_get_a_group_by_ID | 4 - .../Successfully_get_a_group_by_name | 4 - ... Panics_registering_a_pre-auth_user_again} | 2 +- ..._a_pre-auth_user_again_with_different_uid} | 2 +- ...first_generated_UID_is_already_registered} | 2 +- .../Successfully_register_a_pre-auth_user | 2 +- ...er_a_pre-auth_user_again_TestPreAuthUser_1 | 6 - ..._the_first_generated_UID_is_already_in_use | 6 - ..._first_generated_UID_is_already_registered | 6 - ...ted_UID_is_already_registered_other-test_1 | 6 - .../Successfully_get_a_user_by_ID_and_name | 2 +- ...hen_registering_a_group_with_the_same_name | 4 - ..._the_first_generated_GID_is_already_in_use | 4 - ..._first_generated_GID_is_already_registered | 4 - ...ated_GID_is_already_registered_other-group | 4 - .../Successfully_register_a_new_group | 4 - ..._with_the_same_name_returning_the_same_GID | 4 - ...urning_the_same_GID_authd-temp-groups-test | 4 - ...a_user_if_the_pre-auth_user_already_exists | 6 - .../Error_if_there_are_no_GID_to_generate | 6 - ..._are_no_GID_to_generate_for_pre-check_user | 6 - ...ully_register_a_new_group_for_generic_user | 6 - ...thd-temp-users-test+authd-temp-groups-test | 4 - ...lly_register_a_new_group_for_pre-auth_user | 6 - ...ster_a_new_group_for_various_generic_users | 6 - ...hd-temp-users-test1+authd-temp-groups-test | 4 - ...hd-temp-users-test2+authd-temp-groups-test | 4 - ...ous_generic_users_authd-temp-users-test2_1 | 6 - ...hd-temp-users-test3+authd-temp-groups-test | 4 - ...ous_generic_users_authd-temp-users-test3_2 | 6 - ...ter_a_new_group_for_various_pre-auth_users | 6 - ...us_pre-auth_users_authd-temp-users-test2_1 | 6 - ...us_pre-auth_users_authd-temp-users-test3_2 | 6 - ..._the_first_generated_GID_is_already_in_use | 6 - ...thd-temp-users-test+authd-temp-groups-test | 4 - ..._GID_is_already_in_use_by_a_pre-check_user | 6 - ...r_another-user-name+authd-temp-groups-test | 4 - ...se_by_a_pre-check_user_another-user-name_1 | 6 - ...e_first_generated_GID_matches_the_user_UID | 6 - ...thd-temp-users-test+authd-temp-groups-test | 4 - ..._the_first_generated_UID_is_already_in_use | 6 - ...ser_if_multiple_concurrent_requests_happen | 6 - ...oncurrent_requests_happen_for_the_same_UID | 6 - ...for_the_same_UID_racing-pre-checked-user_1 | 6 - ..._requests_happen_racing-pre-checked-user_1 | 6 - ..._first_generated_UID_is_already_registered | 6 - ...lready_registered_other-pre-checked-user_1 | 6 - ...a_pre-checked_user_twice_with_the_same_UID | 6 - ...twice_with_the_same_UID_pre-checked-user_1 | 6 - ...register_a_various_groups_for_generic_user | 6 - ...generic_user_authd-temp-users-test+group-a | 4 - ...s_for_generic_user_authd-temp-users-test_1 | 4 - ...s_for_generic_user_authd-temp-users-test_2 | 4 - ...egister_a_various_groups_for_pre-auth_user | 6 - ...ully_register_an_user_after_two_pre_checks | 6 - ...ks_pre-checked-user+authd-temp-groups-test | 4 - ...er_after_two_pre_checks_pre-checked-user_1 | 6 - ...er_after_two_pre_checks_pre-checked-user_2 | 6 - ...tiple_concurrent_pre-check_requests_happen | 6 - ...pre-check_requests_happen_for_the_same_UID | 6 - ...ng-pre-checked-user+authd-temp-groups-test | 4 - ...for_the_same_UID_racing-pre-checked-user_1 | 6 - ...ng-pre-checked-user+authd-temp-groups-test | 4 - ..._requests_happen_racing-pre-checked-user_1 | 6 - ...ser_if_multiple_concurrent_requests_happen | 6 - ...oncurrent_requests_happen_for_the_same_UID | 6 - ...ame_UID_racing-user+authd-temp-groups-test | 4 - ...ests_happen_for_the_same_UID_racing-user_1 | 6 - ..._happen_racing-user+authd-temp-groups-test | 4 - ...e_concurrent_requests_happen_racing-user_1 | 6 - ..._first_generated_UID_is_already_registered | 6 - ...gistered_other-user+authd-temp-groups-test | 4 - ...ted_UID_is_already_registered_other-user_2 | 6 - ...ed_pre-checked-user+authd-temp-groups-test | 4 - ...D_is_already_registered_pre-checked-user_1 | 6 - ...er_with_the_same_name_after_two_pre_checks | 6 - ...ks_pre-checked-user+authd-temp-groups-test | 4 - ...me_after_two_pre_checks_pre-checked-user_1 | 6 - ...me_after_two_pre_checks_pre-checked-user_2 | 6 - ...me_after_two_pre_checks_pre-checked-user_3 | 6 - internal/users/tempentries/users.go | 97 -- internal/users/testutils/manager.go | 3 + 93 files changed, 401 insertions(+), 2290 deletions(-) delete mode 100644 internal/users/tempentries/export_test.go delete mode 100644 internal/users/tempentries/groups.go delete mode 100644 internal/users/tempentries/groups_test.go delete mode 100644 internal/users/tempentries/tempentries.go delete mode 100644 internal/users/tempentries/tempentries_test.go delete mode 100644 internal/users/tempentries/testdata/golden/TestGroupByIDAndName/Successfully_get_a_group_by_ID delete mode 100644 internal/users/tempentries/testdata/golden/TestGroupByIDAndName/Successfully_get_a_group_by_name rename internal/users/tempentries/testdata/golden/TestPreAuthUser/{No_error_when_registering_a_pre-auth_user_with_the_same_name => Panics_registering_a_pre-auth_user_again} (69%) rename internal/users/tempentries/testdata/golden/TestPreAuthUser/{No_error_when_registering_a_pre-auth_user_with_the_same_name_TestPreAuthUser_1 => Panics_registering_a_pre-auth_user_again_with_different_uid} (69%) rename internal/users/tempentries/testdata/golden/TestPreAuthUser/{Successfully_register_a_pre-auth_user_again => Panics_registering_a_pre-auth_user_if_the_first_generated_UID_is_already_registered} (69%) delete mode 100644 internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again_TestPreAuthUser_1 delete mode 100644 internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_in_use delete mode 100644 internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_registered delete mode 100644 internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_registered_other-test_1 delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterGroup/Error_when_registering_a_group_with_the_same_name delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_register_a_group_if_the_first_generated_GID_is_already_in_use delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_register_a_group_if_the_first_generated_GID_is_already_registered delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_register_a_group_if_the_first_generated_GID_is_already_registered_other-group delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_register_a_new_group delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_registering_a_group_with_the_same_name_returning_the_same_GID delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_registering_a_group_with_the_same_name_returning_the_same_GID_authd-temp-groups-test delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUser/Successfully_register_a_user_if_the_pre-auth_user_already_exists delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Error_if_there_are_no_GID_to_generate delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Error_if_there_are_no_GID_to_generate_for_pre-check_user delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_generic_user delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_generic_user_authd-temp-users-test+authd-temp-groups-test delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_pre-auth_user delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test1+authd-temp-groups-test delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test2+authd-temp-groups-test delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test2_1 delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test3+authd-temp-groups-test delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test3_2 delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_pre-auth_users delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_pre-auth_users_authd-temp-users-test2_1 delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_pre-auth_users_authd-temp-users-test3_2 delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_authd-temp-users-test+authd-temp-groups-test delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user_another-user-name+authd-temp-groups-test delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user_another-user-name_1 delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_matches_the_user_UID delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_matches_the_user_UID_authd-temp-users-test+authd-temp-groups-test delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_user_if_the_first_generated_UID_is_already_in_use delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_for_the_same_UID delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_for_the_same_UID_racing-pre-checked-user_1 delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_racing-pre-checked-user_1 delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_if_the_first_generated_UID_is_already_registered delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_if_the_first_generated_UID_is_already_registered_other-pre-checked-user_1 delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_twice_with_the_same_UID delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_twice_with_the_same_UID_pre-checked-user_1 delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user_authd-temp-users-test+group-a delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user_authd-temp-users-test_1 delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user_authd-temp-users-test_2 delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_pre-auth_user delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks_pre-checked-user+authd-temp-groups-test delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks_pre-checked-user_1 delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks_pre-checked-user_2 delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID_racing-pre-checked-user+authd-temp-groups-test delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID_racing-pre-checked-user_1 delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_racing-pre-checked-user+authd-temp-groups-test delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_racing-pre-checked-user_1 delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID_racing-user+authd-temp-groups-test delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID_racing-user_1 delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_racing-user+authd-temp-groups-test delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_racing-user_1 delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_other-user+authd-temp-groups-test delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_other-user_2 delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_pre-checked-user+authd-temp-groups-test delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_pre-checked-user_1 delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user+authd-temp-groups-test delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user_1 delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user_2 delete mode 100644 internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user_3 delete mode 100644 internal/users/tempentries/users.go diff --git a/internal/users/db/update.go b/internal/users/db/update.go index a071585aeb..8b036596f5 100644 --- a/internal/users/db/update.go +++ b/internal/users/db/update.go @@ -10,12 +10,6 @@ import ( "github.com/ubuntu/authd/log" ) -// ErrUIDAlreadyInUse is the error we return on registering a duplicate UID. -var ErrUIDAlreadyInUse = errors.New("UID already in use by a different user") - -// ErrGIDAlreadyInUse is the error we return on registering a duplicate GID. -var ErrGIDAlreadyInUse = errors.New("GID already in use by a different group") - // UpdateUserEntry inserts or updates user and group records from the user information. func (m *Manager) UpdateUserEntry(user UserRow, authdGroups []GroupRow, localGroups []string) (err error) { // authd uses lowercase usernames @@ -68,7 +62,8 @@ func handleUserUpdate(db queryable, u UserRow) error { if existingUser.Name != "" && existingUser.Name != u.Name { log.Errorf(context.TODO(), "UID %d for user %q already in use by user %q", u.UID, u.Name, existingUser.Name) - return fmt.Errorf("%w: %q", ErrUIDAlreadyInUse, u.Name) + return fmt.Errorf("UID for user %q already in use by a different user %q", + u.Name, existingUser.Name) } // Ensure that we use the same homedir as the one we have in the database. @@ -100,7 +95,8 @@ func handleGroupsUpdate(db queryable, groups []GroupRow) error { // UGID, which was the case before https://github.com/ubuntu/authd/pull/647. if groupExists && existingGroup.UGID != "" && existingGroup.UGID != group.UGID { log.Errorf(context.TODO(), "GID %d for group with UGID %q already in use by a group with UGID %q", group.GID, group.UGID, existingGroup.UGID) - return fmt.Errorf("%w: %q", ErrGIDAlreadyInUse, group.Name) + return fmt.Errorf("GID for group %q already in use by a different group %q", + group.Name, existingGroup.Name) } log.Debugf(context.Background(), "Updating entry of group %q (%+v)", group.Name, group) diff --git a/internal/users/export_test.go b/internal/users/export_test.go index b139d0c5cc..c1d5632ac6 100644 --- a/internal/users/export_test.go +++ b/internal/users/export_test.go @@ -2,13 +2,8 @@ package users import ( "github.com/ubuntu/authd/internal/users/db" - "github.com/ubuntu/authd/internal/users/tempentries" ) -func (m *Manager) TemporaryRecords() *tempentries.TemporaryRecords { - return m.temporaryRecords -} - func (m *Manager) DB() *db.Manager { return m.db } diff --git a/internal/users/manager.go b/internal/users/manager.go index 12499ef1e6..ae687a4827 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -8,6 +8,7 @@ import ( "os" "slices" "strings" + "sync" "syscall" "github.com/ubuntu/authd/internal/users/db" @@ -37,21 +38,31 @@ var DefaultConfig = Config{ // Manager is the manager for any user related operation. type Manager struct { - db *db.Manager - config Config - temporaryRecords *tempentries.TemporaryRecords + // userManagementMu must be used to protect all the operations in which we + // do users registration to the DB, to ensure that concurrent goroutines may + // not falsify the checks we are performing (such as the users existence). + userManagementMu sync.Mutex + + db *db.Manager + config Config + preAuthRecords *tempentries.PreAuthUserRecords + idGenerator tempentries.IDGenerator + + userEntries []types.UserEntry + groupEntries []types.GroupEntry } type options struct { idGenerator tempentries.IDGenerator } +// Avoid to loop forever if we can't find an UID for the user, it's just better +// to fail after a limit is reached than hang or crash. +const maxIDGenerateIterations = 256 + // Option is a function that allows changing some of the default behaviors of the manager. type Option func(*options) -// errUpdateRetry is an error when the user update failed in a non fatal way. -var errUpdateRetry = errors.New("update failed. Try again") - // WithIDGenerator makes the manager use a specific ID generator. // This option is only useful in tests. func WithIDGenerator(g tempentries.IDGenerator) Option { @@ -93,8 +104,9 @@ func NewManager(config Config, dbDir string, args ...Option) (m *Manager, err er } m = &Manager{ - config: config, - temporaryRecords: tempentries.NewTemporaryRecords(opts.idGenerator), + config: config, + preAuthRecords: tempentries.NewPreAuthUserRecords(), + idGenerator: opts.idGenerator, } m.db, err = db.New(dbDir) @@ -112,33 +124,11 @@ func (m *Manager) Stop() error { // UpdateUser updates the user information in the db. func (m *Manager) UpdateUser(u types.UserInfo) (err error) { - // Maybe we can even avoid to have a max... But well we'd likely fail only - // if the UID/GID set is very limited and we've lots of concurrent requests. - // In the worse case we'd fail anyways because temporary entries max - // max generation limit is reached anyways. - const maxRetries = 100 - - for i := range maxRetries { - user := u - user.Groups = slices.Clone(u.Groups) - - err = m.updateUser(user) - if errors.Is(err, errUpdateRetry) { - log.Infof(context.Background(), - "User %q update [try: %d] failed for a recoverable reason: %v", - u.Name, i, err) - continue - } - - break - } - - return err -} - -func (m *Manager) updateUser(u types.UserInfo) (err error) { defer decorate.OnError(&err, "failed to update user %q", u.Name) + m.userManagementMu.Lock() + defer m.userManagementMu.Unlock() + log.Debugf(context.TODO(), "Updating user %q", u.Name) // authd uses lowercase usernames @@ -149,28 +139,36 @@ func (m *Manager) updateUser(u types.UserInfo) (err error) { } var uid uint32 - var isNewUser bool - // Check if the user already exists in the database oldUser, err := m.db.UserByName(u.Name) if err != nil && !errors.Is(err, db.NoDataFoundError{}) { return fmt.Errorf("could not get user %q: %w", u.Name, err) } if errors.Is(err, db.NoDataFoundError{}) { - isNewUser = true - localEntries, unlockEntries, err := localentries.NewUserDBLocked() - if err != nil { + preauthUID, cleanup, err := m.preAuthRecords.MaybeCompletePreauthUser(u.Name) + if err != nil && !errors.Is(err, tempentries.NoDataFoundError{}) { return err } - defer func() { err = errors.Join(err, unlockEntries()) }() - records := m.temporaryRecords.LockForChanges(localEntries) + if preauthUID != 0 { + uid = preauthUID + defer cleanup() + } else { + unlockEntries, err := m.cacheLockedEntries() + if err != nil { + return err + } + defer func() { err = errors.Join(err, unlockEntries()) }() - var cleanup func() - uid, cleanup, err = records.RegisterUser(u.Name) - if err != nil { - return fmt.Errorf("could not register user %q: %w", u.Name, err) + if !m.isUniqueSystemUserName(u.Name) { + log.Warningf(context.Background(), "Another user exists with name %q", u.Name) + return fmt.Errorf("another system user exists with %q name", u.Name) + } + + if uid, err = m.generateUniqueUID(); err != nil { + return err + } + log.Debugf(context.Background(), "Using new UID %d for user %q", uid, u.Name) } - defer cleanup() } else { // The user already exists in the database, use the existing UID to avoid permission issues. uid = oldUser.UID @@ -224,21 +222,41 @@ func (m *Manager) updateUser(u types.UserInfo) (err error) { } if len(newGroups) > 0 { - localEntries, unlockEntries, err := localentries.NewUserDBLocked() + unlockEntries, err := m.cacheLockedEntries() if err != nil { return err } defer func() { err = errors.Join(err, unlockEntries()) }() - records := m.temporaryRecords.LockForChanges(localEntries) - for _, g := range newGroups { - gid, cleanup, err := records.RegisterGroupForUser(uid, g.Name) + for i, j := 0, 0; i < len(newGroups); j++ { + g := &newGroups[i] + if j >= maxIDGenerateIterations { + return fmt.Errorf("failed to find a valid GID for %q after %d attempts", + g.Name, maxIDGenerateIterations) + } + + gid, err := m.generateUniqueGID() if err != nil { - return fmt.Errorf("could not generate GID for group %q: %v", g.Name, err) + return err + } + if gid == uid { + continue + } + if slices.ContainsFunc(newGroups, func(og types.GroupInfo) bool { + return og.GID != nil && *og.GID == gid + }) { + continue } - defer cleanup() - groupRows = append(groupRows, db.NewGroupRow(g.Name, gid, g.UGID)) + if !m.isUniqueSystemGroupName(g.Name) { + log.Warningf(context.Background(), "Group %q already exists", g.Name) + return fmt.Errorf("another system group exists with %q name", g.Name) + } + + g.GID = &gid + groupRows = append(groupRows, db.NewGroupRow(g.Name, *g.GID, g.UGID)) + log.Debugf(context.Background(), "Using new GID %d for group %q", gid, u.Name) + i++ } } @@ -250,19 +268,7 @@ func (m *Manager) updateUser(u types.UserInfo) (err error) { // Update user information in the db. userPrivateGroup := groupRows[0] userRow := db.NewUserRow(u.Name, uid, userPrivateGroup.GID, u.Gecos, u.Dir, u.Shell) - err = m.db.UpdateUserEntry(userRow, groupRows, localGroups) - if isNewUser && errors.Is(err, db.ErrUIDAlreadyInUse) { - return fmt.Errorf("%w: %w", errUpdateRetry, err) - } - if errors.Is(err, db.ErrGIDAlreadyInUse) { - return fmt.Errorf("%w: %w", errUpdateRetry, err) - } - if errors.Is(err, db.NoDataFoundError{}) { - // The user or group has not been found while updating it, likely due - // to previous concurrent requests not having finished, so let's retry. - return fmt.Errorf("%w: %w", errUpdateRetry, err) - } - if err != nil { + if err = m.db.UpdateUserEntry(userRow, groupRows, localGroups); err != nil { return err } @@ -385,10 +391,6 @@ func (m *Manager) UpdateBrokerForUser(username, brokerID string) error { // UserByName returns the user information for the given user name. func (m *Manager) UserByName(username string) (types.UserEntry, error) { usr, err := m.db.UserByName(username) - if errors.Is(err, db.NoDataFoundError{}) { - // Check if the user is a temporary user. - return m.temporaryRecords.UserByName(username) - } if err != nil { return types.UserEntry{}, err } @@ -400,7 +402,7 @@ func (m *Manager) UserByID(uid uint32) (types.UserEntry, error) { usr, err := m.db.UserByID(uid) if errors.Is(err, db.NoDataFoundError{}) { // Check if the user is a temporary user. - return m.temporaryRecords.UserByID(uid) + return m.preAuthRecords.UserByID(uid) } if err != nil { return types.UserEntry{}, err @@ -480,21 +482,173 @@ func (m *Manager) AllShadows() ([]types.ShadowEntry, error) { return shadowEntries, err } +func (m *Manager) generateUniqueID(generator func() (uint32, error)) (uint32, error) { + for range maxIDGenerateIterations { + uid, err := generator() + if err != nil { + return 0, err + } + + var unique bool + unique, err = m.isUniqueID(uid) + if err != nil { + return 0, err + } + if !unique { + log.Debugf(context.Background(), "UID %d is not unique", uid) + continue + } + + return uid, nil + } + + return 0, fmt.Errorf("Cannot find an unique ID") +} + +func (m *Manager) generateUniqueUID() (uint32, error) { + return m.generateUniqueID(m.idGenerator.GenerateUID) +} + +func (m *Manager) generateUniqueGID() (uint32, error) { + return m.generateUniqueID(m.idGenerator.GenerateGID) +} + +func (m *Manager) isUniqueID(id uint32) (bool, error) { + oldUser, err := m.UserByID(id) + if err == nil { + log.Debugf(context.TODO(), "Found duplicate user %v", oldUser) + return false, nil + } + if !errors.Is(err, db.NoDataFoundError{}) { + return false, err + } + + oldGroup, err := m.GroupByID(id) + if err == nil { + log.Debugf(context.TODO(), "Found duplicate group %v", oldGroup) + return false, nil + } + if !errors.Is(err, db.NoDataFoundError{}) { + return false, err + } + + return m.isUniqueSystemID(id) +} + +func (m *Manager) isUniqueSystemID(id uint32) (unique bool, err error) { + if m.userEntries == nil { + panic("User entries have note ben set") + } + if m.groupEntries == nil { + panic("Group entries have note ben set") + } + + if idx := slices.IndexFunc(m.userEntries, func(p types.UserEntry) (found bool) { + return p.UID == id + }); idx != -1 { + log.Debugf(context.Background(), "ID %d already in use by user %q", + id, m.userEntries[idx].Name) + return false, nil + } + + if idx := slices.IndexFunc(m.groupEntries, func(g types.GroupEntry) (found bool) { + return g.GID == id + }); idx != -1 { + log.Debugf(context.Background(), "ID %d already in use by user %q", + id, m.groupEntries[idx].Name) + return false, nil + } + + return true, nil +} + +func (m *Manager) cacheLockedEntries() (unlockEntries func() error, err error) { + if m.userEntries != nil && m.groupEntries != nil { + return func() error { return nil }, nil + } + + localEntries, unlock, err := localentries.NewUserDBLocked() + if err != nil { + return nil, err + } + + m.userEntries, err = localEntries.GetUserEntries() + if err != nil { + return nil, err + } + + m.groupEntries, err = localEntries.GetGroupEntries() + if err != nil { + return nil, err + } + + return func() error { + m.userEntries = nil + m.groupEntries = nil + return unlock() + }, nil +} + +func (m *Manager) isUniqueSystemUserName(name string) (unique bool) { + if m.userEntries == nil { + panic("User entries are not set!") + } + return !slices.ContainsFunc(m.userEntries, func(g types.UserEntry) bool { + return g.Name == name + }) +} + +func (m *Manager) isUniqueSystemGroupName(name string) (unique bool) { + if m.groupEntries == nil { + panic("Group entries are not set!") + } + return !slices.ContainsFunc(m.groupEntries, func(g types.GroupEntry) bool { + return g.Name == name + }) +} + // RegisterUserPreAuth registers a temporary user with a unique UID in our NSS handler (in memory, not in the database). // // The temporary user record is removed when UpdateUser is called with the same username. func (m *Manager) RegisterUserPreAuth(name string) (uid uint32, err error) { defer decorate.OnError(&err, "failed to register pre-auth user %q", name) + m.userManagementMu.Lock() + defer m.userManagementMu.Unlock() + if userRow, err := m.db.UserByName(name); err == nil { + log.Debugf(context.Background(), "user %q already exists on the system", name) return userRow.UID, nil } - localEntries, unlockEntries, err := localentries.NewUserDBLocked() + user, err := m.preAuthRecords.UserByLogin(name) + if err == nil { + log.Debugf(context.Background(), "user %q already pre-authenticated", name) + return user.UID, nil + } + if err != nil && !errors.Is(err, tempentries.NoDataFoundError{}) { + return 0, err + } + + unlockEntries, err := m.cacheLockedEntries() if err != nil { return 0, err } defer func() { err = errors.Join(err, unlockEntries()) }() - return m.temporaryRecords.LockForChanges(localEntries).RegisterPreAuthUser(name) + if !m.isUniqueSystemUserName(name) { + return 0, fmt.Errorf("another system user exists with %q name", name) + } + + uid, err = m.generateUniqueUID() + if err != nil { + return 0, err + } + + if err := m.preAuthRecords.RegisterPreAuthUser(name, uid); err != nil { + return 0, err + } + + log.Debugf(context.Background(), "Using new UID %d for temporary user %q", uid, name) + return uid, nil } diff --git a/internal/users/manager_test.go b/internal/users/manager_test.go index e27ccdc68a..e857a98c08 100644 --- a/internal/users/manager_test.go +++ b/internal/users/manager_test.go @@ -365,7 +365,7 @@ func TestConcurrentUserUpdate(t *testing.T) { UIDMax: uint32(len(systemPasswd)) + nIterations*preAuthIterations, GIDMin: 0, //nolint: gosec // we're in tests, overflow is very unlikely to happen. - GIDMax: uint32(len(systemGroups)) + nIterations*perUserGroups*3, + GIDMax: uint32(len(systemGroups)) + nIterations*perUserGroups, } m := newManagerForTests(t, dbDir, users.WithIDGenerator(idGenerator)) @@ -694,15 +694,7 @@ func TestUserByIDAndName(t *testing.T) { m := newManagerForTests(t, dbDir) if tc.isTempUser { - entries, entriesUnlock, err := localentries.NewUserDBLocked() - require.NoError(t, err, "Setup: failed to lock the locale entries") - t.Cleanup(func() { - err = entriesUnlock() - require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") - }) - records := m.TemporaryRecords().LockForChanges(entries) - - tc.uid, err = records.RegisterPreAuthUser("tempuser1") + tc.uid, err = m.RegisterUserPreAuth("tempuser1") require.NoError(t, err, "RegisterUser should not return an error, but did") } diff --git a/internal/users/tempentries/export_test.go b/internal/users/tempentries/export_test.go deleted file mode 100644 index d163dfd127..0000000000 --- a/internal/users/tempentries/export_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package tempentries - -import ( - "github.com/ubuntu/authd/internal/users/db" - "github.com/ubuntu/authd/internal/users/types" -) - -// GroupByID returns the group information for the given group ID. -func (r *temporaryGroupRecords) GroupByID(gid uint32) (types.GroupEntry, error) { - group, ok := r.groups[gid] - if !ok { - return types.GroupEntry{}, db.NewGIDNotFoundError(gid) - } - - return groupEntry(*group), nil -} - -func groupEntry(group groupRecord) types.GroupEntry { - return types.GroupEntry{Name: group.name, GID: group.gid} -} - -// GroupByName returns the group information for the given group name. -func (r *temporaryGroupRecords) GroupByName(name string) (types.GroupEntry, error) { - gid, ok := r.gidByName[name] - if !ok { - return types.GroupEntry{}, db.NewGroupNotFoundError(name) - } - - return r.GroupByID(gid) -} diff --git a/internal/users/tempentries/groups.go b/internal/users/tempentries/groups.go deleted file mode 100644 index e7f31a6f4c..0000000000 --- a/internal/users/tempentries/groups.go +++ /dev/null @@ -1,100 +0,0 @@ -package tempentries - -import ( - "context" - "fmt" - "sync" - - "github.com/ubuntu/authd/log" -) - -type groupRecord struct { - refCount uint64 - name string - gid uint32 -} - -type temporaryGroupRecords struct { - idGenerator IDGenerator - mu sync.Mutex - groups map[uint32]*groupRecord - gidByName map[string]uint32 -} - -func newTemporaryGroupRecords(idGenerator IDGenerator) *temporaryGroupRecords { - return &temporaryGroupRecords{ - idGenerator: idGenerator, - groups: make(map[uint32]*groupRecord), - gidByName: make(map[string]uint32), - } -} - -// generateGroupID generates a potential unique GID. -// -// Returns the generated GID . -func (r *temporaryGroupRecords) generateGroupID() (gid uint32, err error) { - // Generate a GID - return r.idGenerator.GenerateGID() -} - -// getTemporaryGroup gets the GID of a temporary group with the given name, -// if any, increasing its reference count. -// -// This must be called with the group mutex locked. -func (r *temporaryGroupRecords) getTemporaryGroup(name string) (gid uint32) { - gid, ok := r.gidByName[name] - if !ok { - return 0 - } - - group := r.groups[gid] - group.refCount++ - - log.Debugf(context.Background(), "Reusing GID %d for group %q", gid, group.name) - - return group.gid -} - -// Adds a temporary group. -// -// This must be called with the group mutex locked. -func (r *temporaryGroupRecords) addTemporaryGroup(gid uint32, name string) { - // log.Debugf(nil, "Adding group %d (%q)", gid, name) - if oldGroup, ok := r.groups[gid]; ok { - panic(fmt.Sprintf("group ID %d is already registered for %q, cannot register %q", - gid, oldGroup.name, name)) - } - if oldGID, ok := r.gidByName[name]; ok { - panic(fmt.Sprintf("group %q has already GID %d, cannot use %d", name, oldGID, gid)) - } - - r.groups[gid] = &groupRecord{name: name, gid: gid, refCount: 1} - r.gidByName[name] = gid - - log.Debugf(context.Background(), "Registered group %q with GID %d", name, gid) -} - -// releaseTemporaryGroup releases a temporary group reference, it returns -// whether the last reference has been dropped. -// -// This must be called with the group mutex locked. -func (r *temporaryGroupRecords) releaseTemporaryGroup(gid uint32) bool { - group, ok := r.groups[gid] - if !ok { - log.Warningf(context.Background(), "Can't delete temporary group with GID %d, it does not exist", gid) - return false - } - - if group.refCount > 1 { - log.Debugf(context.Background(), "Removed reference on temporary record for group %q with GID %d", - group.name, gid) - group.refCount-- - return false - } - - delete(r.groups, gid) - delete(r.gidByName, group.name) - - log.Debugf(context.Background(), "Removed temporary record for group %q with GID %d", group.name, gid) - return true -} diff --git a/internal/users/tempentries/groups_test.go b/internal/users/tempentries/groups_test.go deleted file mode 100644 index f72a06df47..0000000000 --- a/internal/users/tempentries/groups_test.go +++ /dev/null @@ -1,223 +0,0 @@ -package tempentries - -import ( - "slices" - "testing" - - "github.com/stretchr/testify/require" - "github.com/ubuntu/authd/internal/testutils/golden" - "github.com/ubuntu/authd/internal/users/idgenerator" - "github.com/ubuntu/authd/internal/users/types" -) - -func TestRegisterGroup(t *testing.T) { - t.Parallel() - - ownerUID := uint32(54321) - defaultGroupName := "authd-temp-groups-test" - gidToGenerate := uint32(12345) - - tests := map[string]struct { - groups []string - gidsToGenerate []uint32 - - wantErr []bool - wantGIDs []uint32 - }{ - "Successfully_register_a_new_group": {}, - "Successfully_register_a_group_if_the_first_generated_GID_is_already_in_use": { - gidsToGenerate: []uint32{0, gidToGenerate}, // GID 0 (root) always exists - wantGIDs: []uint32{gidToGenerate}, - }, - "Successfully_register_a_group_if_the_first_generated_GID_is_already_registered": { - groups: []string{defaultGroupName, "other-group"}, - gidsToGenerate: []uint32{gidToGenerate, gidToGenerate, gidToGenerate + 1}, - wantGIDs: []uint32{gidToGenerate, gidToGenerate + 1}, - }, - "Successfully_registering_a_group_with_the_same_name_returning_the_same_GID": { - groups: []string{defaultGroupName, defaultGroupName}, - wantGIDs: []uint32{gidToGenerate, gidToGenerate}, - }, - - "Error_when_name_is_already_in_use": {groups: []string{"root"}, wantErr: []bool{true}}, - "Error_when_a_valid_GID_cannot_be_found": { - gidsToGenerate: make([]uint32, maxIDGenerateIterations*10), - wantErr: []bool{true}, - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - t.Parallel() - - if len(tc.groups) == 0 { - tc.groups = append(tc.groups, defaultGroupName) - } - - if tc.gidsToGenerate == nil { - gid := gidToGenerate - for range tc.groups { - tc.gidsToGenerate = append(tc.gidsToGenerate, gid) - gid++ - } - } - if tc.wantGIDs == nil { - tc.wantGIDs = tc.gidsToGenerate - } - if tc.wantErr == nil { - tc.wantErr = make([]bool, len(tc.groups)) - } - - wantRegistered := 0 - var registeredGIDs []uint32 - cleanupFunctions := map[uint32]func(){} - - t.Log("GIDs to generate", tc.gidsToGenerate) - idGeneratorMock := &idgenerator.IDGeneratorMock{GIDsToGenerate: tc.gidsToGenerate} - records := NewTemporaryRecords(idGeneratorMock) - - for idx, groupName := range tc.groups { - t.Logf("Registering group %q", groupName) - gid, cleanup, err := records.registerGroupForUser(ownerUID, groupName) - if tc.wantErr[idx] { - require.Error(t, err, "RegisterGroup should return an error, but did not") - continue - } - - require.NoError(t, err, "RegisterGroup should not return an error, but did") - t.Cleanup(cleanup) - - isDuplicated := slices.Contains(tc.groups[0:idx], groupName) - if !isDuplicated { - wantRegistered++ - } - - if isDuplicated { - require.Contains(t, registeredGIDs, gid, "GID %d has been already registered!", gid) - } else { - require.NotContains(t, registeredGIDs, gid, "GID %d has not been already registered!", gid) - } - - wantGID := tc.wantGIDs[wantRegistered-1] - registeredGIDs = append(registeredGIDs, gid) - cleanupFunctions[gid] = cleanup - - require.NoError(t, err, "RegisterGroup should not return an error, but did") - require.Equal(t, wantGID, gid, "GID should be the one generated by the IDGenerator") - require.Equal(t, wantRegistered, len(records.groups), - "Number of groups registered, users should be %d", wantRegistered) - - // Check that the temporary group was created - group, err := records.GroupByID(gid) - require.NoError(t, err, "GroupByID should not return an error, but did") - - var goldenOptions []golden.Option - if idx > 0 { - goldenOptions = append(goldenOptions, golden.WithSuffix("_"+groupName)) - } - checkGroup(t, group, goldenOptions...) - } - - if wantRegistered == 0 { - return - } - - for idx, groupName := range tc.groups { - isDuplicated := slices.Contains(tc.groups[0:idx], groupName) - if !isDuplicated { - wantRegistered-- - } - - removeGID := registeredGIDs[len(registeredGIDs)-wantRegistered-1] - t.Logf("Removing group %q for GID %v", groupName, removeGID) - cleanupFunctions[removeGID]() - - // Check that the temporary group was deleted - _, err := records.GroupByID(removeGID) - - if !slices.Contains(tc.groups[idx+1:], groupName) { - require.Error(t, err, "GroupByID should return an error, but did not") - continue - } - - require.NoError(t, err, "GroupByID should return an error, but did not") - } - }) - } -} - -func TestGroupByIDAndName(t *testing.T) { - t.Parallel() - - ownerUID := uint32(54321) - groupName := "authd-temp-groups-test" - gidToGenerate := uint32(12345) - - tests := map[string]struct { - registerGroup bool - groupAlreadyRemoved bool - byName bool - - wantErr bool - }{ - "Successfully_get_a_group_by_ID": {registerGroup: true}, - "Successfully_get_a_group_by_name": {registerGroup: true, byName: true}, - - "Error_when_group_is_not_registered_-_GroupByID": {wantErr: true}, - "Error_when_group_is_not_registered_-_GroupByName": {byName: true, wantErr: true}, - "Error_when_group_is_already_removed_-_GroupByID": { - registerGroup: true, - groupAlreadyRemoved: true, - wantErr: true, - }, - "Error_when_group_is_already_removed_-_GroupByName": { - registerGroup: true, - groupAlreadyRemoved: true, - byName: true, - wantErr: true, - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - t.Parallel() - - idGeneratorMock := &idgenerator.IDGeneratorMock{GIDsToGenerate: []uint32{gidToGenerate}} - records := NewTemporaryRecords(idGeneratorMock) - - if tc.registerGroup { - gid, cleanup, err := records.registerGroupForUser(ownerUID, groupName) - require.NoError(t, err, "RegisterGroup should not return an error, but did") - require.Equal(t, gidToGenerate, gid, "GID should be the one generated by the IDGenerator") - - if tc.groupAlreadyRemoved { - cleanup() - } else { - defer cleanup() - } - } - - var group types.GroupEntry - var err error - if tc.byName { - group, err = records.GroupByID(gidToGenerate) - } else { - group, err = records.GroupByName(groupName) - } - - if tc.wantErr { - require.Error(t, err, "GroupByID should return an error, but did not") - return - } - require.NoError(t, err, "GroupByID should not return an error, but did") - checkGroup(t, group) - }) - } -} - -func checkGroup(t *testing.T, group types.GroupEntry, options ...golden.Option) { - t.Helper() - - require.Empty(t, group.Passwd, "Passwd should be empty") - - golden.CheckOrUpdateYAML(t, group, options...) -} diff --git a/internal/users/tempentries/preauth.go b/internal/users/tempentries/preauth.go index d2d3e8e4cd..17f7b79199 100644 --- a/internal/users/tempentries/preauth.go +++ b/internal/users/tempentries/preauth.go @@ -1,3 +1,4 @@ +// Package tempentries provides a temporary pre-authentication records. package tempentries import ( @@ -25,6 +26,15 @@ const ( UserPrefix = "authd-pre-auth-user" ) +// NoDataFoundError is the error returned when no entry is found in the database. +type NoDataFoundError = db.NoDataFoundError + +// IDGenerator is the interface that must be implemented by the ID generator. +type IDGenerator interface { + GenerateUID() (uint32, error) + GenerateGID() (uint32, error) +} + type preAuthUser struct { // name is the generated random name of the pre-auth user (which is returned by UserByID). name string @@ -33,54 +43,119 @@ type preAuthUser struct { uid uint32 } -type preAuthUserRecords struct { - idGenerator IDGenerator - rwMu sync.RWMutex - users map[uint32]preAuthUser - uidByName map[string]uint32 - uidByLogin map[string]uint32 +// PreAuthUserRecords is a structure holding in memory all the temporary users +// that have done a pre-auth request. +type PreAuthUserRecords struct { + rwMu sync.RWMutex + users map[uint32]preAuthUser + uidByName map[string]uint32 + uidByLogin map[string]uint32 } -func newPreAuthUserRecords(idGenerator IDGenerator) *preAuthUserRecords { - return &preAuthUserRecords{ - idGenerator: idGenerator, - users: make(map[uint32]preAuthUser), - uidByName: make(map[string]uint32), - uidByLogin: make(map[string]uint32), +// NewPreAuthUserRecords creates a new instance of [preAuthUserRecords] that +// can be used to track the temporary preauth user entries. +func NewPreAuthUserRecords() *PreAuthUserRecords { + return &PreAuthUserRecords{ + users: make(map[uint32]preAuthUser), + uidByName: make(map[string]uint32), + uidByLogin: make(map[string]uint32), } } +// MaybeCompletePreauthUser tries to complete a pre-auth user registration. +// +// Returns the generated UID if the user was found in the pre-auth list or an, and a cleanup function that should be called to +// remove the temporary user once the user is added to the database. +func (r *PreAuthUserRecords) MaybeCompletePreauthUser(name string) (uid uint32, cleanup func(), err error) { + r.rwMu.Lock() + defer r.rwMu.Unlock() + + // Check if there is already a pre-auth user for that name + uid, ok := r.uidByLogin[name] + if !ok { + return 0, nil, NoDataFoundError{} + } + + return uid, func() { + r.rwMu.Lock() + defer r.rwMu.Unlock() + + r.deletePreAuthUser(uid) + }, nil +} + // UserByID returns the user information for the given user ID. -func (r *preAuthUserRecords) userByID(uid uint32) (types.UserEntry, error) { - r.rwMu.RLock() - defer r.rwMu.RUnlock() +func (r *PreAuthUserRecords) UserByID(uid uint32) (types.UserEntry, error) { + return r.userByID(uid) +} - return r.userByIDWithoutLock(uid) +// UserByLogin returns the user information for the given user name. +func (r *PreAuthUserRecords) UserByLogin(name string) (types.UserEntry, error) { + return r.userByLogin(name) } -func (r *preAuthUserRecords) userByIDWithoutLock(uid uint32) (types.UserEntry, error) { - user, ok := r.users[uid] - if !ok { - return types.UserEntry{}, db.NewUIDNotFoundError(uid) +// RegisterPreAuthUser registers a temporary user with a unique UID in our NSS +// handler (in memory, not in the database). +// +// The temporary user record is removed when [MaybeCompletePreauthUser] is called with the +// same username. +// +// This method is called when a user logs in for the first time via SSH, in +// which case sshd checks if the user exists on the system (before +// authentication), and denies the login if the user does not exist. +// We pretend that the user exists by creating this temporary user record, +// which is converted into a permanent user record when [RegisterUser] is called +// after the user authenticated successfully. +func (r *PreAuthUserRecords) RegisterPreAuthUser(loginName string, uid uint32) (err error) { + if loginName == "" { + return fmt.Errorf("empty username") } - return preAuthUserEntry(user), nil + r.rwMu.Lock() + defer r.rwMu.Unlock() + + r.rwMu.Lock() + defer r.rwMu.Unlock() + + // To mitigate DoS attacks, we limit the pre-auth users to [MaxPreAuthUsers]. + if len(r.users) >= MaxPreAuthUsers { + return errors.New("maximum number of pre-auth users reached, login for new users via SSH is disabled until authd is restarted") + } + + if _, ok := r.uidByLogin[loginName]; ok { + panic(fmt.Sprintf("We should never be here, User %q is already tracked", loginName)) + } + + if user, ok := r.users[uid]; ok { + panic(fmt.Sprintf("We should never be here, ID %d is already tracked for %q", + uid, user.loginName)) + } + + if err := r.addPreAuthUser(uid, loginName); err != nil { + return fmt.Errorf("could not add pre-auth user record: %w", err) + } + + return nil } -// UserByName returns the user information for the given user name. -func (r *preAuthUserRecords) userByName(name string) (types.UserEntry, error) { +// UserByID returns the user information for the given user ID. +func (r *PreAuthUserRecords) userByID(uid uint32) (types.UserEntry, error) { r.rwMu.RLock() defer r.rwMu.RUnlock() - uid, ok := r.uidByName[name] + return r.userByIDWithoutLock(uid) +} + +func (r *PreAuthUserRecords) userByIDWithoutLock(uid uint32) (types.UserEntry, error) { + user, ok := r.users[uid] if !ok { - return types.UserEntry{}, db.NewUserNotFoundError(name) + return types.UserEntry{}, db.NewUIDNotFoundError(uid) } - return r.userByIDWithoutLock(uid) + return preAuthUserEntry(user), nil } -func (r *preAuthUserRecords) userByLogin(loginName string) (types.UserEntry, error) { +func (r *PreAuthUserRecords) userByLogin(loginName string) (types.UserEntry, error) { r.rwMu.RLock() defer r.rwMu.RUnlock() @@ -104,27 +179,13 @@ func preAuthUserEntry(user preAuthUser) types.UserEntry { } } -func (r *preAuthUserRecords) generatePreAuthUserID(loginName string) (uid uint32, err error) { - if loginName == "" { - return 0, errors.New("empty username") - } - - // To mitigate DoS attacks, we limit the length of the name to 256 characters. - if len(loginName) > 256 { - return 0, errors.New("username is too long (max 256 characters)") - } - +// addPreAuthUser adds a temporary user with a random name and the given UID. We use a random name here to avoid +// creating user records with attacker-controlled names. +func (r *PreAuthUserRecords) addPreAuthUser(uid uint32, loginName string) (err error) { if len(r.users) >= MaxPreAuthUsers { - return 0, errors.New("maximum number of pre-auth users reached, login for new users via SSH is disabled until authd is restarted") + return errors.New("maximum number of pre-auth users reached, login for new users via SSH is disabled until authd is restarted") } - // Generate a UID - return r.idGenerator.GenerateUID() -} - -// addPreAuthUser adds a temporary user with a random name and the given UID. We use a random name here to avoid -// creating user records with attacker-controlled names. -func (r *preAuthUserRecords) addPreAuthUser(uid uint32, loginName string) (err error) { var name string for { // Generate a 64 character (32 bytes in hex) random ID which we store in the @@ -157,17 +218,16 @@ func (r *preAuthUserRecords) addPreAuthUser(uid uint32, loginName string) (err e // deletePreAuthUser deletes the temporary user with the given UID. // // This must be called with the mutex locked. -func (r *preAuthUserRecords) deletePreAuthUser(uid uint32) bool { +func (r *PreAuthUserRecords) deletePreAuthUser(uid uint32) { user, ok := r.users[uid] if !ok { // We ignore the case that the pre-auth user does not exist, because it can happen that the same user is // registered multiple times (because multiple SSH sessions are opened for the same user) and the cleanup // function is called multiple times. - return false + return } delete(r.users, uid) delete(r.uidByName, user.name) delete(r.uidByLogin, user.loginName) log.Debugf(context.Background(), "Removed temporary record for user %q with UID %d", user.name, uid) - return true } diff --git a/internal/users/tempentries/preauth_test.go b/internal/users/tempentries/preauth_test.go index 38a9f26675..3a22953cc1 100644 --- a/internal/users/tempentries/preauth_test.go +++ b/internal/users/tempentries/preauth_test.go @@ -9,7 +9,9 @@ import ( "github.com/stretchr/testify/require" "github.com/ubuntu/authd/internal/testutils/golden" "github.com/ubuntu/authd/internal/users/idgenerator" + userslocking "github.com/ubuntu/authd/internal/users/locking" "github.com/ubuntu/authd/internal/users/types" + "github.com/ubuntu/authd/log" ) func TestPreAuthUser(t *testing.T) { @@ -23,30 +25,34 @@ func TestPreAuthUser(t *testing.T) { maxUsers bool uidsToGenerate []uint32 - wantErr bool - wantUIDs []uint32 + wantErr bool + wantPanic []bool + wantUIDs []uint32 }{ "Successfully_register_a_pre-auth_user": {}, - "Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_in_use": { - uidsToGenerate: []uint32{0, uidToGenerate}, // UID 0 (root) always exists + "Panics_registering_a_pre-auth_user_again": { + users: []string{defaultLoginName, defaultLoginName}, + uidsToGenerate: []uint32{uidToGenerate, uidToGenerate}, wantUIDs: []uint32{uidToGenerate}, + wantPanic: []bool{false, true}, }, - "Successfully_register_a_pre-auth_user_again": { + "Panics_registering_a_pre-auth_user_again_with_different_uid": { users: []string{defaultLoginName, defaultLoginName}, - uidsToGenerate: []uint32{uidToGenerate}, - wantUIDs: []uint32{uidToGenerate, uidToGenerate}, + uidsToGenerate: []uint32{uidToGenerate, uidToGenerate + 1}, + wantUIDs: []uint32{uidToGenerate}, + wantPanic: []bool{false, true}, }, - "Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_registered": { + "Panics_registering_a_pre-auth_user_if_the_first_generated_UID_is_already_registered": { users: []string{defaultLoginName, "other-test"}, uidsToGenerate: []uint32{uidToGenerate, uidToGenerate, uidToGenerate + 1}, wantUIDs: []uint32{uidToGenerate, uidToGenerate + 1}, + wantPanic: []bool{false, true}, }, - "No_error_when_registering_a_pre-auth_user_with_the_same_name": {users: []string{defaultLoginName, defaultLoginName}}, "Error_when_maximum_number_of_pre-auth_users_is_reached": {maxUsers: true, wantErr: true}, - "Error_when_a_valid_UID_cannot_be_found": { - uidsToGenerate: make([]uint32, maxIDGenerateIterations*10), - wantErr: true, + "Error_when_login_name_is_empty": { + users: []string{""}, + wantErr: true, }, } @@ -71,21 +77,35 @@ func TestPreAuthUser(t *testing.T) { t.Log("UIDs to generate", tc.uidsToGenerate) idGeneratorMock := &idgenerator.IDGeneratorMock{UIDsToGenerate: tc.uidsToGenerate} - records := NewTemporaryRecords(idGeneratorMock) + records := NewPreAuthUserRecords() if tc.maxUsers { records.users = make(map[uint32]preAuthUser, MaxPreAuthUsers) for i := range uint32(MaxPreAuthUsers) { - records.users[uidToGenerate+i] = preAuthUser{uid: uidToGenerate + i} + uid := uidToGenerate + i + 1 + loginName := fmt.Sprintf("pre-auth-%d", uid) + records.users[uid] = preAuthUser{uid: uid, loginName: loginName} + records.uidByLogin[loginName] = uid } } + if tc.wantPanic == nil { + tc.wantPanic = make([]bool, len(tc.users)) + } wantRegistered := 0 var registeredUIDs []uint32 for idx, loginName := range tc.users { t.Logf("Registering user %q", loginName) - uid, err := records.registerPreAuthUser(loginName) + uid, err := idGeneratorMock.GenerateUID() + require.NoError(t, err, "GenerateUID should not return an error, but it did") + + if tc.wantPanic[idx] { + require.Panics(t, func() { _ = records.RegisterPreAuthUser(loginName, uid) }, + "RegisterPreAuthUser should have panic'ed but it did not") + continue + } + err = records.RegisterPreAuthUser(loginName, uid) if tc.wantErr { require.Error(t, err, "RegisterPreAuthUser should return an error, but did not") continue @@ -128,6 +148,9 @@ func TestPreAuthUser(t *testing.T) { } for idx, loginName := range tc.users { + if tc.wantPanic[idx] { + continue + } isDuplicated := slices.Contains(tc.users[0:idx], loginName) if !isDuplicated { wantRegistered-- @@ -170,12 +193,14 @@ func TestPreAuthUserByIDAndName(t *testing.T) { t.Parallel() idGeneratorMock := &idgenerator.IDGeneratorMock{UIDsToGenerate: []uint32{uidToGenerate}} - records := NewTemporaryRecords(idGeneratorMock) + records := NewPreAuthUserRecords() if tc.registerUser { - uid, err := records.registerPreAuthUser(loginName) + uid, err := idGeneratorMock.GenerateUID() + require.NoError(t, err, "GenerateUID should not return an error, but it did") + + err = records.RegisterPreAuthUser(loginName, uid) require.NoError(t, err, "generatePreAuthUserID should not return an error, but did") - require.Equal(t, uidToGenerate, uid, "UID should be the one generated by the IDGenerator") } if tc.userAlreadyRemoved { @@ -192,14 +217,6 @@ func TestPreAuthUserByIDAndName(t *testing.T) { } require.NoError(t, err, "UserByID should not return an error, but did") checkPreAuthUser(t, user) - - user, err = records.userByName(user.Name) - if tc.wantErr { - require.Error(t, err, "UserByName should return an error, but did not") - return - } - require.NoError(t, err, "UserByName should not return an error, but did") - checkPreAuthUser(t, user) }) } } @@ -211,7 +228,16 @@ func checkPreAuthUser(t *testing.T, user types.UserEntry, options ...golden.Opti // before comparing the user with the golden file. require.True(t, strings.HasPrefix(user.Name, UserPrefix), "Name should have %q prefix", UserPrefix) - user.Name = UserPrefix + "-XXXXXXXX" + user.Name = UserPrefix + "-{RANDOM-ID}" golden.CheckOrUpdateYAML(t, user, options...) } + +func TestMain(m *testing.M) { + log.SetLevel(log.DebugLevel) + + userslocking.Z_ForTests_OverrideLocking() + defer userslocking.Z_ForTests_RestoreLocking() + + m.Run() +} diff --git a/internal/users/tempentries/tempentries.go b/internal/users/tempentries/tempentries.go deleted file mode 100644 index cca18a6802..0000000000 --- a/internal/users/tempentries/tempentries.go +++ /dev/null @@ -1,427 +0,0 @@ -// Package tempentries provides a temporary user and group records. -package tempentries - -import ( - "context" - "errors" - "fmt" - "slices" - "sync/atomic" - - "github.com/ubuntu/authd/internal/testsdetection" - "github.com/ubuntu/authd/internal/users/db" - "github.com/ubuntu/authd/internal/users/localentries" - "github.com/ubuntu/authd/internal/users/types" - "github.com/ubuntu/authd/log" - "github.com/ubuntu/decorate" -) - -// Avoid to loop forever if we can't find an UID for the user, it's just better -// to fail after a limit is reached than hang or crash. -const maxIDGenerateIterations = 256 - -// NoDataFoundError is the error returned when no entry is found in the database. -type NoDataFoundError = db.NoDataFoundError - -// IDGenerator is the interface that must be implemented by the ID generator. -type IDGenerator interface { - GenerateUID() (uint32, error) - GenerateGID() (uint32, error) -} - -// TemporaryRecords is the in-memory temporary user and group records. -type TemporaryRecords struct { - *idTracker - *preAuthUserRecords - *temporaryGroupRecords - - idGenerator IDGenerator - lockedRecords atomic.Pointer[TemporaryRecordsWithLock] -} - -// TemporaryRecordsWithLock is the structure that allows to safely perform write -// changes to [TemporaryRecords] entries while the local entries database is -// locked. -type TemporaryRecordsWithLock struct { - tr *TemporaryRecords - entries *localentries.UserDBLocked -} - -// RegisterUser registers a temporary user with a unique UID. -// -// Returns the generated UID and a cleanup function that should be called to -// remove the temporary user once the user is added to the database. -func (l *TemporaryRecordsWithLock) RegisterUser(name string) (uid uint32, cleanup func(), err error) { - return l.tr.registerUser(name) -} - -// RegisterPreAuthUser registers a temporary user with a unique UID in our NSS -// handler (in memory, not in the database). -// -// The temporary user record is removed when [RegisterUser] is called with the -// same username. -// -// This method is called when a user logs in for the first time via SSH, in -// which case sshd checks if the user exists on the system (before -// authentication), and denies the login if the user does not exist. -// We pretend that the user exists by creating this temporary user record, -// which is converted into a permanent user record when [RegisterUser] is called -// after the user authenticated successfully. -// -// Returns the generated UID. -func (l *TemporaryRecordsWithLock) RegisterPreAuthUser(loginName string) (uid uint32, err error) { - return l.tr.registerPreAuthUser(loginName) -} - -// RegisterGroupForUser registers a temporary group with a unique GID in -// memory for the provided UID. -// -// Returns the generated GID and a cleanup function that should be called to -// remove the temporary group once the group was added to the database. -func (l *TemporaryRecordsWithLock) RegisterGroupForUser(uid uint32, name string) (gid uint32, cleanup func(), err error) { - return l.tr.registerGroupForUser(uid, name) -} - -// NewTemporaryRecords creates a new TemporaryRecords. -func NewTemporaryRecords(idGenerator IDGenerator) *TemporaryRecords { - return &TemporaryRecords{ - idGenerator: idGenerator, - idTracker: newIDTracker(), - preAuthUserRecords: newPreAuthUserRecords(idGenerator), - temporaryGroupRecords: newTemporaryGroupRecords(idGenerator), - } -} - -// UserByID returns the user information for the given user ID. -func (r *TemporaryRecords) UserByID(uid uint32) (types.UserEntry, error) { - return r.preAuthUserRecords.userByID(uid) -} - -// UserByName returns the user information for the given user name. -func (r *TemporaryRecords) UserByName(name string) (types.UserEntry, error) { - return r.preAuthUserRecords.userByName(name) -} - -// LockForChanges locks the underneath system user entries databases and returns -// a [temporaryRecordsLocked] that allows to perform write-modifications to the -// user records in a way that is safe for the environment, and preventing races -// with other NSS modules. -// -//nolint:revive,nolintlint // [temporaryRecordsLocked] is not a type we want to be able to use outside of this package -func (r *TemporaryRecords) LockForChanges(entries *localentries.UserDBLocked) (tra *TemporaryRecordsWithLock) { - entries.MustBeLocked() - lockedRecords := &TemporaryRecordsWithLock{tr: r, entries: entries} - - for { - if r.lockedRecords.CompareAndSwap(nil, lockedRecords) { - break - } - - // We've old locked records, let's just return these once we're sure! - l := r.lockedRecords.Load() - if l == nil { - continue - } - return l - } - - return lockedRecords -} - -func (r *TemporaryRecords) registerUser(name string) (uid uint32, cleanup func(), err error) { - defer decorate.OnError(&err, "failed to register user %q", name) - - unique, err := r.isUniqueSystemUserName(name) - if err != nil { - return 0, nil, err - } - if !unique { - return 0, nil, fmt.Errorf("user %q already exists", name) - } - - // Check if there is a pre-auth user with the same login name. - user, err := r.preAuthUserRecords.userByLogin(name) - if err != nil && !errors.Is(err, NoDataFoundError{}) { - return 0, nil, fmt.Errorf("could not check if pre-auth user %q already exists: %w", name, err) - } - if err == nil { - // There is a pre-auth user with the same login name. - - // Remove the pre-checked user as last thing, so that the user UID is exposed as the - // ones we manage in authd while we're updating the users, although there's no risk - // that someone else takes the UID here, since we're locked. - cleanup := func() { - r.preAuthUserRecords.rwMu.Lock() - defer r.preAuthUserRecords.rwMu.Unlock() - - r.idTracker.forgetUser(name) - if r.deletePreAuthUser(user.UID) { - r.forgetID(user.UID) - } - } - - // Now that the user authenticated successfully, we don't really need to check again - // if the UID is unique, since that's something we did while registering it, and we're - // currently locked, so nothing else can add another user with such ID, but we do to - // double check it, just in case. - unique, err := r.isUniqueSystemID(user.UID) - if err != nil { - cleanup() - return 0, nil, err - } - if !unique { - cleanup() - return 0, nil, fmt.Errorf("UID (%d) or name (%q) from pre-auth user are not unique", user.UID, name) - } - - return user.UID, cleanup, nil - } - - // Generate a UID until we find a unique one - for range maxIDGenerateIterations { - uid, err = r.idGenerator.GenerateUID() - if err != nil { - return 0, nil, err - } - - unique, err := r.maybeTrackUniqueID(uid) - if err != nil { - return 0, nil, err - } - if !unique { - // If the UID is not unique, generate a new one in the next iteration. - continue - } - - if tracked, currentUID := r.idTracker.trackUser(name, uid); !tracked { - // If the loginName is already set for a different UID, it means - // that another concurrent request won the race, so let's just - // use that one instead. - r.idTracker.forgetID(uid) - return currentUID, func() {}, nil - } - - log.Debugf(context.Background(), "Generated UID %d for user UID %s", uid, name) - return uid, func() { - r.idTracker.forgetID(uid) - r.idTracker.forgetUser(name) - }, nil - } - - return 0, nil, fmt.Errorf("failed to find a valid UID after %d attempts", - maxIDGenerateIterations) -} - -func (r *TemporaryRecords) registerPreAuthUser(loginName string) (uid uint32, err error) { - r.rwMu.Lock() - defer r.rwMu.Unlock() - - // Check if there is already a pre-auth user for that name - if uid, ok := r.preAuthUserRecords.uidByLogin[loginName]; ok { - return uid, nil - } - - unique, err := r.isUniqueSystemUserName(loginName) - if err != nil { - return 0, err - } - if !unique { - log.Errorf(context.Background(), "User already exists on the system: %+v", loginName) - return 0, fmt.Errorf("user %q already exists on the system", loginName) - } - - for range maxIDGenerateIterations { - uid, err := r.preAuthUserRecords.generatePreAuthUserID(loginName) - if err != nil { - return 0, err - } - - unique, err := r.maybeTrackUniqueID(uid) - if err != nil { - return 0, err - } - if !unique { - // If the UID is not unique, generate a new one in the next iteration. - continue - } - - if tracked, currentUID := r.idTracker.trackUser(loginName, uid); !tracked { - // If the loginName is already set for a different UID, it means - // that another concurrent request won the race, so let's just - // use that one instead. - r.idTracker.forgetID(uid) - uid = currentUID - } - - if err := r.addPreAuthUser(uid, loginName); err != nil { - r.idTracker.forgetUser(loginName) - r.idTracker.forgetID(uid) - return 0, fmt.Errorf("could not add pre-auth user record: %w", err) - } - - return uid, nil - } - - return 0, fmt.Errorf("failed to find a valid UID after %d attempts", - maxIDGenerateIterations) -} - -func (r *TemporaryRecords) userEntries() ([]types.UserEntry, error) { - l := r.lockedRecords.Load() - if l == nil { - testsdetection.MustBeTesting() - - le, cleanup, err := localentries.NewUserDBLocked() - if err != nil { - panic(fmt.Sprintf("Failed get lock local entries: %v", err)) - } - defer func() { - if err := cleanup(); err != nil { - panic(fmt.Sprintf("Failed get cleanup locked entries: %v", err)) - } - }() - - l = &TemporaryRecordsWithLock{entries: le} - } - - return l.entries.GetUserEntries() -} - -func (r *TemporaryRecords) groupEntries() ([]types.GroupEntry, error) { - l := r.lockedRecords.Load() - if l == nil { - testsdetection.MustBeTesting() - - le, cleanup, err := localentries.NewUserDBLocked() - if err != nil { - panic(fmt.Sprintf("Failed get lock local entries: %v", err)) - } - defer func() { - if err := cleanup(); err != nil { - panic(fmt.Sprintf("Failed get cleanup locked entries: %v", err)) - } - }() - - l = &TemporaryRecordsWithLock{entries: le} - } - - return l.entries.GetGroupEntries() -} - -func (r *TemporaryRecords) registerGroupForUser(uid uint32, name string) (gid uint32, cleanup func(), err error) { - defer decorate.OnError(&err, "failed to register group %q for user ID %d", name, uid) - - groupEntries, err := r.groupEntries() - if err != nil { - return 0, nil, err - } - - if slices.ContainsFunc(groupEntries, func(g types.GroupEntry) bool { - return g.Name == name - }) { - return 0, nil, fmt.Errorf("group %q already exists", name) - } - - r.temporaryGroupRecords.mu.Lock() - defer r.temporaryGroupRecords.mu.Unlock() - - groupCleanup := func() { - r.temporaryGroupRecords.mu.Lock() - defer r.temporaryGroupRecords.mu.Unlock() - - if r.releaseTemporaryGroup(gid) { - r.idTracker.forgetID(gid) - } - } - - if gid = r.getTemporaryGroup(name); gid != 0 { - return gid, groupCleanup, nil - } - - for range maxIDGenerateIterations { - gid, err = r.temporaryGroupRecords.generateGroupID() - if err != nil { - return 0, nil, err - } - - if gid == uid { - // Generated GID matches current user UID, try again... - continue - } - - unique, err := r.maybeTrackUniqueID(gid) - if err != nil { - return 0, nil, err - } - if !unique { - // If the GID is not unique, generate a new one in the next iteration. - continue - } - - r.addTemporaryGroup(gid, name) - - return gid, groupCleanup, nil - } - - return 0, nil, fmt.Errorf("failed to find a valid GID after %d attempts", - maxIDGenerateIterations) -} - -func (r *TemporaryRecords) maybeTrackUniqueID(id uint32) (unique bool, err error) { - defer func() { - if !unique { - log.Debugf(context.TODO(), "ID %d is not unique in this system", id) - } - }() - - unique, err = r.isUniqueSystemID(id) - if err != nil { - return false, err - } - if !unique { - return false, nil - } - - return r.idTracker.trackID(id), nil -} - -func (r *TemporaryRecords) isUniqueSystemID(id uint32) (unique bool, err error) { - userEntries, err := r.userEntries() - if err != nil { - return false, err - } - - if idx := slices.IndexFunc(userEntries, func(p types.UserEntry) (found bool) { - return p.UID == id - }); idx != -1 { - log.Debugf(context.Background(), "ID %d already in use by user %q", - id, userEntries[idx].Name) - return false, nil - } - - groupEntries, err := r.groupEntries() - if err != nil { - return false, err - } - - if idx := slices.IndexFunc(groupEntries, func(g types.GroupEntry) (found bool) { - return g.GID == id - }); idx != -1 { - log.Debugf(context.Background(), "ID %d already in use by user %q", - id, groupEntries[idx].Name) - return false, nil - } - - return true, nil -} - -func (r *TemporaryRecords) isUniqueSystemUserName(name string) (unique bool, err error) { - userEntries, err := r.userEntries() - if err != nil { - return false, err - } - - return !slices.ContainsFunc(userEntries, func(p types.UserEntry) bool { - return p.Name == name - }), nil -} diff --git a/internal/users/tempentries/tempentries_test.go b/internal/users/tempentries/tempentries_test.go deleted file mode 100644 index bbe440b49f..0000000000 --- a/internal/users/tempentries/tempentries_test.go +++ /dev/null @@ -1,842 +0,0 @@ -package tempentries - -import ( - "fmt" - "slices" - "sync" - "sync/atomic" - "testing" - - "github.com/stretchr/testify/require" - "github.com/ubuntu/authd/internal/testutils/golden" - "github.com/ubuntu/authd/internal/users/idgenerator" - "github.com/ubuntu/authd/internal/users/localentries" - userslocking "github.com/ubuntu/authd/internal/users/locking" - "github.com/ubuntu/authd/internal/users/types" - "github.com/ubuntu/authd/log" -) - -func TestLockedInvalidActions(t *testing.T) { - t.Parallel() - - require.Panics(t, func() { _, _ = (&TemporaryRecordsWithLock{}).RegisterPreAuthUser("foobar") }, - "RegisterPreAuthUser should panic but did not") - require.Panics(t, func() { _, _, _ = (&TemporaryRecordsWithLock{}).RegisterUser("foobar") }, - "RegisterUser should panic but did not") - require.Panics(t, func() { _, _, _ = (&TemporaryRecordsWithLock{}).RegisterGroupForUser(123, "foobar") }, - "RegisterGroupForUser should panic but did not") - - require.Panics(t, func() { NewTemporaryRecords(nil).LockForChanges(&localentries.UserDBLocked{}) }, - "LockForChanges should panic but did not") - - tmpRecords := NewTemporaryRecords(&idgenerator.IDGenerator{UIDMax: 100}) - - entries, entriesUnlock, err := localentries.NewUserDBLocked(([]localentries.Option{})...) - require.NoError(t, err, "Setup: failed to lock the locale entries") - - records := tmpRecords.LockForChanges(entries) - - err = entriesUnlock() - require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") - - err = entriesUnlock() - require.Error(t, err, "Unlocking twice should fail") - - require.Panics(t, func() { _, _ = records.RegisterPreAuthUser("foobar") }, - "RegisterPreAuthUser should panic but did not") - require.Panics(t, func() { _, _, _ = records.RegisterUser("foobar") }, - "RegisterUser should panic but did not") - require.Panics(t, func() { _, _, _ = records.RegisterGroupForUser(123, "foobar") }, - "RegisterGroupForUser should panic but did not") - - // This is to ensure that we're in a good state, despite the actions above - for range 10 { - entries, entriesUnlock, err := localentries.NewUserDBLocked() - require.NoError(t, err, "Failed to lock the local entries") - _ = tmpRecords.LockForChanges(entries) - defer func() { - err := entriesUnlock() - require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") - }() - } -} - -func TestRacingLockingActions(t *testing.T) { - t.Parallel() - - // These tests are meant to stress-test in parallel our temporary entries, - // this is happening by registering new users or pre-auth some of them and - // ensure that the generated IDs and GIDs are not clashing. - // There may be still clashes when the cleanup functions are called, but - // then it's up to the caller to ensure that these IDs are not duplicated - // in the database. - - const nIterations = 100 - - type cleanupType string - const ( - noCleanup cleanupType = "no_cleanup" - perUserCleanup cleanupType = "per_user_cleanup" - perTestCleanup cleanupType = "per_test_cleanup" - ) - - for _, cleanupType := range []cleanupType{noCleanup, perTestCleanup, perUserCleanup} { - registeredUsersMu := sync.Mutex{} - registeredUsers := make(map[string][]uint32) - registeredGroups := make(map[string][]uint32) - - tmpRecords := NewTemporaryRecords(&idgenerator.IDGenerator{ - UIDMin: 0, - UIDMax: nIterations * 5, - GIDMin: 0, - GIDMax: nIterations * 5, - }) - - t.Run(fmt.Sprintf("with_%s", cleanupType), func(t *testing.T) { - t.Parallel() - - for idx := range nIterations { - t.Run(fmt.Sprintf("iteration_%d", idx), func(t *testing.T) { - t.Parallel() - - entries, entriesUnlock, err := localentries.NewUserDBLocked() - require.NoError(t, err, "Setup: failed to lock the locale entries") - t.Cleanup(func() { - err = entriesUnlock() - require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") - }) - - records := tmpRecords.LockForChanges(entries) - doPreAuth := idx%3 == 0 - userName := fmt.Sprintf("authd-test-user%d", idx) - - cleanupsMu := sync.Mutex{} - var cleanups []func() - - var userID atomic.Uint32 - var firstGroupID atomic.Uint32 - - if doPreAuth { - // In the pre-auth case we do even more parallelization, so that - // the pre-auth happens without a defined order of the actual - // registration. - userName = fmt.Sprintf("authd-test-maybe-pre-check-user%d", idx) - - //nolint:thelper // This is actually a test function! - preAuth := func(t *testing.T) { - t.Parallel() - - uid, err := records.RegisterPreAuthUser(userName) - require.NoError(t, err, "RegisterPreAuthUser should not fail but it did") - - if !userID.CompareAndSwap(0, uid) && cleanupType != perTestCleanup { - require.Equal(t, int(uid), int(userID.Load()), - "Pre-auth UID for already-registered user is not matching expected") - } - } - - for i := range 3 { - t.Run(fmt.Sprintf("pre_auth%d", i), preAuth) - } - } - - //nolint:thelper // This is actually a test function! - userUpdate := func(t *testing.T) { - t.Parallel() - - // We do not run the cleanup function here, because we want to preserve the - // user in our temporary entries, to ensure that we may not register the same - // twice. - uid, userCleanup, err := records.RegisterUser(userName) - require.NoError(t, err, "RegisterUser should not fail but it did") - if cleanupType == perTestCleanup { - t.Cleanup(userCleanup) - } - - if !userID.CompareAndSwap(0, uid) && cleanupType != perTestCleanup { - require.Equal(t, int(uid), int(userID.Load()), - "UID for pre-auth or already registered user %q is not matching expected", - userName) - } - - groupName1 := fmt.Sprintf("authd-test-group%d.1", idx) - gid1, groupCleanup1, err := records.RegisterGroupForUser(uid, groupName1) - require.NoError(t, err, "RegisterGroupForUser should not fail but it did") - if cleanupType == perTestCleanup { - t.Cleanup(groupCleanup1) - } - - if !firstGroupID.CompareAndSwap(0, gid1) && cleanupType != perTestCleanup { - require.Equal(t, int(gid1), int(firstGroupID.Load()), - "GID for group %q is not matching expected", groupName1) - } - - groupName2 := fmt.Sprintf("authd-test-group%d.2", idx) - gid2, groupCleanup2, err := records.RegisterGroupForUser(uid, groupName2) - require.NoError(t, err, "RegisterGroupForUser should not fail but it did") - if cleanupType == perTestCleanup { - t.Cleanup(groupCleanup2) - } - - registeredUsersMu.Lock() - defer registeredUsersMu.Unlock() - registeredUsers[userName] = append(registeredUsers[userName], uid) - registeredGroups[groupName1] = append(registeredGroups[groupName1], gid1) - registeredGroups[groupName2] = append(registeredGroups[groupName2], gid2) - - cleanupsMu.Lock() - defer cleanupsMu.Unlock() - - if cleanupType == perUserCleanup { - cleanups = append(cleanups, userCleanup, groupCleanup1, groupCleanup2) - } - } - - testName := "update_user" - if doPreAuth { - testName = "maybe_finish_registration" - } - - for i := range 3 { - t.Run(fmt.Sprintf("%s%d", testName, i), userUpdate) - } - - t.Cleanup(func() { - cleanupsMu.Lock() - defer cleanupsMu.Unlock() - - t.Logf("Running cleanups for iteration %d", idx) - for _, cleanup := range cleanups { - cleanup() - } - }) - }) - } - - t.Cleanup(func() { - t.Log("Running final checks for cleanup mode " + fmt.Sprint(cleanupType)) - - registeredUsersMu.Lock() - defer registeredUsersMu.Unlock() - - if cleanupType == perTestCleanup { - // In such case we may have duplicate UIDs or GIDs since they - // may be duplicated after each test has finished. - // This is not actually a problem because in the real scenario - // the temporary entries owner (the user manager) should check - // that such IDs are already registered in the database before - // actually using them. - return - } - - uniqueIDs := make(map[uint32]string) - - for u, uids := range registeredUsers { - uids = slices.Compact(uids) - require.Len(t, uids, 1, "Only one UID should be registered for user %q", u) - - if cleanupType == perUserCleanup { - // In this case we only care about the fact of having registered only one - // UID for each user, although the UIDs may not be unique across all the - // tests, since the cleanup functions have deleted the temporary data. - // It's still important to test this case though, since it allows to ensure - // that the pre-check and registered user IDs are valid. - continue - } - - old, ok := uniqueIDs[uids[0]] - require.False(t, ok, "ID %d must be unique across entries, but it's used by both %q and %q", - uids[0], u, old) - uniqueIDs[uids[0]] = u - } - - for g, gids := range registeredGroups { - gids = slices.Compact(gids) - require.Len(t, gids, 1, "Only one GID should be registered for group %q", g) - - if cleanupType == perUserCleanup { - // In this case we only care about the fact of having registered only one - // UID for each user, although the UIDs may not be unique across all the - // tests, since the cleanup functions have deleted the temporary data. - // It's still important to test this case though, since it allows to ensure - // that the pre-check and registered user IDs are valid. - continue - } - - old, ok := uniqueIDs[gids[0]] - require.False(t, ok, "ID %d must be unique across entries, but it's used both %q and %q", - gids[0], g, old) - uniqueIDs[gids[0]] = g - } - }) - }) - } -} - -func TestRegisterUser(t *testing.T) { - t.Parallel() - - uidToGenerate := uint32(12345) - userName := "authd-temp-users-test" - - tests := map[string]struct { - userName string - uidsToGenerate []uint32 - userAlreadyRemoved bool - replacesPreAuthUser bool - preAuthUIDAlreadyExists bool - - wantErr bool - }{ - "Successfully_register_a_new_user": {}, - "Successfully_register_a_user_if_the_first_generated_UID_is_already_in_use": { - uidsToGenerate: []uint32{0, uidToGenerate}, // UID 0 (root) always exists - }, - "Successfully_register_a_user_if_the_pre-auth_user_already_exists": { - replacesPreAuthUser: true, - uidsToGenerate: []uint32{}, // No UID generation needed - }, - - "Error_when_name_is_already_in_use": {userName: "root", wantErr: true}, - "Error_when_pre-auth_user_already_exists_and_name_is_not_unique": { - replacesPreAuthUser: true, - userName: "root", - wantErr: true, - }, - "Error_when_a_valid_UID_cannot_be_found": { - uidsToGenerate: make([]uint32, maxIDGenerateIterations*10), - wantErr: true, - }, - "Error_when_pre-auth_UID_is_not_unique": { - replacesPreAuthUser: true, - preAuthUIDAlreadyExists: true, - wantErr: true, - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - t.Parallel() - - if tc.userName == "" { - tc.userName = userName - } - - if tc.uidsToGenerate == nil { - tc.uidsToGenerate = []uint32{uidToGenerate} - } - - idGeneratorMock := &idgenerator.IDGeneratorMock{UIDsToGenerate: tc.uidsToGenerate} - records := NewTemporaryRecords(idGeneratorMock) - - var preAuthUID uint32 - if tc.replacesPreAuthUser { - preAuthUID = uidToGenerate - if tc.preAuthUIDAlreadyExists { - preAuthUID = 0 // UID 0 (root) always exists - } - err := records.preAuthUserRecords.addPreAuthUser(preAuthUID, tc.userName) - require.NoError(t, err, "addPreAuthUser should not return an error, but did") - } - - uid, cleanup, err := records.registerUser(tc.userName) - if tc.wantErr { - require.Error(t, err, "RegisterUser should return an error, but did not") - return - } - t.Cleanup(cleanup) - require.NoError(t, err, "RegisterUser should not return an error, but did") - require.Equal(t, uidToGenerate, uid, "UID should be the one generated by the IDGenerator") - - if tc.replacesPreAuthUser { - // Check that the pre-auth user was removed - user, err := records.preAuthUserRecords.userByID(preAuthUID) - require.NoError(t, err, "userByID should not return an error, but it not") - checkPreAuthUser(t, user) - return - } - - _, err = records.UserByID(uid) - require.Error(t, err, "userByID should return an error, but did not") - }) - } -} - -func TestRegisterUserAndGroupForUser(t *testing.T) { - t.Parallel() - - defaultUserName := "authd-temp-users-test" - uidToGenerate := uint32(12345) - defaultGroupName := "authd-temp-groups-test" - gidToGenerate := uint32(54321) - - type userTestData struct { - name string - preAuth bool - groups []string - runCleanup bool - - // Simulates the case in which an UID has been registered while another - // request wins the UID registration race. - simulateUIDRace bool - - // Simulates the case in which an user has been registered while another - // request wins the username registration race. - simulateNameRace bool - - wantUID uint32 - wantErr bool - wantGroupErr []bool - wantGIDs []uint32 - } - - defaultUserTestData := userTestData{name: defaultUserName} - - tests := map[string]struct { - users []userTestData - uidsToGenerate []uint32 - gidsToGenerate []uint32 - }{ - "Successfully_register_a_new_group_for_generic_user": {}, - "Successfully_register_a_new_group_for_pre-auth_user": { - users: []userTestData{{name: defaultUserName, preAuth: true}}, - }, - "Successfully_register_a_new_group_for_various_generic_users": { - users: []userTestData{ - {name: defaultUserName + fmt.Sprint(1)}, - {name: defaultUserName + fmt.Sprint(2)}, - {name: defaultUserName + fmt.Sprint(3)}, - }, - }, - "Successfully_register_a_new_group_for_various_pre-auth_users": { - users: []userTestData{ - {name: defaultUserName + fmt.Sprint(1), preAuth: true}, - {name: defaultUserName + fmt.Sprint(2), preAuth: true}, - {name: defaultUserName + fmt.Sprint(3), preAuth: true}, - }, - }, - "Successfully_register_a_various_groups_for_generic_user": { - users: []userTestData{ - {name: defaultUserName, groups: []string{"group-a", "group-b", "group-c"}}, - }, - }, - "Successfully_register_a_various_groups_for_pre-auth_user": { - users: []userTestData{ - {name: defaultUserName, preAuth: true, groups: []string{"group-a", "group-b", "group-c"}}, - }, - }, - "Successfully_register_a_new_user_if_the_first_generated_UID_is_already_in_use": { - uidsToGenerate: []uint32{0, uidToGenerate}, // UID 0 (root) always exists - users: []userTestData{{name: defaultUserName, preAuth: true, wantUID: uidToGenerate}}, - }, - "Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use": { - users: []userTestData{{name: defaultUserName, wantGIDs: []uint32{gidToGenerate}}}, - gidsToGenerate: []uint32{0, gidToGenerate}, // GID 0 (root) always exists - }, - "Successfully_register_a_new_group_if_the_first_generated_GID_matches_the_user_UID": { - users: []userTestData{{name: defaultUserName, wantGIDs: []uint32{gidToGenerate}}}, - gidsToGenerate: []uint32{uidToGenerate, gidToGenerate}, // UID should be skipped! - }, - "Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user": { - users: []userTestData{ - {name: defaultUserName, preAuth: true}, - {name: "another-user-name"}, - }, - gidsToGenerate: []uint32{uidToGenerate, gidToGenerate, gidToGenerate + 1}, - }, - "Successfully_register_an_user_if_the_first_generated_UID_is_already_registered": { - users: []userTestData{ - {name: "pre-checked-user", preAuth: true, wantUID: uidToGenerate}, - {name: "pre-checked-user", wantUID: uidToGenerate, wantGIDs: []uint32{gidToGenerate}}, - {name: "other-user", wantUID: uidToGenerate + 1, wantGIDs: []uint32{gidToGenerate + 1}}, - }, - uidsToGenerate: []uint32{uidToGenerate, uidToGenerate, uidToGenerate + 1}, - }, - "Successfully_register_a_pre-checked_user_if_the_first_generated_UID_is_already_registered": { - users: []userTestData{ - {name: "pre-checked-user", preAuth: true, wantUID: uidToGenerate}, - {name: "other-pre-checked-user", preAuth: true, wantUID: uidToGenerate + 1}, - }, - uidsToGenerate: []uint32{uidToGenerate, uidToGenerate, uidToGenerate + 1}, - }, - "Successfully_register_a_pre-checked_user_twice_with_the_same_UID": { - users: []userTestData{ - {name: "pre-checked-user", preAuth: true, wantUID: uidToGenerate}, - {name: "pre-checked-user", preAuth: true, wantUID: uidToGenerate}, - }, - }, - "Successfully_register_an_user_after_two_pre_checks": { - users: []userTestData{ - {name: "pre-checked-user", preAuth: true, wantUID: uidToGenerate}, - {name: "pre-checked-user", preAuth: true, wantUID: uidToGenerate}, - {name: "pre-checked-user", wantUID: uidToGenerate, wantGIDs: []uint32{gidToGenerate}}, - }, - }, - "Successfully_register_an_user_with_the_same_name_after_two_pre_checks": { - users: []userTestData{ - {name: "pre-checked-user", preAuth: true, wantUID: uidToGenerate}, - {name: "pre-checked-user", preAuth: true, wantUID: uidToGenerate}, - {name: "pre-checked-user", wantUID: uidToGenerate, wantGIDs: []uint32{gidToGenerate}, runCleanup: true}, - {name: "pre-checked-user", preAuth: true, wantUID: uidToGenerate + 1}, - }, - }, - "Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen": { - users: []userTestData{ - {name: "racing-pre-checked-user", preAuth: true, simulateNameRace: true}, - {name: "racing-pre-checked-user", preAuth: true, wantUID: uidToGenerate}, - }, - }, - "Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen": { - users: []userTestData{ - {name: "racing-pre-checked-user", preAuth: true, simulateNameRace: true}, - {name: "racing-pre-checked-user", wantUID: uidToGenerate, wantGIDs: []uint32{gidToGenerate}}, - }, - }, - "Successfully_register_an_user_if_multiple_concurrent_requests_happen": { - users: []userTestData{ - {name: "racing-user", simulateNameRace: true, wantUID: uidToGenerate}, - {name: "racing-user", wantUID: uidToGenerate + 1, wantGIDs: []uint32{gidToGenerate}}, - }, - }, - "Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID": { - users: []userTestData{ - {name: "racing-pre-checked-user", preAuth: true, simulateUIDRace: true}, - {name: "racing-pre-checked-user", wantUID: uidToGenerate, wantGIDs: []uint32{gidToGenerate}}, - }, - }, - "Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID": { - users: []userTestData{ - {name: "racing-user", simulateUIDRace: true, wantUID: uidToGenerate}, - {name: "racing-user", wantUID: uidToGenerate, wantGIDs: []uint32{gidToGenerate}}, - }, - }, - - "Error_if_there_are_no_UID_to_generate": { - users: []userTestData{{name: defaultUserName, wantErr: true}}, - uidsToGenerate: []uint32{0}, - }, - "Error_if_there_are_no_UID_to_generate_for_pre-check_user": { - users: []userTestData{{name: defaultUserName, preAuth: true, wantErr: true}}, - uidsToGenerate: []uint32{0}, - }, - "Error_if_there_are_no_GID_to_generate": { - users: []userTestData{{name: defaultUserName, preAuth: true, wantGroupErr: []bool{true}}}, - gidsToGenerate: []uint32{}, - }, - "Error_if_there_are_no_GID_to_generate_for_pre-check_user": { - users: []userTestData{{name: defaultUserName, preAuth: true, wantGroupErr: []bool{true}}}, - gidsToGenerate: []uint32{}, - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - t.Parallel() - - if len(tc.users) == 0 { - tc.users = append(tc.users, defaultUserTestData) - } - - if tc.uidsToGenerate == nil { - uid := uidToGenerate - for range tc.users { - tc.uidsToGenerate = append(tc.uidsToGenerate, uid) - uid++ - } - } - t.Log("UIDs to generate", tc.uidsToGenerate) - - if tc.gidsToGenerate == nil { - gid := gidToGenerate - for _, u := range tc.users { - if u.preAuth { - continue - } - groups := len(u.groups) - if u.groups == nil { - groups = 1 - } - for range groups { - tc.gidsToGenerate = append(tc.gidsToGenerate, gid) - gid++ - } - } - } - t.Log("GIDs to generate", tc.gidsToGenerate) - - wantRegisteredUsers := 0 - var registeredUIDs []uint32 - - wantRegisteredGroups := 0 - var registeredGIDs []uint32 - - lastCleanupIdx := 0 - - idGeneratorMock := &idgenerator.IDGeneratorMock{ - UIDsToGenerate: tc.uidsToGenerate, - GIDsToGenerate: tc.gidsToGenerate, - } - records := NewTemporaryRecords(idGeneratorMock) - - for idx, uc := range tc.users { - t.Logf("Registering user %q", uc.name) - - replacingPreAuthUser := false - if !uc.preAuth { - _, err := records.userByLogin(uc.name) - replacingPreAuthUser = err == nil - } - - entries, entriesUnlock, err := localentries.NewUserDBLocked() - require.NoError(t, err, "Setup: failed to lock the locale entries") - t.Cleanup(func() { - err = entriesUnlock() - require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") - }) - - records := records.LockForChanges(entries) - internalRecords := records.tr - - var uid uint32 - var cleanup func() - if uc.preAuth { - uid, err = records.RegisterPreAuthUser(uc.name) - } else { - uid, cleanup, err = records.RegisterUser(uc.name) - } - - if cleanup != nil { - t.Cleanup(cleanup) - } - - if uc.wantErr { - require.Error(t, err, "User registration should return an error, but did not") - continue - } - - require.NoError(t, err, "User registration should not return an error, but did") - t.Logf("Registered user %q (preauth: %v) with UID %d", uc.name, uc.preAuth, uid) - - isDuplicated := slices.ContainsFunc(tc.users[lastCleanupIdx:idx], func(u userTestData) bool { - return u.name == uc.name - }) - - if uc.wantUID == 0 { - uc.wantUID = tc.uidsToGenerate[idx] - } - - if !isDuplicated && uc.preAuth { - wantRegisteredUsers++ - } - - require.Equal(t, uc.wantUID, uid, "%q UID is not matching expected value", uc.name) - require.Equal(t, wantRegisteredUsers, len(internalRecords.users), - "Number of pre-auth registered, users should be %d", wantRegisteredUsers) - - if isDuplicated { - require.Contains(t, registeredUIDs, uid, "UID %d has been already registered!", uid) - } else { - require.NotContains(t, registeredUIDs, uid, "UID %d has not been already registered!", uid) - } - - registeredUIDs = append(registeredUIDs, uid) - - if uc.runCleanup { - require.NotNil(t, cleanup, "Cleanup function is invalid!") - cleanup() - lastCleanupIdx = idx + 1 - - if replacingPreAuthUser { - wantRegisteredUsers-- - require.Equal(t, wantRegisteredUsers, len(internalRecords.users), - "Number of pre-auth registered, users should be %d", wantRegisteredUsers) - } - } - - // Check that the user was registered - user, err := internalRecords.userByLogin(uc.name) - if uc.preAuth || (replacingPreAuthUser && !uc.runCleanup) { - require.NoError(t, err, "UserByID should not return an error, but did") - } else { - require.ErrorIs(t, err, NoDataFoundError{}) - require.Zero(t, user, "User should be unset") - } - - var goldenOptions []golden.Option - userSuffix := "" - if idx > 0 { - userSuffix = fmt.Sprintf("_%s_%d", uc.name, idx) - goldenOptions = append(goldenOptions, golden.WithSuffix(userSuffix)) - } - - if uc.preAuth { - checkPreAuthUser(t, user, goldenOptions...) - - if uc.simulateUIDRace { - t.Logf("Dropping the registered UID %d for %q", uid, uc.name) - delete(internalRecords.preAuthUserRecords.users, uid) - - lastCleanupIdx = idx + 1 - registeredUIDs = slices.DeleteFunc(registeredUIDs, - func(u uint32) bool { return u == uid }) - wantRegisteredUsers-- - } - - if uc.simulateNameRace { - t.Logf("Dropping the registered login name %q for %d", uc.name, uid) - delete(internalRecords.preAuthUserRecords.uidByLogin, uc.name) - } - - // We don't have groups registration for the pre-check user. - continue - } - - checkUser(t, types.UserEntry{Name: uc.name, UID: uid, GID: uid}, goldenOptions...) - - if uc.simulateUIDRace { - t.Logf("Dropping the registered UID %d for %q", uid, uc.name) - delete(internalRecords.idTracker.ids, uid) - continue - } - - if uc.simulateNameRace { - // We drop the registered name to check if the logic can handle such case. - t.Logf("Dropping the registered user name %q for %d", uc.name, uid) - delete(internalRecords.idTracker.userNames, uc.name) - lastCleanupIdx = idx + 1 - continue - } - - if uc.groups == nil { - uc.groups = append(uc.groups, defaultGroupName) - } - if uc.wantGIDs == nil { - uc.wantGIDs = tc.gidsToGenerate[idx:] - } - if uc.wantGroupErr == nil { - uc.wantGroupErr = make([]bool, len(uc.groups)) - } - - numGroups := 0 - - for idx, groupName := range uc.groups { - groupName = uc.name + "+" + groupName - t.Logf("Registering group %q", groupName) - - gid, cleanup, err := records.RegisterGroupForUser(uid, groupName) - if uc.wantGroupErr[idx] { - require.Error(t, err, "RegisterGroup should return an error, but did not") - continue - } - - require.NoError(t, err, "RegisterGroup should not return an error, but did") - t.Logf("Registered group %q with GID %d", groupName, gid) - t.Cleanup(cleanup) - - isDuplicated := slices.Contains(uc.groups[:idx], groupName) - if !isDuplicated { - numGroups++ - wantRegisteredGroups++ - } - - if isDuplicated { - require.Contains(t, registeredGIDs, gid, "GID %d has been already registered!", gid) - } else { - require.NotContains(t, registeredGIDs, gid, "GID %d has not been already registered!", gid) - } - - wantGID := uc.wantGIDs[numGroups-1] - registeredGIDs = append(registeredGIDs, gid) - - require.NoError(t, err, "RegisterGroup should not return an error, but did") - require.Equal(t, wantGID, gid, "%q GID is not matching expected value", groupName) - require.Equal(t, wantRegisteredGroups, len(internalRecords.groups), - "Number of groups registered, users should be %d", wantRegisteredGroups) - - // Check that the temporary group was created - group, err := internalRecords.GroupByID(gid) - require.NoError(t, err, "GroupByID should not return an error, but did") - - groupSuffix := groupName - if idx > 0 { - groupSuffix = fmt.Sprintf("%s_%d", uc.name, idx) - } - checkGroup(t, group, - golden.WithSuffix("_"+groupSuffix)) - } - } - }) - } -} - -func TestUserByIDAndName(t *testing.T) { - t.Parallel() - - userName := "authd-temp-users-test" - uidToGenerate := uint32(12345) - - tests := map[string]struct { - registerUser bool - userAlreadyRemoved bool - byName bool - - wantErr bool - }{ - "Error_when_user_is_not_registered_-_UserByID": {wantErr: true}, - "Error_when_user_is_not_registered_-_UserByName": {byName: true, wantErr: true}, - "Error_when_user_is_already_removed_-_UserByID": { - registerUser: true, - wantErr: true, - }, - "Error_when_user_is_already_removed_-_UserByName": { - registerUser: true, - byName: true, - wantErr: true, - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - t.Parallel() - - idGeneratorMock := &idgenerator.IDGeneratorMock{UIDsToGenerate: []uint32{uidToGenerate}} - records := NewTemporaryRecords(idGeneratorMock) - - if tc.registerUser { - uid, cleanup, err := records.registerUser(userName) - require.NoError(t, err, "RegisterUser should not return an error, but did") - require.Equal(t, uidToGenerate, uid, "UID should be the one generated by the IDGenerator") - t.Cleanup(cleanup) - } - - var user types.UserEntry - var err error - if tc.byName { - user, err = records.userByName(userName) - } else { - user, err = records.userByID(uidToGenerate) - } - - if tc.wantErr { - require.Error(t, err, "UserByID should return an error, but did not") - return - } - require.NoError(t, err, "UserByID should not return an error, but did") - checkUser(t, user) - }) - } -} - -func checkUser(t *testing.T, user types.UserEntry, options ...golden.Option) { - t.Helper() - - golden.CheckOrUpdateYAML(t, user, options...) -} - -func TestMain(m *testing.M) { - log.SetLevel(log.DebugLevel) - - userslocking.Z_ForTests_OverrideLocking() - defer userslocking.Z_ForTests_RestoreLocking() - - m.Run() -} diff --git a/internal/users/tempentries/testdata/golden/TestGroupByIDAndName/Successfully_get_a_group_by_ID b/internal/users/tempentries/testdata/golden/TestGroupByIDAndName/Successfully_get_a_group_by_ID deleted file mode 100644 index e992cfed83..0000000000 --- a/internal/users/tempentries/testdata/golden/TestGroupByIDAndName/Successfully_get_a_group_by_ID +++ /dev/null @@ -1,4 +0,0 @@ -name: authd-temp-groups-test -gid: 12345 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestGroupByIDAndName/Successfully_get_a_group_by_name b/internal/users/tempentries/testdata/golden/TestGroupByIDAndName/Successfully_get_a_group_by_name deleted file mode 100644 index e992cfed83..0000000000 --- a/internal/users/tempentries/testdata/golden/TestGroupByIDAndName/Successfully_get_a_group_by_name +++ /dev/null @@ -1,4 +0,0 @@ -name: authd-temp-groups-test -gid: 12345 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUser/No_error_when_registering_a_pre-auth_user_with_the_same_name b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Panics_registering_a_pre-auth_user_again similarity index 69% rename from internal/users/tempentries/testdata/golden/TestPreAuthUser/No_error_when_registering_a_pre-auth_user_with_the_same_name rename to internal/users/tempentries/testdata/golden/TestPreAuthUser/Panics_registering_a_pre-auth_user_again index 31ef5bbe2a..a03c6001b8 100644 --- a/internal/users/tempentries/testdata/golden/TestPreAuthUser/No_error_when_registering_a_pre-auth_user_with_the_same_name +++ b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Panics_registering_a_pre-auth_user_again @@ -1,4 +1,4 @@ -name: authd-pre-auth-user-XXXXXXXX +name: authd-pre-auth-user-{RANDOM-ID} uid: 12345 gid: 12345 gecos: TestPreAuthUser diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUser/No_error_when_registering_a_pre-auth_user_with_the_same_name_TestPreAuthUser_1 b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Panics_registering_a_pre-auth_user_again_with_different_uid similarity index 69% rename from internal/users/tempentries/testdata/golden/TestPreAuthUser/No_error_when_registering_a_pre-auth_user_with_the_same_name_TestPreAuthUser_1 rename to internal/users/tempentries/testdata/golden/TestPreAuthUser/Panics_registering_a_pre-auth_user_again_with_different_uid index 31ef5bbe2a..a03c6001b8 100644 --- a/internal/users/tempentries/testdata/golden/TestPreAuthUser/No_error_when_registering_a_pre-auth_user_with_the_same_name_TestPreAuthUser_1 +++ b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Panics_registering_a_pre-auth_user_again_with_different_uid @@ -1,4 +1,4 @@ -name: authd-pre-auth-user-XXXXXXXX +name: authd-pre-auth-user-{RANDOM-ID} uid: 12345 gid: 12345 gecos: TestPreAuthUser diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Panics_registering_a_pre-auth_user_if_the_first_generated_UID_is_already_registered similarity index 69% rename from internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again rename to internal/users/tempentries/testdata/golden/TestPreAuthUser/Panics_registering_a_pre-auth_user_if_the_first_generated_UID_is_already_registered index 31ef5bbe2a..a03c6001b8 100644 --- a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again +++ b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Panics_registering_a_pre-auth_user_if_the_first_generated_UID_is_already_registered @@ -1,4 +1,4 @@ -name: authd-pre-auth-user-XXXXXXXX +name: authd-pre-auth-user-{RANDOM-ID} uid: 12345 gid: 12345 gecos: TestPreAuthUser diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user index 31ef5bbe2a..a03c6001b8 100644 --- a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user +++ b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user @@ -1,4 +1,4 @@ -name: authd-pre-auth-user-XXXXXXXX +name: authd-pre-auth-user-{RANDOM-ID} uid: 12345 gid: 12345 gecos: TestPreAuthUser diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again_TestPreAuthUser_1 b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again_TestPreAuthUser_1 deleted file mode 100644 index 31ef5bbe2a..0000000000 --- a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_again_TestPreAuthUser_1 +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: TestPreAuthUser -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_in_use b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_in_use deleted file mode 100644 index 31ef5bbe2a..0000000000 --- a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_in_use +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: TestPreAuthUser -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_registered b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_registered deleted file mode 100644 index 31ef5bbe2a..0000000000 --- a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_registered +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: TestPreAuthUser -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_registered_other-test_1 b/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_registered_other-test_1 deleted file mode 100644 index 24dee20169..0000000000 --- a/internal/users/tempentries/testdata/golden/TestPreAuthUser/Successfully_register_a_pre-auth_user_if_the_first_generated_UID_is_already_registered_other-test_1 +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12346 -gid: 12346 -gecos: other-test -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestPreAuthUserByIDAndName/Successfully_get_a_user_by_ID_and_name b/internal/users/tempentries/testdata/golden/TestPreAuthUserByIDAndName/Successfully_get_a_user_by_ID_and_name index 9f16ba80a3..6742c3e888 100644 --- a/internal/users/tempentries/testdata/golden/TestPreAuthUserByIDAndName/Successfully_get_a_user_by_ID_and_name +++ b/internal/users/tempentries/testdata/golden/TestPreAuthUserByIDAndName/Successfully_get_a_user_by_ID_and_name @@ -1,4 +1,4 @@ -name: authd-pre-auth-user-XXXXXXXX +name: authd-pre-auth-user-{RANDOM-ID} uid: 12345 gid: 12345 gecos: TestPreAuthUserByIDAndName diff --git a/internal/users/tempentries/testdata/golden/TestRegisterGroup/Error_when_registering_a_group_with_the_same_name b/internal/users/tempentries/testdata/golden/TestRegisterGroup/Error_when_registering_a_group_with_the_same_name deleted file mode 100644 index e992cfed83..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterGroup/Error_when_registering_a_group_with_the_same_name +++ /dev/null @@ -1,4 +0,0 @@ -name: authd-temp-groups-test -gid: 12345 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_register_a_group_if_the_first_generated_GID_is_already_in_use b/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_register_a_group_if_the_first_generated_GID_is_already_in_use deleted file mode 100644 index e992cfed83..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_register_a_group_if_the_first_generated_GID_is_already_in_use +++ /dev/null @@ -1,4 +0,0 @@ -name: authd-temp-groups-test -gid: 12345 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_register_a_group_if_the_first_generated_GID_is_already_registered b/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_register_a_group_if_the_first_generated_GID_is_already_registered deleted file mode 100644 index e992cfed83..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_register_a_group_if_the_first_generated_GID_is_already_registered +++ /dev/null @@ -1,4 +0,0 @@ -name: authd-temp-groups-test -gid: 12345 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_register_a_group_if_the_first_generated_GID_is_already_registered_other-group b/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_register_a_group_if_the_first_generated_GID_is_already_registered_other-group deleted file mode 100644 index 409866d28f..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_register_a_group_if_the_first_generated_GID_is_already_registered_other-group +++ /dev/null @@ -1,4 +0,0 @@ -name: other-group -gid: 12346 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_register_a_new_group b/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_register_a_new_group deleted file mode 100644 index e992cfed83..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_register_a_new_group +++ /dev/null @@ -1,4 +0,0 @@ -name: authd-temp-groups-test -gid: 12345 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_registering_a_group_with_the_same_name_returning_the_same_GID b/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_registering_a_group_with_the_same_name_returning_the_same_GID deleted file mode 100644 index e992cfed83..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_registering_a_group_with_the_same_name_returning_the_same_GID +++ /dev/null @@ -1,4 +0,0 @@ -name: authd-temp-groups-test -gid: 12345 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_registering_a_group_with_the_same_name_returning_the_same_GID_authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_registering_a_group_with_the_same_name_returning_the_same_GID_authd-temp-groups-test deleted file mode 100644 index e992cfed83..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterGroup/Successfully_registering_a_group_with_the_same_name_returning_the_same_GID_authd-temp-groups-test +++ /dev/null @@ -1,4 +0,0 @@ -name: authd-temp-groups-test -gid: 12345 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUser/Successfully_register_a_user_if_the_pre-auth_user_already_exists b/internal/users/tempentries/testdata/golden/TestRegisterUser/Successfully_register_a_user_if_the_pre-auth_user_already_exists deleted file mode 100644 index 6713d89aa7..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUser/Successfully_register_a_user_if_the_pre-auth_user_already_exists +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: authd-temp-users-test -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Error_if_there_are_no_GID_to_generate b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Error_if_there_are_no_GID_to_generate deleted file mode 100644 index 6713d89aa7..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Error_if_there_are_no_GID_to_generate +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: authd-temp-users-test -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Error_if_there_are_no_GID_to_generate_for_pre-check_user b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Error_if_there_are_no_GID_to_generate_for_pre-check_user deleted file mode 100644 index 6713d89aa7..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Error_if_there_are_no_GID_to_generate_for_pre-check_user +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: authd-temp-users-test -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_generic_user b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_generic_user deleted file mode 100644 index afa721ad15..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_generic_user +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-temp-users-test -uid: 12345 -gid: 12345 -gecos: "" -dir: "" -shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_generic_user_authd-temp-users-test+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_generic_user_authd-temp-users-test+authd-temp-groups-test deleted file mode 100644 index 8c86b68a77..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_generic_user_authd-temp-users-test+authd-temp-groups-test +++ /dev/null @@ -1,4 +0,0 @@ -name: authd-temp-users-test+authd-temp-groups-test -gid: 54321 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_pre-auth_user b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_pre-auth_user deleted file mode 100644 index 6713d89aa7..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_pre-auth_user +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: authd-temp-users-test -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users deleted file mode 100644 index c4e480f88d..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-temp-users-test1 -uid: 12345 -gid: 12345 -gecos: "" -dir: "" -shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test1+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test1+authd-temp-groups-test deleted file mode 100644 index 2f0f2a9aba..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test1+authd-temp-groups-test +++ /dev/null @@ -1,4 +0,0 @@ -name: authd-temp-users-test1+authd-temp-groups-test -gid: 54321 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test2+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test2+authd-temp-groups-test deleted file mode 100644 index 23f5f9cc6b..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test2+authd-temp-groups-test +++ /dev/null @@ -1,4 +0,0 @@ -name: authd-temp-users-test2+authd-temp-groups-test -gid: 54322 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test2_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test2_1 deleted file mode 100644 index 911bf43e39..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test2_1 +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-temp-users-test2 -uid: 12346 -gid: 12346 -gecos: "" -dir: "" -shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test3+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test3+authd-temp-groups-test deleted file mode 100644 index f4eb834fdb..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test3+authd-temp-groups-test +++ /dev/null @@ -1,4 +0,0 @@ -name: authd-temp-users-test3+authd-temp-groups-test -gid: 54323 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test3_2 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test3_2 deleted file mode 100644 index b46fc62da6..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_generic_users_authd-temp-users-test3_2 +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-temp-users-test3 -uid: 12347 -gid: 12347 -gecos: "" -dir: "" -shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_pre-auth_users b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_pre-auth_users deleted file mode 100644 index fed9e90062..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_pre-auth_users +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: authd-temp-users-test1 -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_pre-auth_users_authd-temp-users-test2_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_pre-auth_users_authd-temp-users-test2_1 deleted file mode 100644 index 0a7f178b57..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_pre-auth_users_authd-temp-users-test2_1 +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12346 -gid: 12346 -gecos: authd-temp-users-test2 -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_pre-auth_users_authd-temp-users-test3_2 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_pre-auth_users_authd-temp-users-test3_2 deleted file mode 100644 index d566a268b2..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_for_various_pre-auth_users_authd-temp-users-test3_2 +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12347 -gid: 12347 -gecos: authd-temp-users-test3 -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use deleted file mode 100644 index afa721ad15..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-temp-users-test -uid: 12345 -gid: 12345 -gecos: "" -dir: "" -shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_authd-temp-users-test+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_authd-temp-users-test+authd-temp-groups-test deleted file mode 100644 index 8c86b68a77..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_authd-temp-users-test+authd-temp-groups-test +++ /dev/null @@ -1,4 +0,0 @@ -name: authd-temp-users-test+authd-temp-groups-test -gid: 54321 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user deleted file mode 100644 index 6713d89aa7..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: authd-temp-users-test -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user_another-user-name+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user_another-user-name+authd-temp-groups-test deleted file mode 100644 index dc2f03061f..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user_another-user-name+authd-temp-groups-test +++ /dev/null @@ -1,4 +0,0 @@ -name: another-user-name+authd-temp-groups-test -gid: 54321 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user_another-user-name_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user_another-user-name_1 deleted file mode 100644 index 434285dfec..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_is_already_in_use_by_a_pre-check_user_another-user-name_1 +++ /dev/null @@ -1,6 +0,0 @@ -name: another-user-name -uid: 12346 -gid: 12346 -gecos: "" -dir: "" -shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_matches_the_user_UID b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_matches_the_user_UID deleted file mode 100644 index afa721ad15..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_matches_the_user_UID +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-temp-users-test -uid: 12345 -gid: 12345 -gecos: "" -dir: "" -shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_matches_the_user_UID_authd-temp-users-test+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_matches_the_user_UID_authd-temp-users-test+authd-temp-groups-test deleted file mode 100644 index 8c86b68a77..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_group_if_the_first_generated_GID_matches_the_user_UID_authd-temp-users-test+authd-temp-groups-test +++ /dev/null @@ -1,4 +0,0 @@ -name: authd-temp-users-test+authd-temp-groups-test -gid: 54321 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_user_if_the_first_generated_UID_is_already_in_use b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_user_if_the_first_generated_UID_is_already_in_use deleted file mode 100644 index 6713d89aa7..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_new_user_if_the_first_generated_UID_is_already_in_use +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: authd-temp-users-test -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen deleted file mode 100644 index 9cece8375e..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: racing-pre-checked-user -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_for_the_same_UID b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_for_the_same_UID deleted file mode 100644 index 9cece8375e..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_for_the_same_UID +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: racing-pre-checked-user -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_for_the_same_UID_racing-pre-checked-user_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_for_the_same_UID_racing-pre-checked-user_1 deleted file mode 100644 index 9cece8375e..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_for_the_same_UID_racing-pre-checked-user_1 +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: racing-pre-checked-user -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_racing-pre-checked-user_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_racing-pre-checked-user_1 deleted file mode 100644 index 9cece8375e..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-check_user_if_multiple_concurrent_requests_happen_racing-pre-checked-user_1 +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: racing-pre-checked-user -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_if_the_first_generated_UID_is_already_registered b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_if_the_first_generated_UID_is_already_registered deleted file mode 100644 index f6449d4f14..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_if_the_first_generated_UID_is_already_registered +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: pre-checked-user -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_if_the_first_generated_UID_is_already_registered_other-pre-checked-user_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_if_the_first_generated_UID_is_already_registered_other-pre-checked-user_1 deleted file mode 100644 index 829bf88826..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_if_the_first_generated_UID_is_already_registered_other-pre-checked-user_1 +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12346 -gid: 12346 -gecos: other-pre-checked-user -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_twice_with_the_same_UID b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_twice_with_the_same_UID deleted file mode 100644 index f6449d4f14..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_twice_with_the_same_UID +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: pre-checked-user -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_twice_with_the_same_UID_pre-checked-user_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_twice_with_the_same_UID_pre-checked-user_1 deleted file mode 100644 index f6449d4f14..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_pre-checked_user_twice_with_the_same_UID_pre-checked-user_1 +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: pre-checked-user -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user deleted file mode 100644 index afa721ad15..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-temp-users-test -uid: 12345 -gid: 12345 -gecos: "" -dir: "" -shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user_authd-temp-users-test+group-a b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user_authd-temp-users-test+group-a deleted file mode 100644 index d88cf63118..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user_authd-temp-users-test+group-a +++ /dev/null @@ -1,4 +0,0 @@ -name: authd-temp-users-test+group-a -gid: 54321 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user_authd-temp-users-test_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user_authd-temp-users-test_1 deleted file mode 100644 index e9b0e5a23b..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user_authd-temp-users-test_1 +++ /dev/null @@ -1,4 +0,0 @@ -name: authd-temp-users-test+group-b -gid: 54322 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user_authd-temp-users-test_2 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user_authd-temp-users-test_2 deleted file mode 100644 index 0c8758a449..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_generic_user_authd-temp-users-test_2 +++ /dev/null @@ -1,4 +0,0 @@ -name: authd-temp-users-test+group-c -gid: 54323 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_pre-auth_user b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_pre-auth_user deleted file mode 100644 index 6713d89aa7..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_a_various_groups_for_pre-auth_user +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: authd-temp-users-test -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks deleted file mode 100644 index f6449d4f14..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: pre-checked-user -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks_pre-checked-user+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks_pre-checked-user+authd-temp-groups-test deleted file mode 100644 index fb728da53c..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks_pre-checked-user+authd-temp-groups-test +++ /dev/null @@ -1,4 +0,0 @@ -name: pre-checked-user+authd-temp-groups-test -gid: 54321 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks_pre-checked-user_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks_pre-checked-user_1 deleted file mode 100644 index f6449d4f14..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks_pre-checked-user_1 +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: pre-checked-user -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks_pre-checked-user_2 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks_pre-checked-user_2 deleted file mode 100644 index 8bd14e83d8..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_after_two_pre_checks_pre-checked-user_2 +++ /dev/null @@ -1,6 +0,0 @@ -name: pre-checked-user -uid: 12345 -gid: 12345 -gecos: "" -dir: "" -shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen deleted file mode 100644 index 9cece8375e..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: racing-pre-checked-user -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID deleted file mode 100644 index 9cece8375e..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: racing-pre-checked-user -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID_racing-pre-checked-user+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID_racing-pre-checked-user+authd-temp-groups-test deleted file mode 100644 index a77db7210a..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID_racing-pre-checked-user+authd-temp-groups-test +++ /dev/null @@ -1,4 +0,0 @@ -name: racing-pre-checked-user+authd-temp-groups-test -gid: 54321 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID_racing-pre-checked-user_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID_racing-pre-checked-user_1 deleted file mode 100644 index 4c7f974d0f..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_for_the_same_UID_racing-pre-checked-user_1 +++ /dev/null @@ -1,6 +0,0 @@ -name: racing-pre-checked-user -uid: 12345 -gid: 12345 -gecos: "" -dir: "" -shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_racing-pre-checked-user+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_racing-pre-checked-user+authd-temp-groups-test deleted file mode 100644 index a77db7210a..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_racing-pre-checked-user+authd-temp-groups-test +++ /dev/null @@ -1,4 +0,0 @@ -name: racing-pre-checked-user+authd-temp-groups-test -gid: 54321 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_racing-pre-checked-user_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_racing-pre-checked-user_1 deleted file mode 100644 index 4c7f974d0f..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_pre-check_requests_happen_racing-pre-checked-user_1 +++ /dev/null @@ -1,6 +0,0 @@ -name: racing-pre-checked-user -uid: 12345 -gid: 12345 -gecos: "" -dir: "" -shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen deleted file mode 100644 index 4b914fec45..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen +++ /dev/null @@ -1,6 +0,0 @@ -name: racing-user -uid: 12345 -gid: 12345 -gecos: "" -dir: "" -shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID deleted file mode 100644 index 4b914fec45..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID +++ /dev/null @@ -1,6 +0,0 @@ -name: racing-user -uid: 12345 -gid: 12345 -gecos: "" -dir: "" -shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID_racing-user+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID_racing-user+authd-temp-groups-test deleted file mode 100644 index 0176b5efc7..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID_racing-user+authd-temp-groups-test +++ /dev/null @@ -1,4 +0,0 @@ -name: racing-user+authd-temp-groups-test -gid: 54321 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID_racing-user_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID_racing-user_1 deleted file mode 100644 index 4b914fec45..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_for_the_same_UID_racing-user_1 +++ /dev/null @@ -1,6 +0,0 @@ -name: racing-user -uid: 12345 -gid: 12345 -gecos: "" -dir: "" -shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_racing-user+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_racing-user+authd-temp-groups-test deleted file mode 100644 index 0176b5efc7..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_racing-user+authd-temp-groups-test +++ /dev/null @@ -1,4 +0,0 @@ -name: racing-user+authd-temp-groups-test -gid: 54321 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_racing-user_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_racing-user_1 deleted file mode 100644 index bcc33aaabd..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_multiple_concurrent_requests_happen_racing-user_1 +++ /dev/null @@ -1,6 +0,0 @@ -name: racing-user -uid: 12346 -gid: 12346 -gecos: "" -dir: "" -shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered deleted file mode 100644 index f6449d4f14..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: pre-checked-user -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_other-user+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_other-user+authd-temp-groups-test deleted file mode 100644 index 37c90f6054..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_other-user+authd-temp-groups-test +++ /dev/null @@ -1,4 +0,0 @@ -name: other-user+authd-temp-groups-test -gid: 54322 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_other-user_2 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_other-user_2 deleted file mode 100644 index 73fa2bc2d6..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_other-user_2 +++ /dev/null @@ -1,6 +0,0 @@ -name: other-user -uid: 12346 -gid: 12346 -gecos: "" -dir: "" -shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_pre-checked-user+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_pre-checked-user+authd-temp-groups-test deleted file mode 100644 index fb728da53c..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_pre-checked-user+authd-temp-groups-test +++ /dev/null @@ -1,4 +0,0 @@ -name: pre-checked-user+authd-temp-groups-test -gid: 54321 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_pre-checked-user_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_pre-checked-user_1 deleted file mode 100644 index 8bd14e83d8..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_if_the_first_generated_UID_is_already_registered_pre-checked-user_1 +++ /dev/null @@ -1,6 +0,0 @@ -name: pre-checked-user -uid: 12345 -gid: 12345 -gecos: "" -dir: "" -shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks deleted file mode 100644 index f6449d4f14..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: pre-checked-user -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user+authd-temp-groups-test b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user+authd-temp-groups-test deleted file mode 100644 index fb728da53c..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user+authd-temp-groups-test +++ /dev/null @@ -1,4 +0,0 @@ -name: pre-checked-user+authd-temp-groups-test -gid: 54321 -users: [] -passwd: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user_1 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user_1 deleted file mode 100644 index f6449d4f14..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user_1 +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12345 -gid: 12345 -gecos: pre-checked-user -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user_2 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user_2 deleted file mode 100644 index 8bd14e83d8..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user_2 +++ /dev/null @@ -1,6 +0,0 @@ -name: pre-checked-user -uid: 12345 -gid: 12345 -gecos: "" -dir: "" -shell: "" diff --git a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user_3 b/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user_3 deleted file mode 100644 index 891dd18c29..0000000000 --- a/internal/users/tempentries/testdata/golden/TestRegisterUserAndGroupForUser/Successfully_register_an_user_with_the_same_name_after_two_pre_checks_pre-checked-user_3 +++ /dev/null @@ -1,6 +0,0 @@ -name: authd-pre-auth-user-XXXXXXXX -uid: 12346 -gid: 12346 -gecos: pre-checked-user -dir: /nonexistent -shell: /usr/sbin/nologin diff --git a/internal/users/tempentries/users.go b/internal/users/tempentries/users.go deleted file mode 100644 index b6a80c3af1..0000000000 --- a/internal/users/tempentries/users.go +++ /dev/null @@ -1,97 +0,0 @@ -package tempentries - -import ( - "context" - "sync" - - "github.com/ubuntu/authd/log" -) - -type referencedUserName struct { - refCount uint64 - uid uint32 -} - -type idTracker struct { - mu sync.Mutex - ids map[uint32]struct{} - userNames map[string]*referencedUserName -} - -func newIDTracker() *idTracker { - return &idTracker{ - ids: make(map[uint32]struct{}), - userNames: make(map[string]*referencedUserName), - } -} - -func (r *idTracker) trackID(id uint32) bool { - r.mu.Lock() - defer r.mu.Unlock() - - _, ok := r.ids[id] - if ok { - log.Debugf(context.Background(), "Not tracking ID %d, already tracked", id) - return false - } - - log.Debugf(context.Background(), "Tracking ID %d", id) - r.ids[id] = struct{}{} - return true -} - -func (r *idTracker) forgetID(id uint32) { - r.mu.Lock() - defer r.mu.Unlock() - - if log.IsLevelEnabled(log.DebugLevel) { - _, ok := r.ids[id] - log.Debugf(context.Background(), - "Forgetting tracked ID %d: %v", id, ok) - } - - delete(r.ids, id) -} - -func (r *idTracker) trackUser(name string, uid uint32) (tracked bool, currentID uint32) { - r.mu.Lock() - defer r.mu.Unlock() - - if current, ok := r.userNames[name]; ok { - log.Debugf(context.Background(), - "Not tracking user name %q for UID %d, already tracked as %d", - name, uid, current.uid) - if current.uid != uid { - return false, current.uid - } - - current.refCount++ - return true, current.uid - } - - log.Debugf(context.Background(), "Tracking user name %q for UID %d", name, uid) - r.userNames[name] = &referencedUserName{uid: uid, refCount: 1} - return true, uid -} - -func (r *idTracker) forgetUser(name string) { - r.mu.Lock() - defer r.mu.Unlock() - - current, ok := r.userNames[name] - - if !ok { - log.Debugf(context.Background(), "No tracked user for %q", name) - return - } - - if current.refCount > 1 { - current.refCount-- - return - } - - log.Debugf(context.Background(), - "Forgetting tracked user name %q for UID %d", name, current.uid) - - delete(r.userNames, name) -} diff --git a/internal/users/testutils/manager.go b/internal/users/testutils/manager.go index d99370fd71..59673d6d65 100644 --- a/internal/users/testutils/manager.go +++ b/internal/users/testutils/manager.go @@ -2,6 +2,7 @@ package userstestutils import ( + "sync" "unsafe" "github.com/ubuntu/authd/internal/testsdetection" @@ -15,6 +16,8 @@ func init() { } type manager struct { + userRegisterMu sync.Mutex + db *db.Manager } From e335e0b550eb6514e03cbe4c9011f1be9c3fdde0 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 1 Jul 2025 21:39:38 +0200 Subject: [PATCH 0567/1670] users/tempentries: Add test checking username max length --- internal/users/manager_test.go | 2 +- internal/users/tempentries/preauth.go | 11 +++++++++-- internal/users/tempentries/preauth_test.go | 4 ++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/internal/users/manager_test.go b/internal/users/manager_test.go index e857a98c08..ece4fd327d 100644 --- a/internal/users/manager_test.go +++ b/internal/users/manager_test.go @@ -172,7 +172,7 @@ func TestUpdateUser(t *testing.T) { "GID_does_not_change_if_group_with_same_UGID_exists": {groupsCase: "different-name-same-ugid", dbFile: "one_user_and_group"}, "GID_does_not_change_if_group_with_same_name_and_empty_UGID_exists": {groupsCase: "authd-group", dbFile: "group-with-empty-UGID"}, "Removing_last_user_from_a_group_keeps_the_group_record": {groupsCase: "no-groups", dbFile: "one_user_and_group"}, - "Names of authd groups are stored in lowercase": {groupsCase: "authd-group-with-uppercase"}, + "Names_of_authd_groups_are_stored_in_lowercase": {groupsCase: "authd-group-with-uppercase"}, "Error_if_user_has_no_username": {userCase: "nameless", wantErr: true, noOutput: true}, "Error_if_group_has_no_name": {groupsCase: "nameless-group", wantErr: true, noOutput: true}, diff --git a/internal/users/tempentries/preauth.go b/internal/users/tempentries/preauth.go index 17f7b79199..6d717b8852 100644 --- a/internal/users/tempentries/preauth.go +++ b/internal/users/tempentries/preauth.go @@ -22,6 +22,9 @@ const ( // too long. MaxPreAuthUsers = 4096 + // MaxPreAuthUserNameLength is the maximum length of the pre-auth user name. + MaxPreAuthUserNameLength = 256 + // UserPrefix is the prefix used as login name by the pre-auth temporary users. UserPrefix = "authd-pre-auth-user" ) @@ -111,8 +114,12 @@ func (r *PreAuthUserRecords) RegisterPreAuthUser(loginName string, uid uint32) ( return fmt.Errorf("empty username") } - r.rwMu.Lock() - defer r.rwMu.Unlock() + // To mitigate DoS attacks, we limit the length of the name to + // MaxPreAuthUserNameLength] characters. + if len(loginName) > MaxPreAuthUserNameLength { + return fmt.Errorf("username is too long (maximum %d characters): %q", + MaxPreAuthUserNameLength, loginName) + } r.rwMu.Lock() defer r.rwMu.Unlock() diff --git a/internal/users/tempentries/preauth_test.go b/internal/users/tempentries/preauth_test.go index 3a22953cc1..af1a9fd65f 100644 --- a/internal/users/tempentries/preauth_test.go +++ b/internal/users/tempentries/preauth_test.go @@ -54,6 +54,10 @@ func TestPreAuthUser(t *testing.T) { users: []string{""}, wantErr: true, }, + "Error_when_login_name_exceeds_maximum_length": { + users: []string{strings.Repeat("a", MaxPreAuthUserNameLength+1)}, + wantErr: true, + }, } for name, tc := range tests { From ba9c02d36148700e8a6e5cce9d399215228581f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 1 Jul 2025 22:45:37 +0200 Subject: [PATCH 0568/1670] users/localentries: Use better naming for group files --- internal/users/localentries/export_test.go | 8 ++++---- internal/users/localentries/localgroups.go | 4 ++-- internal/users/localentries/lockedentries.go | 14 +++++++------- internal/users/localentries/testutils.go | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/internal/users/localentries/export_test.go b/internal/users/localentries/export_test.go index bf7dadfb06..69f0b7fcdc 100644 --- a/internal/users/localentries/export_test.go +++ b/internal/users/localentries/export_test.go @@ -8,22 +8,22 @@ import ( // WithGroupPath overrides the default /etc/group path for tests. func WithGroupPath(p string) Option { return func(o *options) { - o.groupInputPath = p - o.groupOutputPath = p + o.inputGroupPath = p + o.outputGroupPath = p } } // WithGroupInputPath overrides the default /etc/group path for input in tests. func WithGroupInputPath(p string) Option { return func(o *options) { - o.groupInputPath = p + o.inputGroupPath = p } } // WithGroupOutputPath overrides the default /etc/group path for output in tests. func WithGroupOutputPath(p string) Option { return func(o *options) { - o.groupOutputPath = p + o.outputGroupPath = p } } diff --git a/internal/users/localentries/localgroups.go b/internal/users/localentries/localgroups.go index db2e77c910..05f957cff1 100644 --- a/internal/users/localentries/localgroups.go +++ b/internal/users/localentries/localgroups.go @@ -198,8 +198,8 @@ func formatGroupEntries(groups []types.GroupEntry) string { } func (g *GroupsWithLock) saveLocalGroups(groups []types.GroupEntry) (err error) { - inputPath := g.l.options.groupInputPath - groupPath := g.l.options.groupOutputPath + inputPath := g.l.options.inputGroupPath + groupPath := g.l.options.outputGroupPath defer decorate.OnError(&err, "could not write local groups to %q", groupPath) diff --git a/internal/users/localentries/lockedentries.go b/internal/users/localentries/lockedentries.go index 67571330b9..9f6347adb4 100644 --- a/internal/users/localentries/lockedentries.go +++ b/internal/users/localentries/lockedentries.go @@ -16,12 +16,12 @@ import ( const GroupFile = "/etc/group" type options struct { - // groupInputPath is the path used to read the group file. Defaults to + // inputGroupPath is the path used to read the group file. Defaults to // [GroupFile], but can be overwritten in tests. - groupInputPath string - // groupOutputPath is the path used to write the group file. Defaults to + inputGroupPath string + // outputGroupPath is the path used to write the group file. Defaults to // [GroupFile], but can be overwritten in tests. - groupOutputPath string + outputGroupPath string // These are the lock and unlock functions to be used that can be overridden // for testing purposes. @@ -38,8 +38,8 @@ var defaultOptions = options{ // no test options are provided. userDBLocked: &UserDBLocked{}, - groupInputPath: GroupFile, - groupOutputPath: GroupFile, + inputGroupPath: GroupFile, + outputGroupPath: GroupFile, writeLockFunc: userslocking.WriteLock, writeUnlockFunc: userslocking.WriteUnlock, @@ -193,7 +193,7 @@ func (l *UserDBLocked) GetLocalGroupEntries() (entries []types.GroupEntry, err e return l.localGroupEntries, nil } - l.localGroupEntries, err = parseLocalGroups(l.options.groupInputPath) + l.localGroupEntries, err = parseLocalGroups(l.options.inputGroupPath) return l.localGroupEntries, err } diff --git a/internal/users/localentries/testutils.go b/internal/users/localentries/testutils.go index 2f575bb08c..0ded6241b9 100644 --- a/internal/users/localentries/testutils.go +++ b/internal/users/localentries/testutils.go @@ -35,6 +35,6 @@ func Z_ForTests_RestoreDefaultOptions() { func Z_ForTests_SetGroupPath(inputGroupPath, outputGroupPath string) { testsdetection.MustBeTesting() - defaultOptions.groupInputPath = inputGroupPath - defaultOptions.groupOutputPath = outputGroupPath + defaultOptions.inputGroupPath = inputGroupPath + defaultOptions.outputGroupPath = outputGroupPath } From f573b6fa91f007cd0ebf23bc2b92edaca1e4de85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Tue, 1 Jul 2025 23:20:09 +0200 Subject: [PATCH 0569/1670] users/localentries: Allow more invalid entries for local groups --- internal/users/localentries/localgroups.go | 41 +++++++++++++------ .../users/localentries/localgroups_test.go | 37 +++++++++-------- internal/users/localentries/lockedentries.go | 12 +++++- ...file_has_entries_with_missing_fields.group | 10 +++++ ...s_entries_with_missing_fields.group.backup | 9 ++++ ...arn_when_groups_file_has_invalid_gid.group | 6 +++ ...n_groups_file_has_invalid_gid.group.backup | 6 +++ ...roup_file_with_empty_line_is_ignored.group | 1 + ...arn_when_groups_file_has_invalid_gid.group | 6 +++ ...n_groups_file_has_invalid_gid.group.backup | 6 +++ ...alformed_file_invalid_entries_length.group | 9 ++++ 11 files changed, 113 insertions(+), 30 deletions(-) create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Warn_when_groups_file_has_entries_with_missing_fields.group create mode 100644 internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Warn_when_groups_file_has_entries_with_missing_fields.group.backup create mode 100644 internal/users/localentries/testdata/golden/TestUpdateGroups/Warn_when_groups_file_has_invalid_gid.group create mode 100644 internal/users/localentries/testdata/golden/TestUpdateGroups/Warn_when_groups_file_has_invalid_gid.group.backup create mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Warn_when_groups_file_has_invalid_gid.group create mode 100644 internal/users/localentries/testdata/golden/TestUpdatelocalentries/Warn_when_groups_file_has_invalid_gid.group.backup create mode 100644 internal/users/localentries/testdata/malformed_file_invalid_entries_length.group diff --git a/internal/users/localentries/localgroups.go b/internal/users/localentries/localgroups.go index 05f957cff1..4ece997436 100644 --- a/internal/users/localentries/localgroups.go +++ b/internal/users/localentries/localgroups.go @@ -121,35 +121,47 @@ func (g *GroupsWithLock) Update(username string, newGroups []string, oldGroups [ return g.saveLocalGroups(allGroups) } -func parseLocalGroups(groupPath string) (groups []types.GroupEntry, err error) { +func parseLocalGroups(groupPath string) (groups []types.GroupEntry, invalidEntries []invalidEntry, err error) { defer decorate.OnError(&err, "could not fetch existing local group") log.Debugf(context.Background(), "Reading groups from %q", groupPath) f, err := os.Open(groupPath) if err != nil { - return nil, err + return nil, nil, err } defer f.Close() // Format of a line composing the group file is: // group_name:password:group_id:user1,…,usern scanner := bufio.NewScanner(f) - for scanner.Scan() { - t := strings.TrimSpace(scanner.Text()) - if t == "" { + for lineNum := 0; scanner.Scan(); lineNum++ { + line := strings.TrimSpace(scanner.Text()) + if line == "" || line[0] == '#' { + invalidEntries = append(invalidEntries, + invalidEntry{lineNum: lineNum, line: line}) continue } - elems := strings.SplitN(t, ":", 4) + + elems := strings.SplitN(line, ":", 4) if len(elems) < 4 { - return nil, fmt.Errorf("malformed entry in group file (should have 4 separators, got %d): %q", len(elems), t) + log.Warningf(context.Background(), + "Malformed entry in group file (should have 4 separators, got %d): %q", + len(elems), line) + invalidEntries = append(invalidEntries, + invalidEntry{lineNum: lineNum, line: line}) + continue } name, passwd, gidValue, usersValue := elems[0], elems[1], elems[2], elems[3] gid, err := strconv.ParseUint(gidValue, 10, 0) if err != nil || gid > math.MaxUint32 { - return nil, fmt.Errorf("failed parsing entry %q, unexpected GID value", t) + log.Warningf(context.Background(), + "Failed parsing entry %q, unexpected GID value: %v", line, err) + invalidEntries = append(invalidEntries, + invalidEntry{lineNum: lineNum, line: line}) + continue } var users []string @@ -166,7 +178,7 @@ func parseLocalGroups(groupPath string) (groups []types.GroupEntry, err error) { } if err := scanner.Err(); err != nil { - return nil, err + return nil, nil, err } if err := types.ValidateGroupEntries(groups); err != nil { @@ -175,7 +187,7 @@ func parseLocalGroups(groupPath string) (groups []types.GroupEntry, err error) { groupPath, err) } - return groups, nil + return groups, invalidEntries, nil } func groupFileTemporaryPath(groupPath string) string { @@ -186,11 +198,16 @@ func groupFileBackupPath(groupPath string) string { return fmt.Sprintf("%s-", groupPath) } -func formatGroupEntries(groups []types.GroupEntry) string { +func (g *GroupsWithLock) formatGroupEntries(groups []types.GroupEntry) string { groupLines := sliceutils.Map(groups, func(group types.GroupEntry) string { return group.String() }) + for _, entry := range g.l.localGroupInvalidEntries { + groupLines = slices.Insert(groupLines, + min(entry.lineNum, len(groupLines)-1), entry.line) + } + // Add final new line to the group file. groupLines = append(groupLines, "") @@ -219,7 +236,7 @@ func (g *GroupsWithLock) saveLocalGroups(groups []types.GroupEntry) (err error) } backupPath := groupFileBackupPath(groupPath) - groupsEntries := formatGroupEntries(groups) + groupsEntries := g.formatGroupEntries(groups) log.Debugf(context.Background(), "Saving group entries %#v to %q", groups, groupPath) if len(groupsEntries) > 0 { diff --git a/internal/users/localentries/localgroups_test.go b/internal/users/localentries/localgroups_test.go index a91a681fe7..2daee4ae97 100644 --- a/internal/users/localentries/localgroups_test.go +++ b/internal/users/localentries/localgroups_test.go @@ -62,6 +62,13 @@ func TestUpdatelocalentries(t *testing.T) { groupFilePath: "malformed_file_no_group_name.group", newGroups: []string{"localgroup6"}, }, + "Warn_when_groups_file_has_missing_fields": { + groupFilePath: "malformed_file_missing_field.group", + newGroups: []string{"localgroup6"}, + }, + "Warn_when_groups_file_has_invalid_gid": { + groupFilePath: "malformed_file_invalid_gid.group", + }, // Error cases "Error_on_missing_groups_file": { @@ -73,14 +80,6 @@ func TestUpdatelocalentries(t *testing.T) { username: "no,commas,please", wantErr: true, }, - "Error_when_groups_file_has_missing_fields": { - groupFilePath: "malformed_file_missing_field.group", - wantErr: true, - }, - "Error_when_groups_file_has_invalid_gid": { - groupFilePath: "malformed_file_invalid_gid.group", - wantErr: true, - }, "Error_when_groups_file_has_a_duplicated_group": { groupFilePath: "malformed_file_duplicated.group", wantErr: true, @@ -239,21 +238,25 @@ func TestGetAndSaveLocalGroups(t *testing.T) { {Name: "localgroup5", Passwd: "x", GID: 45}, }, }, + "Warn_when_groups_file_has_entries_with_missing_fields": { + groupFilePath: "malformed_file_invalid_entries_length.group", + addGroups: []types.GroupEntry{ + {Name: "localgroup9", Passwd: "x", GID: 49}, + }, + }, "Warn_when_groups_file_has_no_group_name": { groupFilePath: "malformed_file_no_group_name.group", }, - - // Error cases - "Error_on_missing_groups_file": { - groupFilePath: "does_not_exists.group", - wantGetErr: true, - }, - "Error_when_groups_file_has_missing_fields": { + "Warn_when_groups_file_has_missing_fields": { groupFilePath: "malformed_file_missing_field.group", - wantGetErr: true, }, - "Error_when_groups_file_has_invalid_gid": { + "Warn_when_groups_file_has_invalid_gid": { groupFilePath: "malformed_file_invalid_gid.group", + }, + + // Error cases. + "Error_when_missing_groups_file": { + groupFilePath: "does_not_exists.group", wantGetErr: true, }, "Error_adding_duplicated_groups": { diff --git a/internal/users/localentries/lockedentries.go b/internal/users/localentries/lockedentries.go index 9f6347adb4..eecb110d87 100644 --- a/internal/users/localentries/lockedentries.go +++ b/internal/users/localentries/lockedentries.go @@ -48,6 +48,13 @@ var defaultOptions = options{ // Option represents an optional function to override [NewUserDBLocked] default values. type Option func(*options) +type invalidEntry struct { + // lineNum is the line number in the group file where the invalid line was found. + lineNum int + // line is the content of the invalid line. + line string +} + // UserDBLocked is a struct that holds the current users and groups while // ensuring that the system's user database is locked to prevent concurrent // modifications. @@ -73,6 +80,8 @@ type UserDBLocked struct { groupEntries []types.GroupEntry // localGroupEntries holds the current group entries. localGroupEntries []types.GroupEntry + // localGroupInvalidEntries holds the current group invalid entries. + localGroupInvalidEntries []invalidEntry // options to set the local entries context. options options @@ -193,7 +202,8 @@ func (l *UserDBLocked) GetLocalGroupEntries() (entries []types.GroupEntry, err e return l.localGroupEntries, nil } - l.localGroupEntries, err = parseLocalGroups(l.options.inputGroupPath) + l.localGroupEntries, l.localGroupInvalidEntries, err = parseLocalGroups( + l.options.inputGroupPath) return l.localGroupEntries, err } diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Warn_when_groups_file_has_entries_with_missing_fields.group b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Warn_when_groups_file_has_entries_with_missing_fields.group new file mode 100644 index 0000000000..d8d0ce3c4f --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Warn_when_groups_file_has_entries_with_missing_fields.group @@ -0,0 +1,10 @@ +localgroup1 +localgroup2: +localgroup3:x +localgroup4:x:44 +localgroup5:x:45: +localgroup6:x:46: +localgroup7:x:47: +localgroup8:x:48: +#localgroup8:x:48:with-comment +localgroup9:x:49: diff --git a/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Warn_when_groups_file_has_entries_with_missing_fields.group.backup b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Warn_when_groups_file_has_entries_with_missing_fields.group.backup new file mode 100644 index 0000000000..f60158b440 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestGetAndSaveLocalGroups/Warn_when_groups_file_has_entries_with_missing_fields.group.backup @@ -0,0 +1,9 @@ +localgroup1 +localgroup2: +localgroup3:x +localgroup4:x:44 +localgroup5:x:45: +localgroup6:x:46: +localgroup7:x:47: +localgroup8:x:48: +#localgroup8:x:48:with-comment diff --git a/internal/users/localentries/testdata/golden/TestUpdateGroups/Warn_when_groups_file_has_invalid_gid.group b/internal/users/localentries/testdata/golden/TestUpdateGroups/Warn_when_groups_file_has_invalid_gid.group new file mode 100644 index 0000000000..d8ec794f3d --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdateGroups/Warn_when_groups_file_has_invalid_gid.group @@ -0,0 +1,6 @@ +localgroup1:x:41:otheruser,myuser +localgroup2:x:42:otheruser2 +localgroup3:x:gid:invalidGID +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/golden/TestUpdateGroups/Warn_when_groups_file_has_invalid_gid.group.backup b/internal/users/localentries/testdata/golden/TestUpdateGroups/Warn_when_groups_file_has_invalid_gid.group.backup new file mode 100644 index 0000000000..5a55ebf584 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdateGroups/Warn_when_groups_file_has_invalid_gid.group.backup @@ -0,0 +1,6 @@ +localgroup1:x:41:otheruser +localgroup2:x:42:otheruser2 +localgroup3:x:gid:invalidGID +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Group_file_with_empty_line_is_ignored.group b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Group_file_with_empty_line_is_ignored.group index fc7dc2c253..3f414e74b2 100644 --- a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Group_file_with_empty_line_is_ignored.group +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Group_file_with_empty_line_is_ignored.group @@ -1,5 +1,6 @@ localgroup1:x:41:myuser localgroup2:x:42: + localgroup3:x:43:myuser localgroup4:x:44: cloudgroup1:x:9998: diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Warn_when_groups_file_has_invalid_gid.group b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Warn_when_groups_file_has_invalid_gid.group new file mode 100644 index 0000000000..d8ec794f3d --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Warn_when_groups_file_has_invalid_gid.group @@ -0,0 +1,6 @@ +localgroup1:x:41:otheruser,myuser +localgroup2:x:42:otheruser2 +localgroup3:x:gid:invalidGID +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Warn_when_groups_file_has_invalid_gid.group.backup b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Warn_when_groups_file_has_invalid_gid.group.backup new file mode 100644 index 0000000000..5a55ebf584 --- /dev/null +++ b/internal/users/localentries/testdata/golden/TestUpdatelocalentries/Warn_when_groups_file_has_invalid_gid.group.backup @@ -0,0 +1,6 @@ +localgroup1:x:41:otheruser +localgroup2:x:42:otheruser2 +localgroup3:x:gid:invalidGID +localgroup4:x:44:otheruser2 +cloudgroup1:x:9998:otheruser3 +cloudgroup2:x:9999:otheruser4 diff --git a/internal/users/localentries/testdata/malformed_file_invalid_entries_length.group b/internal/users/localentries/testdata/malformed_file_invalid_entries_length.group new file mode 100644 index 0000000000..f60158b440 --- /dev/null +++ b/internal/users/localentries/testdata/malformed_file_invalid_entries_length.group @@ -0,0 +1,9 @@ +localgroup1 +localgroup2: +localgroup3:x +localgroup4:x:44 +localgroup5:x:45: +localgroup6:x:46: +localgroup7:x:47: +localgroup8:x:48: +#localgroup8:x:48:with-comment From 0847714bbfef4f118f93d3b6d63aa11e451dd231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 2 Jul 2025 00:40:06 +0200 Subject: [PATCH 0570/1670] users/localentries: Use a context to handle the user locked entries Co-Authored-By: Adrian Dombeck --- internal/users/db/migration.go | 4 +- internal/users/localentries/localgroups.go | 3 +- .../users/localentries/localgroups_test.go | 30 +-- internal/users/localentries/lockedentries.go | 167 +++++++++++++++- .../users/localentries/lockedentries_test.go | 180 +++++++++++++++++- internal/users/manager.go | 145 ++++++-------- 6 files changed, 412 insertions(+), 117 deletions(-) diff --git a/internal/users/db/migration.go b/internal/users/db/migration.go index db04591630..1c0ac4ac75 100644 --- a/internal/users/db/migration.go +++ b/internal/users/db/migration.go @@ -248,13 +248,13 @@ func renameUsersInGroupFile(oldNames, newNames []string) (err error) { return nil } - lockedEntries, entriesUnlock, err := localentries.NewUserDBLocked() + ctx, entriesUnlock, err := localentries.ContextUserDBLocked(context.Background()) if err != nil { return err } defer func() { err = errors.Join(err, entriesUnlock()) }() - lockedGroups := localentries.GetGroupsWithLock(lockedEntries) + lockedGroups := localentries.GetGroupsWithLock(ctx) groups, err := lockedGroups.GetEntries() if err != nil { return err diff --git a/internal/users/localentries/localgroups.go b/internal/users/localentries/localgroups.go index 4ece997436..a69e4690cd 100644 --- a/internal/users/localentries/localgroups.go +++ b/internal/users/localentries/localgroups.go @@ -27,7 +27,8 @@ type GroupsWithLock struct { } // GetGroupsWithLock gets a GroupsWithLock instance with a lock on the system's user database. -func GetGroupsWithLock(entriesWithLock *UserDBLocked) (groups *GroupsWithLock) { +func GetGroupsWithLock(context context.Context) (groups *GroupsWithLock) { + entriesWithLock := GetUserDBLocked(context) entriesWithLock.MustBeLocked() return &GroupsWithLock{entriesWithLock} diff --git a/internal/users/localentries/localgroups_test.go b/internal/users/localentries/localgroups_test.go index 2daee4ae97..72aa5ff788 100644 --- a/internal/users/localentries/localgroups_test.go +++ b/internal/users/localentries/localgroups_test.go @@ -1,6 +1,7 @@ package localentries_test import ( + "context" "fmt" "os" "path/filepath" @@ -112,7 +113,8 @@ func TestUpdatelocalentries(t *testing.T) { defer localentriestestutils.RequireGroupFile(t, outputGroupFilePath, golden.Path(t)) - entries, entriesUnlock, err := localentries.NewUserDBLocked( + ctx, entriesUnlock, err := localentries.ContextUserDBLocked( + context.Background(), localentries.WithGroupInputPath(inputGroupFilePath), localentries.WithGroupOutputPath(outputGroupFilePath), localentries.WithMockUserDBLocking(), @@ -123,7 +125,7 @@ func TestUpdatelocalentries(t *testing.T) { require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") }) - lg := localentries.GetGroupsWithLock(entries) + lg := localentries.GetGroupsWithLock(ctx) err = lg.Update(tc.username, tc.newGroups, tc.oldGroups) if tc.wantErr { @@ -307,7 +309,8 @@ func TestGetAndSaveLocalGroups(t *testing.T) { defer localentriestestutils.RequireGroupFile(t, outputGroupFilePath, golden.Path(t)) - entries, entriesUnlock, err := localentries.NewUserDBLocked( + ctx, entriesUnlock, err := localentries.ContextUserDBLocked( + context.Background(), localentries.WithGroupInputPath(inputGroupFilePath), localentries.WithGroupOutputPath(outputGroupFilePath), localentries.WithMockUserDBLocking(), @@ -318,7 +321,7 @@ func TestGetAndSaveLocalGroups(t *testing.T) { require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") }() - lg := localentries.GetGroupsWithLock(entries) + lg := localentries.GetGroupsWithLock(ctx) groups, err := lg.GetEntries() if tc.wantGetErr { require.Error(t, err, "GetEntries should return an error, but did not") @@ -399,14 +402,15 @@ func TestRacingGroupsLockingActions(t *testing.T) { wantGroup = types.GroupEntry{Name: "localgroup1", GID: 41, Passwd: "x"} } - entries, entriesUnlock, err := localentries.NewUserDBLocked(opts...) + ctx, entriesUnlock, err := localentries.ContextUserDBLocked( + context.Background(), opts...) require.NoError(t, err, "Failed to lock the local entries") t.Cleanup(func() { err := entriesUnlock() require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") }) - lg := localentries.GetGroupsWithLock(entries) + lg := localentries.GetGroupsWithLock(ctx) groups, err := lg.GetEntries() require.NoError(t, err, "GetEntries should not return an error, but did") require.NotEmpty(t, groups, "Got empty groups (test groups: %v)", useTestGroupFile) @@ -419,10 +423,10 @@ func TestRacingGroupsLockingActions(t *testing.T) { wg.Wait() // Get a last unlock function, to see if we're all good... - entries, entriesUnlock, err := localentries.NewUserDBLocked() + ctx, entriesUnlock, err := localentries.ContextUserDBLocked(context.Background()) require.NoError(t, err, "Failed to lock the local entries") - lg := localentries.GetGroupsWithLock(entries) + lg := localentries.GetGroupsWithLock(ctx) require.NoError(t, err, "Unlock should not fail to lock the users group") err = entriesUnlock() @@ -436,7 +440,7 @@ func TestRacingGroupsLockingActions(t *testing.T) { func TestLockedInvalidActions(t *testing.T) { // This cannot be parallel - require.Panics(t, func() { localentries.GetGroupsWithLock((&localentries.UserDBLocked{})) }, + require.Panics(t, func() { localentries.GetGroupsWithLock(context.Background()) }, "GetGroupsWithLock should panic but did not") require.Panics(t, func() { _ = (&localentries.GroupsWithLock{}).Update("", nil, nil) }, "Update should panic but did not") @@ -445,10 +449,10 @@ func TestLockedInvalidActions(t *testing.T) { require.Panics(t, func() { _ = (&localentries.GroupsWithLock{}).SaveEntries(nil) }, "SaveEntries should panic but did not") - entries, entriesUnlock, err := localentries.NewUserDBLocked() + ctx, entriesUnlock, err := localentries.ContextUserDBLocked(context.Background()) require.NoError(t, err, "Failed to lock the local entries") - lg := localentries.GetGroupsWithLock(entries) + lg := localentries.GetGroupsWithLock(ctx) err = entriesUnlock() require.NoError(t, err, "Unlock should not fail to lock the users group") @@ -464,14 +468,14 @@ func TestLockedInvalidActions(t *testing.T) { // This is to ensure that we're in a good state, despite the actions above for range 10 { - entries, entriesUnlock, err := localentries.NewUserDBLocked() + ctx, entriesUnlock, err := localentries.ContextUserDBLocked(context.Background()) require.NoError(t, err, "Failed to lock the local entries") defer func() { err := entriesUnlock() require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") }() - lg = localentries.GetGroupsWithLock(entries) + lg = localentries.GetGroupsWithLock(ctx) } } diff --git a/internal/users/localentries/lockedentries.go b/internal/users/localentries/lockedentries.go index eecb110d87..ae3dde1cbf 100644 --- a/internal/users/localentries/lockedentries.go +++ b/internal/users/localentries/lockedentries.go @@ -2,7 +2,11 @@ package localentries import ( "context" + "errors" "fmt" + "os/user" + "slices" + "strconv" "sync" "github.com/ubuntu/authd/internal/testsdetection" @@ -45,7 +49,7 @@ var defaultOptions = options{ writeUnlockFunc: userslocking.WriteUnlock, } -// Option represents an optional function to override [NewUserDBLocked] default values. +// Option represents an optional function to override [UserDBLocked] default values. type Option func(*options) type invalidEntry struct { @@ -87,11 +91,29 @@ type UserDBLocked struct { options options } -// NewUserDBLocked gets a [UserDBLocked] instance with a lock on the system's user database. -// It returns an unlock function that should be called to unlock system's user database. -func NewUserDBLocked(args ...Option) (entries *UserDBLocked, unlock func() error, err error) { +type userDBLockKey struct{} + +// GetUserDBLocked retrieves the [WithLock] from context, if present. +func GetUserDBLocked(ctx context.Context) *UserDBLocked { + if l, ok := ctx.Value(userDBLockKey{}).(*UserDBLocked); ok { + l.MustBeLocked() + return l + } + return nil +} + +// ContextUserDBLocked gets a context instance with a lock on the system's +// user database. +// It returns an unlock function that should be called to unlock it. +// +// It can called safely multiple times and will return always a new context that +// is always bound to the same instance of [UserDBLocked] with increased +// reference counting (that is released through the unlock function). +// Use [GetUserDBLocked] to retrieve it. +func ContextUserDBLocked(parent context.Context, args ...Option) (ctx context.Context, unlock func() error, err error) { defer decorate.OnError(&err, "could not lock local groups") + var entries *UserDBLocked unlock = func() error { entries.mu.Lock() defer entries.mu.Unlock() @@ -126,13 +148,14 @@ func NewUserDBLocked(args ...Option) (entries *UserDBLocked, unlock func() error } entries = opts.userDBLocked + ctx = context.WithValue(parent, userDBLockKey{}, entries) entries.mu.Lock() defer entries.mu.Unlock() if entries.refCount != 0 { entries.refCount++ - return entries, unlock, nil + return ctx, unlock, nil } log.Debug(context.Background(), "Locking local entries") @@ -144,7 +167,7 @@ func NewUserDBLocked(args ...Option) (entries *UserDBLocked, unlock func() error entries.options = opts entries.refCount++ - return entries, unlock, nil + return ctx, unlock, nil } // MustBeLocked ensures wether the entries are locked. @@ -225,3 +248,135 @@ func (l *UserDBLocked) lockGroupFile() (unlock func()) { l.localGroupsMu.Lock() return l.localGroupsMu.Unlock } + +// IsUniqueUserName returns if a user exists for the given name. +func (l *UserDBLocked) IsUniqueUserName(name string) (unique bool, err error) { + l.MustBeLocked() + + users, err := l.GetUserEntries() + if err != nil { + return false, err + } + + // Let's try to check first the (potentially) cached entries. + if slices.ContainsFunc(users, func(u types.UserEntry) bool { + return u.Name == name + }) { + return false, nil + } + + // In case we found no user, we need to still double check NSS by name. + _, err = user.Lookup(name) + if err == nil { + return false, nil + } + + var userErr user.UnknownUserError + if !errors.As(err, &userErr) { + return false, err + } + + return true, nil +} + +// IsUniqueGroupName returns if a user exists for the given UID. +func (l *UserDBLocked) IsUniqueGroupName(group string) (unique bool, err error) { + l.MustBeLocked() + + // Let's try to check first the (potentially) cached entries. + groups, err := l.GetGroupEntries() + if err != nil { + return false, err + } + + if slices.ContainsFunc(groups, func(g types.GroupEntry) bool { + return g.Name == group + }) { + return false, nil + } + + // In case we found no user, we need to still double check NSS by name. + _, err = user.LookupGroup(group) + if err == nil { + return false, nil + } + + var groupErr user.UnknownGroupError + if !errors.As(err, &groupErr) { + return false, err + } + + return true, nil +} + +// IsUniqueUID returns if a user exists for the given UID. +func (l *UserDBLocked) IsUniqueUID(uid uint32) (unique bool, err error) { + l.MustBeLocked() + + users, err := l.GetUserEntries() + if err != nil { + return false, err + } + + // Let's try to check first the (potentially) cached entries. + if slices.ContainsFunc(users, func(u types.UserEntry) bool { + return u.UID == uid + }) { + return false, nil + } + + // In case we found no user, we need to still double check NSS by ID. + _, err = user.LookupId(strconv.FormatUint(uint64(uid), 10)) + if err == nil { + return false, nil + } + + var userErr user.UnknownUserIdError + if !errors.As(err, &userErr) { + return false, err + } + + // Also check that the UID is not used by a group. + return l.IsUniqueGID(uid) +} + +// IsUniqueGID returns if a user exists for the given UID. +func (l *UserDBLocked) IsUniqueGID(gid uint32) (unique bool, err error) { + l.MustBeLocked() + + // Let's try to check first the (potentially) cached entries. + groups, err := l.GetGroupEntries() + if err != nil { + return false, err + } + + if slices.ContainsFunc(groups, func(g types.GroupEntry) bool { + return g.GID == gid + }) { + return false, nil + } + + // Then the (potentially) cached user entries, for user local groups. + users, err := l.GetUserEntries() + if err != nil { + return false, err + } + if slices.ContainsFunc(users, func(u types.UserEntry) bool { + return u.GID == gid + }) { + return false, nil + } + + // In case we found no user, we need to still double check NSS by ID. + _, err = user.LookupGroupId(strconv.FormatUint(uint64(gid), 10)) + if err == nil { + return false, nil + } + + var groupErr user.UnknownGroupIdError + if !errors.As(err, &groupErr) { + return false, err + } + + return true, nil +} diff --git a/internal/users/localentries/lockedentries_test.go b/internal/users/localentries/lockedentries_test.go index ee3c8d32ca..2446389b3a 100644 --- a/internal/users/localentries/lockedentries_test.go +++ b/internal/users/localentries/lockedentries_test.go @@ -1,8 +1,11 @@ package localentries_test import ( + "context" + "crypto/rand" "fmt" "path/filepath" + "slices" "sync" "testing" @@ -23,7 +26,9 @@ func TestEntriesWithLockInvalidActions(t *testing.T) { require.Panics(t, func() { _, _ = (&localentries.UserDBLocked{}).GetLocalGroupEntries() }, "GetLocalGroupEntries should panic but did not") - le, unlock, err := localentries.NewUserDBLocked() + ctx, unlock, err := localentries.ContextUserDBLocked(context.Background()) + le := localentries.GetUserDBLocked(ctx) + require.NotNil(t, le, "GetWithLock should not return nil but it did") require.NoError(t, err, "Setup: failed to lock the users group") err = unlock() @@ -32,8 +37,8 @@ func TestEntriesWithLockInvalidActions(t *testing.T) { err = unlock() require.Error(t, err, "Unlocking twice should fail") - require.Panics(t, func() { le.MustBeLocked() }, - "MustBeLocked should panic but did not") + require.Panics(t, func() { _ = localentries.GetUserDBLocked(ctx) }, + "GetWithLock should panic but did not") require.Panics(t, func() { _, _ = le.GetUserEntries() }, "GetUserEntries should panic but did not") require.Panics(t, func() { _, _ = le.GetGroupEntries() }, @@ -43,7 +48,7 @@ func TestEntriesWithLockInvalidActions(t *testing.T) { // This is to ensure that we're in a good state, despite the actions above for range 10 { - le, unlock, err = localentries.NewUserDBLocked() + _, unlock, err = localentries.ContextUserDBLocked(context.Background()) require.NoError(t, err, "Failed to lock the users group") defer func() { err := unlock() @@ -83,10 +88,11 @@ func TestRacingEntriesLockingActions(t *testing.T) { wantGroup = types.GroupEntry{Name: "localgroup1", GID: 41, Passwd: "x"} } - lockedEntries, entriesUnlock, err := localentries.NewUserDBLocked(opts...) + ctx, entriesUnlock, err := localentries.ContextUserDBLocked( + context.Background(), opts...) require.NoError(t, err, "Failed to lock the local entries") - lg := localentries.GetGroupsWithLock(lockedEntries) + lg := localentries.GetGroupsWithLock(ctx) groups, err := lg.GetEntries() require.NoError(t, err, "GetEntries should not return an error, but did") require.NotEmpty(t, groups, "Got empty groups (test groups: %v)", useTestGroupFile) @@ -96,3 +102,165 @@ func TestRacingEntriesLockingActions(t *testing.T) { }) } } + +//nolint:dupl // This is not a duplicated test. +func TestIsUniqueUserName(t *testing.T) { + t.Parallel() + + ctx, unlock, err := localentries.ContextUserDBLocked(context.Background()) + require.NoError(t, err, "Setup: NewUserDBLocked should not fail to lock the users group") + + t.Cleanup(func() { + err := unlock() + require.NoError(t, err, "TearDown: Unlock should not fail, but it did") + }) + + le := localentries.GetUserDBLocked(ctx) + users, err := le.GetUserEntries() + require.NoError(t, err, "Setup: GetUserEntries should not fail, but it did") + + for _, u := range users { + t.Run(fmt.Sprintf("user_%s", u.Name), func(t *testing.T) { + t.Parallel() + + unique, err := le.IsUniqueUserName(u.Name) + require.NoError(t, err, "IsUniqueUserName should not fail, but it did") + require.False(t, unique, "IsUniqueUserName should not return true for user %q", u.Name) + + bytes := make([]byte, 16) + _, err = rand.Read(bytes) + require.NoError(t, err, "Setup: Rand should not fail, but it did") + + otherName := fmt.Sprintf("%s-%x", u.Name, bytes) + unique, err = le.IsUniqueUserName(otherName) + require.NoError(t, err, "IsUniqueUserName should not fail, but it did") + require.True(t, unique, "IsUniqueUserName should not return false for user %q", otherName) + }) + } +} + +//nolint:dupl // This is not a duplicated test. +func TestIsUniqueGroupName(t *testing.T) { + t.Parallel() + + ctx, unlock, err := localentries.ContextUserDBLocked(context.Background()) + require.NoError(t, err, "Setup: NewUserDBLocked should not fail to lock the users group") + + t.Cleanup(func() { + err := unlock() + require.NoError(t, err, "TearDown: Unlock should not fail, but it did") + }) + + le := localentries.GetUserDBLocked(ctx) + groups, err := le.GetGroupEntries() + require.NoError(t, err, "Setup: GetGroupEntries should not fail, but it did") + + for _, g := range groups { + t.Run(fmt.Sprintf("group_%s", g.Name), func(t *testing.T) { + t.Parallel() + + unique, err := le.IsUniqueGroupName(g.Name) + require.NoError(t, err, "IsUniqueGroupName should not fail, but it did") + require.False(t, unique, "IsUniqueGroupName should not return true for user %q", g.Name) + + bytes := make([]byte, 16) + _, err = rand.Read(bytes) + require.NoError(t, err, "Setup: Rand should not fail, but it did") + + otherName := fmt.Sprintf("%s-%x", g.Name, bytes) + unique, err = le.IsUniqueGroupName(otherName) + require.NoError(t, err, "IsUniqueGroupName should not fail, but it did") + require.True(t, unique, "IsUniqueGroupName should not return false for user %q", otherName) + }) + } +} + +//nolint:dupl // This is not a duplicated test. +func TestIsUniqueUID(t *testing.T) { + t.Parallel() + + ctx, unlock, err := localentries.ContextUserDBLocked(context.Background()) + require.NoError(t, err, "Setup: NewUserDBLocked should not fail to lock the users group") + + t.Cleanup(func() { + err := unlock() + require.NoError(t, err, "TearDown: Unlock should not fail, but it did") + }) + + le := localentries.GetUserDBLocked(ctx) + users, err := le.GetUserEntries() + require.NoError(t, err, "Setup: GetUserEntries should not fail, but it did") + + for _, u := range users { + t.Run(fmt.Sprintf("user_%s", u.Name), func(t *testing.T) { + t.Parallel() + + unique, err := le.IsUniqueUID(u.UID) + require.NoError(t, err, "IsUniqueUID should not fail, but it did") + require.False(t, unique, "IsUniqueUID should not return true for user %q", u.Name) + }) + } + + t.Run("at_least_an_unique_id", func(t *testing.T) { + t.Parallel() + + maxUIDUser := slices.MaxFunc(users, func(a types.UserEntry, b types.UserEntry) int { + return int(max(a.UID, b.UID)) + }) + + // This has to return one day... + for uid := maxUIDUser.UID; ; uid++ { + unique, err := le.IsUniqueUID(uid) + require.NoError(t, err, "IsUniqueUID should not fail, but it did") + if unique { + t.Logf("Found unique ID %d", uid) + break + } + } + }) +} + +//nolint:dupl // This is not a duplicated test. +func TestIsUniqueGID(t *testing.T) { + t.Parallel() + + ctx, unlock, err := localentries.ContextUserDBLocked(context.Background()) + require.NoError(t, err, "Setup: NewUserDBLocked should not fail to lock the users group") + + t.Cleanup(func() { + err := unlock() + require.NoError(t, err, "TearDown: Unlock should not fail, but it did") + }) + + le := localentries.GetUserDBLocked(ctx) + groups, err := le.GetGroupEntries() + require.NoError(t, err, "Setup: GetUserEntries should not fail, but it did") + + for _, g := range groups { + t.Run(fmt.Sprintf("group_%s", g.Name), func(t *testing.T) { + t.Parallel() + + unique, err := le.IsUniqueGID(g.GID) + require.NoError(t, err, "IsUniqueGID should not fail, but it did") + require.False(t, unique, "IsUniqueGID should not return true for user %q", g.Name) + }) + } + + t.Run("at_least_an_unique_id", func(t *testing.T) { + t.Parallel() + + maxGIDGroup := slices.MaxFunc(groups, func(a types.GroupEntry, b types.GroupEntry) int { + return int(max(a.GID, b.GID)) + }) + + // This has to return one day... + for gid := maxGIDGroup.GID; ; gid++ { + unique, err := le.IsUniqueGID(gid) + require.NoError(t, err, "IsUniqueGID should not fail, but it did") + if unique { + t.Logf("Found unique ID %d", gid) + break + } + } + }) +} diff --git a/internal/users/manager.go b/internal/users/manager.go index ae687a4827..e158b54fde 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -47,9 +47,6 @@ type Manager struct { config Config preAuthRecords *tempentries.PreAuthUserRecords idGenerator tempentries.IDGenerator - - userEntries []types.UserEntry - groupEntries []types.GroupEntry } type options struct { @@ -153,18 +150,22 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { uid = preauthUID defer cleanup() } else { - unlockEntries, err := m.cacheLockedEntries() + ctx, unlockEntries, err := localentries.ContextUserDBLocked(context.Background()) if err != nil { return err } defer func() { err = errors.Join(err, unlockEntries()) }() - if !m.isUniqueSystemUserName(u.Name) { - log.Warningf(context.Background(), "Another user exists with name %q", u.Name) + unique, err := localentries.GetUserDBLocked(ctx).IsUniqueUserName(u.Name) + if err != nil { + return err + } + if !unique { + log.Warningf(context.Background(), "User %q already exists", u.Name) return fmt.Errorf("another system user exists with %q name", u.Name) } - if uid, err = m.generateUniqueUID(); err != nil { + if uid, err = m.generateUniqueUID(ctx); err != nil { return err } log.Debugf(context.Background(), "Using new UID %d for user %q", uid, u.Name) @@ -222,12 +223,14 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { } if len(newGroups) > 0 { - unlockEntries, err := m.cacheLockedEntries() + ctx, unlockEntries, err := localentries.ContextUserDBLocked(context.Background()) if err != nil { return err } defer func() { err = errors.Join(err, unlockEntries()) }() + lockedEntries := localentries.GetUserDBLocked(ctx) + for i, j := 0, 0; i < len(newGroups); j++ { g := &newGroups[i] if j >= maxIDGenerateIterations { @@ -235,7 +238,16 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { g.Name, maxIDGenerateIterations) } - gid, err := m.generateUniqueGID() + unique, err := lockedEntries.IsUniqueGroupName(g.Name) + if err != nil { + return err + } + if !unique { + log.Warningf(ctx, "Group %q already exists", g.Name) + return fmt.Errorf("another system group exists with %q name", g.Name) + } + + gid, err := m.generateUniqueGID(ctx) if err != nil { return err } @@ -248,14 +260,9 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { continue } - if !m.isUniqueSystemGroupName(g.Name) { - log.Warningf(context.Background(), "Group %q already exists", g.Name) - return fmt.Errorf("another system group exists with %q name", g.Name) - } - g.GID = &gid groupRows = append(groupRows, db.NewGroupRow(g.Name, *g.GID, g.UGID)) - log.Debugf(context.Background(), "Using new GID %d for group %q", gid, u.Name) + log.Debugf(ctx, "Using new GID %d for group %q", gid, u.Name) i++ } } @@ -273,13 +280,13 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { } // Update local groups. - localEntries, unlockEntries, err := localentries.NewUserDBLocked() + ctx, unlockEntries, err := localentries.ContextUserDBLocked(context.Background()) if err != nil { return err } defer func() { err = errors.Join(err, unlockEntries()) }() - lockedGroups := localentries.GetGroupsWithLock(localEntries) + lockedGroups := localentries.GetGroupsWithLock(ctx) if err := lockedGroups.Update(u.Name, localGroups, oldLocalGroups); err != nil { return err } @@ -482,7 +489,7 @@ func (m *Manager) AllShadows() ([]types.ShadowEntry, error) { return shadowEntries, err } -func (m *Manager) generateUniqueID(generator func() (uint32, error)) (uint32, error) { +func (m *Manager) generateUniqueID(ctx context.Context, generator func() (uint32, error)) (uint32, error) { for range maxIDGenerateIterations { uid, err := generator() if err != nil { @@ -490,12 +497,12 @@ func (m *Manager) generateUniqueID(generator func() (uint32, error)) (uint32, er } var unique bool - unique, err = m.isUniqueID(uid) + unique, err = m.isUniqueID(ctx, uid) if err != nil { return 0, err } if !unique { - log.Debugf(context.Background(), "UID %d is not unique", uid) + log.Debugf(ctx, "UID %d is not unique", uid) continue } @@ -505,18 +512,18 @@ func (m *Manager) generateUniqueID(generator func() (uint32, error)) (uint32, er return 0, fmt.Errorf("Cannot find an unique ID") } -func (m *Manager) generateUniqueUID() (uint32, error) { - return m.generateUniqueID(m.idGenerator.GenerateUID) +func (m *Manager) generateUniqueUID(ctx context.Context) (uint32, error) { + return m.generateUniqueID(ctx, m.idGenerator.GenerateUID) } -func (m *Manager) generateUniqueGID() (uint32, error) { - return m.generateUniqueID(m.idGenerator.GenerateGID) +func (m *Manager) generateUniqueGID(ctx context.Context) (uint32, error) { + return m.generateUniqueID(ctx, m.idGenerator.GenerateGID) } -func (m *Manager) isUniqueID(id uint32) (bool, error) { +func (m *Manager) isUniqueID(ctx context.Context, id uint32) (bool, error) { oldUser, err := m.UserByID(id) if err == nil { - log.Debugf(context.TODO(), "Found duplicate user %v", oldUser) + log.Debugf(ctx, "Found duplicate user %v", oldUser) return false, nil } if !errors.Is(err, db.NoDataFoundError{}) { @@ -525,88 +532,44 @@ func (m *Manager) isUniqueID(id uint32) (bool, error) { oldGroup, err := m.GroupByID(id) if err == nil { - log.Debugf(context.TODO(), "Found duplicate group %v", oldGroup) + log.Debugf(ctx, "Found duplicate group %v", oldGroup) return false, nil } if !errors.Is(err, db.NoDataFoundError{}) { return false, err } - return m.isUniqueSystemID(id) + return m.isUniqueSystemID(ctx, id) } -func (m *Manager) isUniqueSystemID(id uint32) (unique bool, err error) { - if m.userEntries == nil { - panic("User entries have note ben set") - } - if m.groupEntries == nil { - panic("Group entries have note ben set") +func (m *Manager) isUniqueSystemID(ctx context.Context, id uint32) (unique bool, err error) { + users, err := localentries.GetUserDBLocked(ctx).GetUserEntries() + if err != nil { + return false, err } - if idx := slices.IndexFunc(m.userEntries, func(p types.UserEntry) (found bool) { + if idx := slices.IndexFunc(users, func(p types.UserEntry) (found bool) { return p.UID == id }); idx != -1 { - log.Debugf(context.Background(), "ID %d already in use by user %q", - id, m.userEntries[idx].Name) + log.Debugf(ctx, "ID %d already in use by user %q", id, users[idx].Name) return false, nil } - if idx := slices.IndexFunc(m.groupEntries, func(g types.GroupEntry) (found bool) { + groups, err := localentries.GetUserDBLocked(ctx).GetGroupEntries() + if err != nil { + return false, err + } + + if idx := slices.IndexFunc(groups, func(g types.GroupEntry) (found bool) { return g.GID == id }); idx != -1 { - log.Debugf(context.Background(), "ID %d already in use by user %q", - id, m.groupEntries[idx].Name) + log.Debugf(ctx, "ID %d already in use by user %q", id, groups[idx].Name) return false, nil } return true, nil } -func (m *Manager) cacheLockedEntries() (unlockEntries func() error, err error) { - if m.userEntries != nil && m.groupEntries != nil { - return func() error { return nil }, nil - } - - localEntries, unlock, err := localentries.NewUserDBLocked() - if err != nil { - return nil, err - } - - m.userEntries, err = localEntries.GetUserEntries() - if err != nil { - return nil, err - } - - m.groupEntries, err = localEntries.GetGroupEntries() - if err != nil { - return nil, err - } - - return func() error { - m.userEntries = nil - m.groupEntries = nil - return unlock() - }, nil -} - -func (m *Manager) isUniqueSystemUserName(name string) (unique bool) { - if m.userEntries == nil { - panic("User entries are not set!") - } - return !slices.ContainsFunc(m.userEntries, func(g types.UserEntry) bool { - return g.Name == name - }) -} - -func (m *Manager) isUniqueSystemGroupName(name string) (unique bool) { - if m.groupEntries == nil { - panic("Group entries are not set!") - } - return !slices.ContainsFunc(m.groupEntries, func(g types.GroupEntry) bool { - return g.Name == name - }) -} - // RegisterUserPreAuth registers a temporary user with a unique UID in our NSS handler (in memory, not in the database). // // The temporary user record is removed when UpdateUser is called with the same username. @@ -630,17 +593,21 @@ func (m *Manager) RegisterUserPreAuth(name string) (uid uint32, err error) { return 0, err } - unlockEntries, err := m.cacheLockedEntries() + ctx, unlockEntries, err := localentries.ContextUserDBLocked(context.Background()) if err != nil { return 0, err } defer func() { err = errors.Join(err, unlockEntries()) }() - if !m.isUniqueSystemUserName(name) { + unique, err := localentries.GetUserDBLocked(ctx).IsUniqueUserName(name) + if err != nil { + return 0, err + } + if !unique { return 0, fmt.Errorf("another system user exists with %q name", name) } - uid, err = m.generateUniqueUID() + uid, err = m.generateUniqueUID(ctx) if err != nil { return 0, err } @@ -649,6 +616,6 @@ func (m *Manager) RegisterUserPreAuth(name string) (uid uint32, err error) { return 0, err } - log.Debugf(context.Background(), "Using new UID %d for temporary user %q", uid, name) + log.Debugf(ctx, "Using new UID %d for temporary user %q", uid, name) return uid, nil } From 633964742a545366fe7fd9a56fe67e7520331d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 2 Jul 2025 01:25:38 +0200 Subject: [PATCH 0571/1670] users/localentries: Unexport the internal implementations to get the entries We want the entries to be checked always with a lock, so do not export the non-locked versions since they are not used anywhere at this point a part from tests --- internal/users/localentries/getgrent_c.go | 4 ++-- internal/users/localentries/getgrent_test.go | 2 +- internal/users/localentries/getpwent_c.go | 4 ++-- internal/users/localentries/getpwent_test.go | 2 +- internal/users/localentries/lockedentries.go | 4 ++-- internal/users/manager_test.go | 22 ++++++++++++++++---- 6 files changed, 26 insertions(+), 12 deletions(-) diff --git a/internal/users/localentries/getgrent_c.go b/internal/users/localentries/getgrent_c.go index 8759d592d9..ce124bb5fa 100644 --- a/internal/users/localentries/getgrent_c.go +++ b/internal/users/localentries/getgrent_c.go @@ -24,8 +24,8 @@ import ( // types.GroupEntry represents a group entry. var getgrentMu sync.Mutex -// GetGroupEntries returns all group entries. -func GetGroupEntries() (entries []types.GroupEntry, err error) { +// getGroupEntries returns all group entries. +func getGroupEntries() (entries []types.GroupEntry, err error) { decorate.OnError(&err, "getgrent_r") // This function repeatedly calls getgrent_r, which iterates over the records in the group database. diff --git a/internal/users/localentries/getgrent_test.go b/internal/users/localentries/getgrent_test.go index 02139019b9..f157a593e3 100644 --- a/internal/users/localentries/getgrent_test.go +++ b/internal/users/localentries/getgrent_test.go @@ -17,7 +17,7 @@ func TestGetGroupEntries(t *testing.T) { t.Run(fmt.Sprintf("iteration_%d", idx), func(t *testing.T) { t.Parallel() - got, err := GetGroupEntries() + got, err := getGroupEntries() require.NoError(t, err, "GetGroupEntries should never return an error") require.NotEmpty(t, got, "GetGroupEntries should never return an empty list") require.True(t, slices.ContainsFunc(got, func(g types.GroupEntry) bool { diff --git a/internal/users/localentries/getpwent_c.go b/internal/users/localentries/getpwent_c.go index 54731c9382..d41665e997 100644 --- a/internal/users/localentries/getpwent_c.go +++ b/internal/users/localentries/getpwent_c.go @@ -21,8 +21,8 @@ import ( var getpwentMu sync.Mutex -// GetPasswdEntries returns all passwd entries. -func GetPasswdEntries() (entries []types.UserEntry, err error) { +// getUserEntries returns all passwd entries. +func getUserEntries() (entries []types.UserEntry, err error) { decorate.OnError(&err, "getpwent_r") // This function repeatedly calls getpwent_r, which iterates over the records in the passwd database. diff --git a/internal/users/localentries/getpwent_test.go b/internal/users/localentries/getpwent_test.go index 8d6434b80e..a147cc9069 100644 --- a/internal/users/localentries/getpwent_test.go +++ b/internal/users/localentries/getpwent_test.go @@ -16,7 +16,7 @@ func TestGetPasswdEntries(t *testing.T) { t.Run(fmt.Sprintf("iteration_%d", idx), func(t *testing.T) { t.Parallel() - got, err := GetPasswdEntries() + got, err := getUserEntries() require.NoError(t, err, "GetPasswdEntries should never return an error") require.NotEmpty(t, got, "GetPasswdEntries should never return an empty list") diff --git a/internal/users/localentries/lockedentries.go b/internal/users/localentries/lockedentries.go index ae3dde1cbf..cce22aa9a2 100644 --- a/internal/users/localentries/lockedentries.go +++ b/internal/users/localentries/lockedentries.go @@ -195,7 +195,7 @@ func (l *UserDBLocked) GetUserEntries() (entries []types.UserEntry, err error) { return l.userEntries, nil } - l.userEntries, err = GetPasswdEntries() + l.userEntries, err = getUserEntries() return l.userEntries, err } @@ -210,7 +210,7 @@ func (l *UserDBLocked) GetGroupEntries() (entries []types.GroupEntry, err error) return l.groupEntries, nil } - l.groupEntries, err = GetGroupEntries() + l.groupEntries, err = getGroupEntries() return l.groupEntries, err } diff --git a/internal/users/manager_test.go b/internal/users/manager_test.go index ece4fd327d..1f7dec024e 100644 --- a/internal/users/manager_test.go +++ b/internal/users/manager_test.go @@ -354,11 +354,17 @@ func TestConcurrentUserUpdate(t *testing.T) { const registeredUserPrefix = "authd-test-maybe-pre-check-user" - systemPasswd, err := localentries.GetPasswdEntries() + ctx, entriesUnlock, err := localentries.ContextUserDBLocked(context.Background()) + require.NoError(t, err, "Failed to lock the local entries") + lockedEntries := localentries.GetUserDBLocked(ctx) + systemPasswd, err := lockedEntries.GetUserEntries() require.NoError(t, err, "GetPasswdEntries should not fail but it did") - systemGroups, err := localentries.GetGroupEntries() + systemGroups, err := lockedEntries.GetGroupEntries() require.NoError(t, err, "GetGroupEntries should not fail but it did") + err = entriesUnlock() + require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") + idGenerator := &idgenerator.IDGenerator{ UIDMin: 0, //nolint: gosec // we're in tests, overflow is very unlikely to happen. @@ -503,9 +509,17 @@ func TestConcurrentUserUpdate(t *testing.T) { require.NoError(t, err, "AllGroups should not fail but it did") require.Len(t, groups, nIterations*3+1, "Number of registered groups mismatch") - localPasswd, err := localentries.GetPasswdEntries() + ctx, entriesUnlock, err := localentries.ContextUserDBLocked(context.Background()) + require.NoError(t, err, "Failed to lock the local entries") + defer func() { + err := entriesUnlock() + require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") + }() + + lockedEntries := localentries.GetUserDBLocked(ctx) + localPasswd, err := lockedEntries.GetUserEntries() require.NoError(t, err, "GetPasswdEntries should not fail but it did") - localGroups, err := localentries.GetGroupEntries() + localGroups, err := lockedEntries.GetGroupEntries() require.NoError(t, err, "GetGroupEntries should not fail but it did") uniqueUIDs := make(map[uint32]types.UserEntry) From da89bfcdf1ea1377d189b51d7405766c38bf363b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 2 Jul 2025 01:37:26 +0200 Subject: [PATCH 0572/1670] users/localentries: Allow to manage local groups only through locked context Simplify the logic by just requiring a context to do localgroups operations, the context although must contain locked entries. --- internal/users/db/migration.go | 5 +- internal/users/localentries/export_test.go | 6 +- internal/users/localentries/localgroups.go | 95 ++++++++----------- .../users/localentries/localgroups_test.go | 47 +++++---- .../users/localentries/lockedentries_test.go | 3 +- internal/users/manager.go | 3 +- 6 files changed, 72 insertions(+), 87 deletions(-) diff --git a/internal/users/db/migration.go b/internal/users/db/migration.go index 1c0ac4ac75..aa30291637 100644 --- a/internal/users/db/migration.go +++ b/internal/users/db/migration.go @@ -254,8 +254,7 @@ func renameUsersInGroupFile(oldNames, newNames []string) (err error) { } defer func() { err = errors.Join(err, entriesUnlock()) }() - lockedGroups := localentries.GetGroupsWithLock(ctx) - groups, err := lockedGroups.GetEntries() + groups, err := localentries.GetGroupEntries(ctx) if err != nil { return err } @@ -269,7 +268,7 @@ func renameUsersInGroupFile(oldNames, newNames []string) (err error) { } } - return lockedGroups.SaveEntries(groups) + return localentries.SaveGroupEntries(ctx, groups) } func removeGroupsWithNameConflicts(db queryable) error { diff --git a/internal/users/localentries/export_test.go b/internal/users/localentries/export_test.go index 69f0b7fcdc..6697371ba3 100644 --- a/internal/users/localentries/export_test.go +++ b/internal/users/localentries/export_test.go @@ -1,6 +1,8 @@ package localentries import ( + "context" + userslocking "github.com/ubuntu/authd/internal/users/locking" "github.com/ubuntu/authd/internal/users/types" ) @@ -51,6 +53,6 @@ func GroupFileBackupPath(groupFilePath string) string { } // ValidateChangedGroups validates the new groups given the current, changed and new groups. -func ValidateChangedGroups(currentGroups, newGroups []types.GroupEntry) error { - return validateChangedGroups(currentGroups, newGroups) +func ValidateChangedGroups(ctx context.Context, currentGroups, newGroups []types.GroupEntry) error { + return validateChangedGroups(ctx, currentGroups, newGroups) } diff --git a/internal/users/localentries/localgroups.go b/internal/users/localentries/localgroups.go index a69e4690cd..24a648b97b 100644 --- a/internal/users/localentries/localgroups.go +++ b/internal/users/localentries/localgroups.go @@ -19,33 +19,18 @@ import ( "github.com/ubuntu/decorate" ) -// GroupsWithLock is a struct that holds the current user groups and provides methods to -// retrieve and update them while ensuring that the system's user database is locked -// to prevent concurrent modifications. -type GroupsWithLock struct { - l *UserDBLocked -} - -// GetGroupsWithLock gets a GroupsWithLock instance with a lock on the system's user database. -func GetGroupsWithLock(context context.Context) (groups *GroupsWithLock) { - entriesWithLock := GetUserDBLocked(context) - entriesWithLock.MustBeLocked() - - return &GroupsWithLock{entriesWithLock} -} - -// GetEntries returns a copy of the current group entries. -func (g *GroupsWithLock) GetEntries() (entries []types.GroupEntry, err error) { +// GetGroupEntries returns a copy of the current group entries. +func GetGroupEntries(ctx context.Context) (entries []types.GroupEntry, err error) { defer decorate.OnError(&err, "could not get groups") - unlock := g.l.lockGroupFile() + unlock := GetUserDBLocked(ctx).lockGroupFile() defer unlock() - return g.getEntries() + return getGroupEntriesWithContext(ctx) } -func (g *GroupsWithLock) getEntries() (entries []types.GroupEntry, err error) { - entries, err = g.l.GetLocalGroupEntries() +func getGroupEntriesWithContext(ctx context.Context) (entries []types.GroupEntry, err error) { + entries, err = GetUserDBLocked(ctx).GetLocalGroupEntries() if err != nil { return nil, err } @@ -53,27 +38,31 @@ func (g *GroupsWithLock) getEntries() (entries []types.GroupEntry, err error) { return types.DeepCopyGroupEntries(entries), nil } -// SaveEntries saves the provided group entries to the local group file. -func (g *GroupsWithLock) SaveEntries(entries []types.GroupEntry) (err error) { +// SaveGroupEntries saves the provided group entries to the local group file. +func SaveGroupEntries(ctx context.Context, entries []types.GroupEntry) (err error) { defer decorate.OnError(&err, "could not save groups") - unlock := g.l.lockGroupFile() + unlock := GetUserDBLocked(ctx).lockGroupFile() defer unlock() - return g.saveLocalGroups(entries) + return saveLocalGroups(ctx, entries) } -// Update updates the local groups for a user, adding them to the groups in +// UpdateGroups updates the local groups for a user, adding them to the groups in // newGroups which they are not already part of, and removing them from the // groups in oldGroups which are not in newGroups. -func (g *GroupsWithLock) Update(username string, newGroups []string, oldGroups []string) (err error) { - log.Debugf(context.TODO(), "Updating local groups for user %q, new groups: %v, old groups: %v", username, newGroups, oldGroups) +func UpdateGroups(ctx context.Context, username string, newGroups []string, oldGroups []string) (err error) { + log.Debugf(ctx, "Updating local groups for user %q, new groups: %v, old groups: %v", username, newGroups, oldGroups) defer decorate.OnError(&err, "could not update local groups for user %q", username) - unlock := g.l.lockGroupFile() + unlock := GetUserDBLocked(ctx).lockGroupFile() defer unlock() - allGroups, err := g.getEntries() + if len(newGroups) == 0 && len(oldGroups) == 0 { + return nil + } + + allGroups, err := getGroupEntriesWithContext(ctx) if err != nil { return err } @@ -84,11 +73,11 @@ func (g *GroupsWithLock) Update(username string, newGroups []string, oldGroups [ }) groupsToAdd := sliceutils.Difference(newGroups, currentGroupsNames) - log.Debugf(context.TODO(), "Adding %q to local groups: %v", username, groupsToAdd) + log.Debugf(ctx, "Adding %q to local groups: %v", username, groupsToAdd) groupsToRemove := sliceutils.Difference(oldGroups, newGroups) // Only remove user from groups which they are part of groupsToRemove = sliceutils.Intersection(groupsToRemove, currentGroupsNames) - log.Debugf(context.TODO(), "Removing %q from local groups: %v", username, groupsToRemove) + log.Debugf(ctx, "Removing %q from local groups: %v", username, groupsToRemove) if len(groupsToRemove) == 0 && len(groupsToAdd) == 0 { return nil @@ -119,7 +108,7 @@ func (g *GroupsWithLock) Update(username string, newGroups []string, oldGroups [ group.Users = append(group.Users, username) } - return g.saveLocalGroups(allGroups) + return saveLocalGroups(ctx, allGroups) } func parseLocalGroups(groupPath string) (groups []types.GroupEntry, invalidEntries []invalidEntry, err error) { @@ -199,12 +188,12 @@ func groupFileBackupPath(groupPath string) string { return fmt.Sprintf("%s-", groupPath) } -func (g *GroupsWithLock) formatGroupEntries(groups []types.GroupEntry) string { +func formatGroupEntries(ctx context.Context, groups []types.GroupEntry) string { groupLines := sliceutils.Map(groups, func(group types.GroupEntry) string { return group.String() }) - for _, entry := range g.l.localGroupInvalidEntries { + for _, entry := range GetUserDBLocked(ctx).localGroupInvalidEntries { groupLines = slices.Insert(groupLines, min(entry.lineNum, len(groupLines)-1), entry.line) } @@ -215,42 +204,43 @@ func (g *GroupsWithLock) formatGroupEntries(groups []types.GroupEntry) string { return strings.Join(groupLines, "\n") } -func (g *GroupsWithLock) saveLocalGroups(groups []types.GroupEntry) (err error) { - inputPath := g.l.options.inputGroupPath - groupPath := g.l.options.outputGroupPath +func saveLocalGroups(ctx context.Context, groups []types.GroupEntry) (err error) { + lockedEntries := GetUserDBLocked(ctx) + inputPath := lockedEntries.options.inputGroupPath + groupPath := lockedEntries.options.outputGroupPath defer decorate.OnError(&err, "could not write local groups to %q", groupPath) - currentGroups, err := g.getEntries() + currentGroups, err := getGroupEntriesWithContext(ctx) if err != nil { return err } if slices.EqualFunc(currentGroups, groups, types.GroupEntry.Equals) { - log.Debugf(context.Background(), "Nothing to do, groups are equal") + log.Debugf(ctx, "Nothing to do, groups are equal") return nil } - if err := validateChangedGroups(currentGroups, groups); err != nil { - log.Debugf(context.Background(), "New groups are not valid: %v", err) + if err := validateChangedGroups(ctx, currentGroups, groups); err != nil { + log.Debugf(ctx, "New groups are not valid: %v", err) return err } backupPath := groupFileBackupPath(groupPath) - groupsEntries := g.formatGroupEntries(groups) + groupsEntries := formatGroupEntries(ctx, groups) - log.Debugf(context.Background(), "Saving group entries %#v to %q", groups, groupPath) + log.Debugf(ctx, "Saving group entries %#v to %q", groups, groupPath) if len(groupsEntries) > 0 { - log.Debugf(context.Background(), "Group file content:\n%s", groupsEntries) + log.Debugf(ctx, "Group file content:\n%s", groupsEntries) } if err := os.Remove(backupPath); err != nil && !errors.Is(err, os.ErrNotExist) { - log.Warningf(context.Background(), "Failed to remove group file backup: %v", err) + log.Warningf(ctx, "Failed to remove group file backup: %v", err) } - log.Debugf(context.Background(), "Backing up %q to %q", inputPath, backupPath) + log.Debugf(ctx, "Backing up %q to %q", inputPath, backupPath) if err := fileutils.CopyFile(inputPath, backupPath); err != nil { - log.Warningf(context.Background(), "Failed make a backup for the group file: %v", err) + log.Warningf(ctx, "Failed make a backup for the group file: %v", err) } tempPath := groupFileTemporaryPath(groupPath) @@ -263,20 +253,19 @@ func (g *GroupsWithLock) saveLocalGroups(groups []types.GroupEntry) (err error) return fmt.Errorf("error renaming %s to %s: %w", tempPath, groupPath, err) } - g.l.updateLocalGroupEntriesCache(groups) + lockedEntries.updateLocalGroupEntriesCache(groups) return nil } -func validateChangedGroups(currentGroups, newGroups []types.GroupEntry) error { +func validateChangedGroups(ctx context.Context, currentGroups, newGroups []types.GroupEntry) error { changedGroups := sliceutils.DifferenceFunc(newGroups, currentGroups, types.GroupEntry.Equals) if len(changedGroups) == 0 { - log.Debugf(context.Background(), "No new groups added to validate") + log.Debugf(ctx, "No new groups added to validate") return nil } - log.Debugf(context.Background(), "Groups added or modified: %#v", - changedGroups) + log.Debugf(ctx, "Groups added or modified: %#v", changedGroups) if err := types.ValidateGroupEntries(changedGroups); err != nil { // One of the group that has been changed is not valid. diff --git a/internal/users/localentries/localgroups_test.go b/internal/users/localentries/localgroups_test.go index 72aa5ff788..ffef3bd83a 100644 --- a/internal/users/localentries/localgroups_test.go +++ b/internal/users/localentries/localgroups_test.go @@ -125,9 +125,7 @@ func TestUpdatelocalentries(t *testing.T) { require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") }) - lg := localentries.GetGroupsWithLock(ctx) - - err = lg.Update(tc.username, tc.newGroups, tc.oldGroups) + err = localentries.UpdateGroups(ctx, tc.username, tc.newGroups, tc.oldGroups) if tc.wantErr { require.Error(t, err, "Updatelocalentries should have failed") } else { @@ -321,8 +319,7 @@ func TestGetAndSaveLocalGroups(t *testing.T) { require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") }() - lg := localentries.GetGroupsWithLock(ctx) - groups, err := lg.GetEntries() + groups, err := localentries.GetGroupEntries(ctx) if tc.wantGetErr { require.Error(t, err, "GetEntries should return an error, but did not") return @@ -341,10 +338,10 @@ func TestGetAndSaveLocalGroups(t *testing.T) { groups[idx].Users = append(groups[idx].Users, userNames...) } - err = lg.SaveEntries(groups) + err = localentries.SaveGroupEntries(ctx, groups) if tc.wantSetErr { require.Error(t, err, "SaveEntries should have failed") - updatedGroups, err := lg.GetEntries() + updatedGroups, err := localentries.GetGroupEntries(ctx) require.NoError(t, err, "GetEntries should not return an error, but did") require.Equal(t, initialGroups, updatedGroups, "Cached groups have been changed") return @@ -356,7 +353,7 @@ func TestGetAndSaveLocalGroups(t *testing.T) { require.NoError(t, err, "SaveEntries should not have failed") // Ensure we also saved the cached version of the groups... - updatedGroups, err := lg.GetEntries() + updatedGroups, err := localentries.GetGroupEntries(ctx) require.NoError(t, err, "GetEntries should not return an error, but did") require.Equal(t, groups, updatedGroups, "Cached groups are not saved") @@ -410,8 +407,7 @@ func TestRacingGroupsLockingActions(t *testing.T) { require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") }) - lg := localentries.GetGroupsWithLock(ctx) - groups, err := lg.GetEntries() + groups, err := localentries.GetGroupEntries(ctx) require.NoError(t, err, "GetEntries should not return an error, but did") require.NotEmpty(t, groups, "Got empty groups (test groups: %v)", useTestGroupFile) require.Contains(t, groups, wantGroup, "Expected group was not found (test groups: %v)", useTestGroupFile) @@ -426,56 +422,50 @@ func TestRacingGroupsLockingActions(t *testing.T) { ctx, entriesUnlock, err := localentries.ContextUserDBLocked(context.Background()) require.NoError(t, err, "Failed to lock the local entries") - lg := localentries.GetGroupsWithLock(ctx) require.NoError(t, err, "Unlock should not fail to lock the users group") err = entriesUnlock() require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") // Ensure that we had cleaned up all the locks correctly! - require.Panics(t, func() { _, _ = lg.GetEntries() }) + require.Panics(t, func() { _, _ = localentries.GetGroupEntries(ctx) }) }) } func TestLockedInvalidActions(t *testing.T) { // This cannot be parallel - require.Panics(t, func() { localentries.GetGroupsWithLock(context.Background()) }, - "GetGroupsWithLock should panic but did not") - require.Panics(t, func() { _ = (&localentries.GroupsWithLock{}).Update("", nil, nil) }, + require.Panics(t, func() { _ = localentries.UpdateGroups(context.Background(), "", nil, nil) }, "Update should panic but did not") - require.Panics(t, func() { _, _ = (&localentries.GroupsWithLock{}).GetEntries() }, + require.Panics(t, func() { _, _ = localentries.GetGroupEntries(context.Background()) }, "GetEntries should panic but did not") - require.Panics(t, func() { _ = (&localentries.GroupsWithLock{}).SaveEntries(nil) }, + require.Panics(t, func() { _ = localentries.SaveGroupEntries(context.Background(), nil) }, "SaveEntries should panic but did not") ctx, entriesUnlock, err := localentries.ContextUserDBLocked(context.Background()) require.NoError(t, err, "Failed to lock the local entries") - lg := localentries.GetGroupsWithLock(ctx) err = entriesUnlock() require.NoError(t, err, "Unlock should not fail to lock the users group") err = entriesUnlock() require.Error(t, err, "Unlocking twice should fail") - require.Panics(t, func() { _ = lg.Update("", nil, nil) }, + require.Panics(t, func() { _ = localentries.UpdateGroups(ctx, "", nil, nil) }, "Update should panic but did not") - require.Panics(t, func() { _, _ = lg.GetEntries() }, + require.Panics(t, func() { _, _ = localentries.GetGroupEntries(ctx) }, "GetEntries should panic but did not") - require.Panics(t, func() { _ = lg.SaveEntries(nil) }, + require.Panics(t, func() { _ = localentries.SaveGroupEntries(ctx, nil) }, "SaveEntries should panic but did not") // This is to ensure that we're in a good state, despite the actions above for range 10 { - ctx, entriesUnlock, err := localentries.ContextUserDBLocked(context.Background()) + _, entriesUnlock, err := localentries.ContextUserDBLocked(context.Background()) require.NoError(t, err, "Failed to lock the local entries") defer func() { err := entriesUnlock() require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") }() - - lg = localentries.GetGroupsWithLock(ctx) } } @@ -634,10 +624,17 @@ func TestValidateChangedGroups(t *testing.T) { }, } + ctx, entriesUnlock, err := localentries.ContextUserDBLocked(context.Background()) + require.NoError(t, err, "Failed to lock the local entries") + t.Cleanup(func() { + err := entriesUnlock() + require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") + }) + for name, tc := range tests { t.Run(name, func(t *testing.T) { t.Parallel() - err := localentries.ValidateChangedGroups(tc.currentGroups, + err := localentries.ValidateChangedGroups(ctx, tc.currentGroups, tc.newGroups) if tc.wantErr { require.Error(t, err, "expected error but got nil") diff --git a/internal/users/localentries/lockedentries_test.go b/internal/users/localentries/lockedentries_test.go index 2446389b3a..cd6251157c 100644 --- a/internal/users/localentries/lockedentries_test.go +++ b/internal/users/localentries/lockedentries_test.go @@ -92,8 +92,7 @@ func TestRacingEntriesLockingActions(t *testing.T) { context.Background(), opts...) require.NoError(t, err, "Failed to lock the local entries") - lg := localentries.GetGroupsWithLock(ctx) - groups, err := lg.GetEntries() + groups, err := localentries.GetGroupEntries(ctx) require.NoError(t, err, "GetEntries should not return an error, but did") require.NotEmpty(t, groups, "Got empty groups (test groups: %v)", useTestGroupFile) require.Contains(t, groups, wantGroup, "Expected group was not found (test groups: %v)", useTestGroupFile) diff --git a/internal/users/manager.go b/internal/users/manager.go index e158b54fde..b824b78b12 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -286,8 +286,7 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { } defer func() { err = errors.Join(err, unlockEntries()) }() - lockedGroups := localentries.GetGroupsWithLock(ctx) - if err := lockedGroups.Update(u.Name, localGroups, oldLocalGroups); err != nil { + if err := localentries.UpdateGroups(ctx, u.Name, localGroups, oldLocalGroups); err != nil { return err } From 340ca3d408029614ff63dac2f6046a80f69ebb9b Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 2 Jul 2025 02:36:59 +0200 Subject: [PATCH 0573/1670] localentries: Add support for parsing local passwd file --- internal/users/localentries/localusers.go | 73 ++++++++++++++++++++ internal/users/localentries/lockedentries.go | 32 ++++++++- 2 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 internal/users/localentries/localusers.go diff --git a/internal/users/localentries/localusers.go b/internal/users/localentries/localusers.go new file mode 100644 index 0000000000..4bf7db4bda --- /dev/null +++ b/internal/users/localentries/localusers.go @@ -0,0 +1,73 @@ +package localentries + +import ( + "bufio" + "context" + "os" + "strconv" + "strings" + + "github.com/ubuntu/authd/internal/users/types" + "github.com/ubuntu/authd/log" + "github.com/ubuntu/decorate" +) + +func parseLocalPasswdFile(passwdFile string) (entries []types.UserEntry, err error) { + defer decorate.OnError(&err, "could not parse local passwd file %s", passwdFile) + + log.Debugf(context.Background(), "Parsing local passwd file: %s", passwdFile) + + f, err := os.Open(passwdFile) + if err != nil { + return nil, err + } + defer f.Close() + + // The format of the local passwd file is: + // username:password:uid:gid:gecos:home:shell + scanner := bufio.NewScanner(f) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" || line[0] == '#' { + continue // Skip empty lines and comments + } + + fields := strings.SplitN(line, ":", 7) + if len(fields) < 7 { + log.Warningf(context.Background(), "Skipping invalid entry in %s (invalid format): %s", passwdFile, line) + continue + } + + username, uidValue, gidValue, gecos, home, shell := + fields[0], fields[2], fields[3], fields[4], fields[5], fields[6] + + uid, err := strconv.ParseUint(uidValue, 10, 32) + if err != nil { + log.Warningf(context.Background(), "Skipping invalid entry in %s (invalid UID): %s", passwdFile, line) + continue + } + + gid, err := strconv.ParseUint(gidValue, 10, 32) + if err != nil { + log.Warningf(context.Background(), "Skipping invalid entry in %s (invalid GID): %s", passwdFile, line) + continue + } + + entry := types.UserEntry{ + Name: username, + UID: uint32(uid), + GID: uint32(gid), + Gecos: gecos, + Dir: home, + Shell: shell, + } + + entries = append(entries, entry) + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + return entries, nil +} diff --git a/internal/users/localentries/lockedentries.go b/internal/users/localentries/lockedentries.go index cce22aa9a2..8f692ed960 100644 --- a/internal/users/localentries/lockedentries.go +++ b/internal/users/localentries/lockedentries.go @@ -16,10 +16,19 @@ import ( "github.com/ubuntu/decorate" ) -// GroupFile is the default local group fill. -const GroupFile = "/etc/group" +const ( + // GroupFile is the default local passwd file. + passwdFile = "/etc/passwd" + + // GroupFile is the default local group file. + GroupFile = "/etc/group" +) type options struct { + // inputPasswdPath is the path used to read the passwd file. Defaults to + // [passwdFile], but can be overwritten in tests. + inputPasswdPath string + // inputGroupPath is the path used to read the group file. Defaults to // [GroupFile], but can be overwritten in tests. inputGroupPath string @@ -42,6 +51,7 @@ var defaultOptions = options{ // no test options are provided. userDBLocked: &UserDBLocked{}, + inputPasswdPath: passwdFile, inputGroupPath: GroupFile, outputGroupPath: GroupFile, @@ -80,6 +90,9 @@ type UserDBLocked struct { // userEntries holds the current group entries. userEntries []types.UserEntry + // localUserEntries holds the current local entries. + localUserEntries []types.UserEntry + // groupEntries holds the current group entries. groupEntries []types.GroupEntry // localGroupEntries holds the current group entries. @@ -214,6 +227,21 @@ func (l *UserDBLocked) GetGroupEntries() (entries []types.GroupEntry, err error) return l.groupEntries, err } +// GetLocalUserEntries gets the local group entries. +func (l *UserDBLocked) GetLocalUserEntries() (entries []types.UserEntry, err error) { + l.mu.Lock() + defer l.mu.Unlock() + + l.mustBeLocked() + + if l.localUserEntries != nil { + return l.localUserEntries, nil + } + + l.localUserEntries, err = parseLocalPasswdFile(l.options.inputPasswdPath) + return l.localUserEntries, err +} + // GetLocalGroupEntries gets the local group entries. func (l *UserDBLocked) GetLocalGroupEntries() (entries []types.GroupEntry, err error) { l.mu.Lock() From a2b017aaddff35da352deddee92e17aabba0767d Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 2 Jul 2025 04:54:57 +0200 Subject: [PATCH 0574/1670] users/idgenerator: Pick deterministic UIDs/GIDs instead of random generated ones Now that we have a way to effectively avoid races when picking a UID/GID (assuming that all other system tools and services also use lckpwdf, which they should), there is no good reason anymore to pick random IDs from such a large ID range. So with this commit we pick deterministic IDs, starting with the next higher ID above the highest ID already in use by an authd user or a user in `/etc/passwd` (while still respecting the configured UID_MIN value). Similarly for groups, we choose a GID taking in considerations the groups IDs already registered by authd and the ones in `/etc/group` (while still respecting the configured GID_MIN value). In follow-up work, this will also allow us to change the default UID/GID ranges to a smaller range which doesn't overlap with the LXD ID ranges, which should fix #819. Co-Authored-By: Marco Trevisan --- internal/services/pam/pam_test.go | 5 +- internal/services/user/user_test.go | 3 +- internal/users/idgenerator.go | 302 ++++++++++++++++++ internal/users/idgenerator/idgenerator.go | 38 --- .../{idgenerator => }/idgenerator_test.go | 4 +- internal/users/manager.go | 177 ++++------ internal/users/manager_test.go | 7 +- internal/users/tempentries/export_test.go | 26 ++ internal/users/tempentries/preauth.go | 23 +- internal/users/tempentries/preauth_test.go | 57 ++-- internal/users/{idgenerator => }/testutils.go | 21 +- 11 files changed, 469 insertions(+), 194 deletions(-) create mode 100644 internal/users/idgenerator.go delete mode 100644 internal/users/idgenerator/idgenerator.go rename internal/users/{idgenerator => }/idgenerator_test.go (91%) create mode 100644 internal/users/tempentries/export_test.go rename internal/users/{idgenerator => }/testutils.go (57%) diff --git a/internal/services/pam/pam_test.go b/internal/services/pam/pam_test.go index 4726d55b76..d773173573 100644 --- a/internal/services/pam/pam_test.go +++ b/internal/services/pam/pam_test.go @@ -26,7 +26,6 @@ import ( "github.com/ubuntu/authd/internal/testutils/golden" "github.com/ubuntu/authd/internal/users" "github.com/ubuntu/authd/internal/users/db" - "github.com/ubuntu/authd/internal/users/idgenerator" localgroupstestutils "github.com/ubuntu/authd/internal/users/localentries/testutils" userslocking "github.com/ubuntu/authd/internal/users/locking" userstestutils "github.com/ubuntu/authd/internal/users/testutils" @@ -466,7 +465,7 @@ func TestIsAuthenticated(t *testing.T) { } managerOpts := []users.Option{ - users.WithIDGenerator(&idgenerator.IDGeneratorMock{ + users.WithIDGenerator(&users.IDGeneratorMock{ UIDsToGenerate: []uint32{1111}, GIDsToGenerate: []uint32{22222}, }), @@ -560,7 +559,7 @@ func TestIDGeneration(t *testing.T) { t.Parallel() managerOpts := []users.Option{ - users.WithIDGenerator(&idgenerator.IDGeneratorMock{ + users.WithIDGenerator(&users.IDGeneratorMock{ UIDsToGenerate: []uint32{1111}, GIDsToGenerate: []uint32{22222}, }), diff --git a/internal/services/user/user_test.go b/internal/services/user/user_test.go index 55370428a0..720dfa1839 100644 --- a/internal/services/user/user_test.go +++ b/internal/services/user/user_test.go @@ -18,7 +18,6 @@ import ( "github.com/ubuntu/authd/internal/testutils/golden" "github.com/ubuntu/authd/internal/users" "github.com/ubuntu/authd/internal/users/db" - "github.com/ubuntu/authd/internal/users/idgenerator" userslocking "github.com/ubuntu/authd/internal/users/locking" "github.com/ubuntu/authd/log" "google.golang.org/grpc" @@ -278,7 +277,7 @@ func newUserManagerForTests(t *testing.T, dbFile string) *users.Manager { require.NoError(t, err, "Setup: could not create database from testdata") managerOpts := []users.Option{ - users.WithIDGenerator(&idgenerator.IDGeneratorMock{ + users.WithIDGenerator(&users.IDGeneratorMock{ UIDsToGenerate: []uint32{1234}, }), } diff --git a/internal/users/idgenerator.go b/internal/users/idgenerator.go new file mode 100644 index 0000000000..c5679fbe31 --- /dev/null +++ b/internal/users/idgenerator.go @@ -0,0 +1,302 @@ +package users + +import ( + "context" + "errors" + "fmt" + "slices" + "sort" + "sync" + + "github.com/ubuntu/authd/internal/testsdetection" + "github.com/ubuntu/authd/internal/users/localentries" + "github.com/ubuntu/authd/log" +) + +// IDGeneratorIface is the interface that must be implemented by the ID generator. +type IDGeneratorIface interface { + GenerateUID(ctx context.Context, owner IDOwner) (uint32, error) + GenerateGID(ctx context.Context, owner IDOwner) (uint32, error) + ClearPendingIDs() +} + +// IDOwner is the interface that must be implemented by the IDs owner to provide +// the currently used UIDs and GIDs. +type IDOwner interface { + UsedUIDs() ([]uint32, error) + UsedGIDs() ([]uint32, error) +} + +// IDGenerator is an ID generator that generates UIDs and GIDs in a specific range. +type IDGenerator struct { + UIDMin uint32 + UIDMax uint32 + GIDMin uint32 + GIDMax uint32 + + // IDs generated but not saved to the database yet. + // This is used to avoid generating the same ID multiple times. + // We don't differentiate between UIDs and GIDs here, because: + // * When picking a UID, we avoid IDs which we already used as GIDs, + // because the UID is also used as the GID of the user private group. + // * When picking a GID, we avoid IDs which we already used as UIDs, + // because those are also GIDs of the user private groups. + pendingIDs []uint32 + pendingIDsMu sync.Mutex + + getUsedUIDsMock func() ([]uint32, error) + getUsedGIDsMock func() ([]uint32, error) + isGIDAvailableMock func(gid uint32) (bool, error) + isUIDAvailableMock func(uid uint32) (bool, error) +} + +// Avoid to loop forever if we can't find an UID for the user, it's just better +// to fail after a limit is reached than hang or crash. +const maxIDGenerateIterations = 256 + +// GenerateUID generates a random UID in the configured range. +func (g *IDGenerator) GenerateUID(ctx context.Context, owner IDOwner) (uint32, error) { + return g.generateID(ctx, owner, generateID{ + idType: "UID", + minID: g.UIDMin, + maxID: g.UIDMax, + getUsedIDs: g.getUsedIDs, + isAvailableID: g.isUIDAvailable, + }) +} + +// GenerateGID generates a random GID in the configured range. +func (g *IDGenerator) GenerateGID(ctx context.Context, owner IDOwner) (uint32, error) { + return g.generateID(ctx, owner, generateID{ + idType: "GID", + minID: g.GIDMin, + maxID: g.GIDMax, + getUsedIDs: g.getUsedGIDs, + isAvailableID: g.isGIDAvailable, + }) +} + +// This is an utility struct to allow code sharing simplifying the arguments passing. +type generateID struct { + idType string + minID, maxID uint32 + isAvailableID func(context.Context, uint32) (bool, error) + getUsedIDs func(context.Context, IDOwner) ([]uint32, error) +} + +func (g *IDGenerator) generateID(ctx context.Context, owner IDOwner, args generateID) (id uint32, err error) { + g.pendingIDsMu.Lock() + defer g.pendingIDsMu.Unlock() + + usedIDs, err := args.getUsedIDs(ctx, owner) + if err != nil { + return 0, err + } + + // Add pending IDs to the used IDs to ensure we don't generate the same ID again + usedIDs = append(usedIDs, g.pendingIDs...) + + usedIDs = normalizeUsedIDs(usedIDs, args.minID, args.maxID) + + for range maxIDGenerateIterations { + id, err := getIDCandidate(args.minID, args.maxID, usedIDs) + if err != nil { + return 0, err + } + + available, err := args.isAvailableID(ctx, id) + if err != nil { + return 0, err + } + + if !available { + // If the GID is not available, try the next candidate + usedIDs = append(usedIDs, id) + log.Debugf(ctx, "%s %d is already used", args.idType, id) + continue + } + + g.pendingIDs = append(g.pendingIDs, id) + return id, nil + } + + return 0, fmt.Errorf("failed to find a valid %s for after %d attempts", + args.idType, maxIDGenerateIterations) +} + +// ClearPendingIDs clears the pending UIDs and GIDs. +// This function should be called once the generated IDs have been saved to the database. +func (g *IDGenerator) ClearPendingIDs() { + g.pendingIDsMu.Lock() + defer g.pendingIDsMu.Unlock() + + g.pendingIDs = nil +} + +func getIDCandidate(minID, maxID uint32, usedIDs []uint32) (uint32, error) { + if minID > maxID { + return 0, errors.New("minID must be less than or equal to maxID") + } + + // Find the highest used ID, if any + var highestUsed uint32 + if len(usedIDs) > 0 { + highestUsed = usedIDs[len(usedIDs)-1] + } else { + highestUsed = minID - 1 // No used IDs + } + + // Try IDs above the highest used + for id := highestUsed + 1; id <= maxID; id++ { + if _, found := slices.BinarySearch(usedIDs, id); found { + continue + } + return id, nil + } + + // Fallback: try IDs from minID up to highestUsed + for id := minID; id <= highestUsed && id <= maxID; id++ { + if _, found := slices.BinarySearch(usedIDs, id); found { + continue + } + return id, nil + } + + return 0, errors.New("no available ID in range") +} + +func (g *IDGenerator) isUIDAvailable(ctx context.Context, uid uint32) (bool, error) { + if g.isUIDAvailableMock != nil { + // If a mock function is provided, use it to check if the UID is available + testsdetection.MustBeTesting() + return g.isUIDAvailableMock(uid) + } + + lockedEntries := localentries.GetUserDBLocked(ctx) + if unique, err := lockedEntries.IsUniqueUID(uid); !unique || err != nil { + return false, err + } + + return true, nil +} + +func (g *IDGenerator) isGIDAvailable(ctx context.Context, gid uint32) (bool, error) { + if g.isUIDAvailableMock != nil { + // If a mock function is provided, use it to check if the UID is available + testsdetection.MustBeTesting() + return g.isGIDAvailableMock(gid) + } + + lockedEntries := localentries.GetUserDBLocked(ctx) + if unique, err := lockedEntries.IsUniqueGID(gid); !unique || err != nil { + return false, err + } + + return true, nil +} + +func (g *IDGenerator) getUsedIDs(ctx context.Context, owner IDOwner) ([]uint32, error) { + usedUIDs, err := g.getUsedUIDs(ctx, owner) + if err != nil { + return nil, err + } + + // For the user ID we also need to exclude all the GIDs, since the user + // private group ID will match its own uid, so if we don't do this, we may + // have a clash later on, when trying to add the group for this user. + usedGIDs, err := g.getUsedGIDs(ctx, owner) + if err != nil { + return nil, err + } + + return append(usedUIDs, usedGIDs...), nil +} + +func (g *IDGenerator) getUsedUIDs(ctx context.Context, owner IDOwner) ([]uint32, error) { + if g.getUsedUIDsMock != nil { + // If a mock function is provided, use it to get the used UIDs + testsdetection.MustBeTesting() + return g.getUsedUIDsMock() + } + + // Get the users from the authd database and pre-auth users. + uids, err := owner.UsedUIDs() + if err != nil { + return nil, err + } + + // Get the user entries from the passwd file. We don't use NSS here, + // because for picking the next higher ID we only want to consider the users + // in /etc/passwd and in the authd database, not from other sources like LDAP. + userEntries, err := localentries.GetUserDBLocked(ctx).GetLocalUserEntries() + if err != nil { + return nil, err + } + for _, user := range userEntries { + uids = append(uids, user.UID) + } + + return uids, nil +} + +func (g *IDGenerator) getUsedGIDs(ctx context.Context, owner IDOwner) ([]uint32, error) { + if g.getUsedGIDsMock != nil { + // If a mock function is provided, use it to get the used GIDs + testsdetection.MustBeTesting() + return g.getUsedGIDsMock() + } + + gids, err := owner.UsedGIDs() + if err != nil { + return nil, err + } + + // Get the group entries from the passwd file. We don't use NSS here, + // because for picking the next higher ID we only want to consider the groups + // in /etc/group and the users in /etc/group and in the authd database, not + // from other sources like LDAP (in case merge method is used). + groupEntries, err := localentries.GetUserDBLocked(ctx).GetLocalGroupEntries() + if err != nil { + return nil, err + } + for _, group := range groupEntries { + gids = append(gids, group.GID) + } + + // And include users GIDs too. + userEntries, err := localentries.GetUserDBLocked(ctx).GetLocalUserEntries() + if err != nil { + return nil, err + } + for _, user := range userEntries { + gids = append(gids, user.GID) + } + + return gids, nil +} + +func normalizeUsedIDs(usedIDs []uint32, minID, maxID uint32) []uint32 { + // Sort usedIDs so we can binary search + sort.Slice(usedIDs, func(i, j int) bool { return usedIDs[i] < usedIDs[j] }) + + // Cut off usedIDs to the range we care about + if len(usedIDs) > 0 && usedIDs[0] < minID { + // Find the first ID >= minID + firstIndex := slices.IndexFunc(usedIDs, func(id uint32) bool { return id >= minID }) + if firstIndex != -1 { + // Slice usedIDs to start from the first ID >= minID + usedIDs = usedIDs[firstIndex:] + } + } + if len(usedIDs) > 0 && usedIDs[len(usedIDs)-1] > maxID { + // Find the last ID <= maxID + lastIndex := slices.IndexFunc(usedIDs, func(id uint32) bool { return id > maxID }) + if lastIndex != -1 { + // Slice usedIDs to end at the last ID <= maxID + usedIDs = usedIDs[:lastIndex] + } + } + + // Remove duplicates from usedIDs + return slices.Compact(usedIDs) +} diff --git a/internal/users/idgenerator/idgenerator.go b/internal/users/idgenerator/idgenerator.go deleted file mode 100644 index f95dd605bd..0000000000 --- a/internal/users/idgenerator/idgenerator.go +++ /dev/null @@ -1,38 +0,0 @@ -// Package idgenerator provides an ID generator that generates UIDs and GIDs in a specific range. -package idgenerator - -import ( - "crypto/rand" - "math/big" -) - -// IDGenerator is an ID generator that generates UIDs and GIDs in a specific range. -type IDGenerator struct { - UIDMin uint32 - UIDMax uint32 - GIDMin uint32 - GIDMax uint32 -} - -// GenerateUID generates a random UID in the configured range. -func (g *IDGenerator) GenerateUID() (uint32, error) { - return generateID(g.UIDMin, g.UIDMax) -} - -// GenerateGID generates a random GID in the configured range. -func (g *IDGenerator) GenerateGID() (uint32, error) { - return generateID(g.GIDMin, g.GIDMax) -} - -func generateID(minID, maxID uint32) (uint32, error) { - diff := int64(maxID - minID) - // Generate a cryptographically secure random number between 0 and diff - nBig, err := rand.Int(rand.Reader, big.NewInt(diff+1)) - if err != nil { - return 0, err - } - - // Add minID to get a number in the desired range - //nolint:gosec // This conversion is safe because we only generate UIDs which are positive and smaller than uint32. - return uint32(nBig.Int64()) + minID, nil -} diff --git a/internal/users/idgenerator/idgenerator_test.go b/internal/users/idgenerator_test.go similarity index 91% rename from internal/users/idgenerator/idgenerator_test.go rename to internal/users/idgenerator_test.go index 845099c36b..63290e74ed 100644 --- a/internal/users/idgenerator/idgenerator_test.go +++ b/internal/users/idgenerator_test.go @@ -1,4 +1,4 @@ -package idgenerator +package users import ( "testing" @@ -21,7 +21,7 @@ func TestGenerateID(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - id, err := generateID(tc.idMin, tc.idMax) + id, err := getIDCandidate(tc.idMin, tc.idMax, []uint32{}) require.NoError(t, err, "GenerateID should not have failed") require.GreaterOrEqual(t, id, tc.idMin, "GenerateID should return an ID greater or equal to the minimum") diff --git a/internal/users/manager.go b/internal/users/manager.go index b824b78b12..cc8726a5d2 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -6,13 +6,11 @@ import ( "errors" "fmt" "os" - "slices" "strings" "sync" "syscall" "github.com/ubuntu/authd/internal/users/db" - "github.com/ubuntu/authd/internal/users/idgenerator" "github.com/ubuntu/authd/internal/users/localentries" "github.com/ubuntu/authd/internal/users/tempentries" "github.com/ubuntu/authd/internal/users/types" @@ -46,23 +44,19 @@ type Manager struct { db *db.Manager config Config preAuthRecords *tempentries.PreAuthUserRecords - idGenerator tempentries.IDGenerator + idGenerator IDGeneratorIface } type options struct { - idGenerator tempentries.IDGenerator + idGenerator IDGeneratorIface } -// Avoid to loop forever if we can't find an UID for the user, it's just better -// to fail after a limit is reached than hang or crash. -const maxIDGenerateIterations = 256 - // Option is a function that allows changing some of the default behaviors of the manager. type Option func(*options) // WithIDGenerator makes the manager use a specific ID generator. // This option is only useful in tests. -func WithIDGenerator(g tempentries.IDGenerator) Option { +func WithIDGenerator(g IDGeneratorIface) Option { return func(o *options) { o.idGenerator = g } @@ -92,7 +86,7 @@ func NewManager(config Config, dbDir string, args ...Option) (m *Manager, err er return nil, fmt.Errorf("UID range configured via UID_MIN and UID_MAX is too small (%d), must be at least %d", numUIDs, minNumUIDs) } - opts.idGenerator = &idgenerator.IDGenerator{ + opts.idGenerator = &IDGenerator{ UIDMin: config.UIDMin, UIDMax: config.UIDMax, GIDMin: config.GIDMin, @@ -165,9 +159,10 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { return fmt.Errorf("another system user exists with %q name", u.Name) } - if uid, err = m.generateUniqueUID(ctx); err != nil { + if uid, err = m.idGenerator.GenerateUID(ctx, m); err != nil { return err } + defer m.idGenerator.ClearPendingIDs() log.Debugf(context.Background(), "Using new UID %d for user %q", uid, u.Name) } } else { @@ -230,14 +225,9 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { defer func() { err = errors.Join(err, unlockEntries()) }() lockedEntries := localentries.GetUserDBLocked(ctx) + defer m.idGenerator.ClearPendingIDs() - for i, j := 0, 0; i < len(newGroups); j++ { - g := &newGroups[i] - if j >= maxIDGenerateIterations { - return fmt.Errorf("failed to find a valid GID for %q after %d attempts", - g.Name, maxIDGenerateIterations) - } - + for _, g := range newGroups { unique, err := lockedEntries.IsUniqueGroupName(g.Name) if err != nil { return err @@ -247,23 +237,14 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { return fmt.Errorf("another system group exists with %q name", g.Name) } - gid, err := m.generateUniqueGID(ctx) + gid, err := m.idGenerator.GenerateGID(ctx, m) if err != nil { return err } - if gid == uid { - continue - } - if slices.ContainsFunc(newGroups, func(og types.GroupInfo) bool { - return og.GID != nil && *og.GID == gid - }) { - continue - } g.GID = &gid groupRows = append(groupRows, db.NewGroupRow(g.Name, *g.GID, g.UGID)) log.Debugf(ctx, "Using new GID %d for group %q", gid, u.Name) - i++ } } @@ -432,6 +413,30 @@ func (m *Manager) AllUsers() ([]types.UserEntry, error) { return usrEntries, err } +// UsedUIDs returns all user IDs, including the UIDs of temporary pre-auth users. +func (m *Manager) UsedUIDs() ([]uint32, error) { + var uids []uint32 + + usrEntries, err := m.AllUsers() + if err != nil { + return nil, err + } + for _, usr := range usrEntries { + uids = append(uids, usr.UID) + } + + // Add temporary users from the pre-auth records. + tempUsers, err := m.preAuthRecords.AllUsers() + if err != nil { + return nil, fmt.Errorf("failed to get temporary users: %w", err) + } + for _, tempUser := range tempUsers { + uids = append(uids, tempUser.UID) + } + + return uids, nil +} + // GroupByName returns the group information for the given group name. func (m *Manager) GroupByName(groupname string) (types.GroupEntry, error) { grp, err := m.db.GroupWithMembersByName(groupname) @@ -465,108 +470,59 @@ func (m *Manager) AllGroups() ([]types.GroupEntry, error) { return grpEntries, nil } -// ShadowByName returns the shadow information for the given user name. -func (m *Manager) ShadowByName(username string) (types.ShadowEntry, error) { - usr, err := m.db.UserByName(username) - if err != nil { - return types.ShadowEntry{}, err - } - return shadowEntryFromUserRow(usr), nil -} +// UsedGIDs returns all group IDs, including the GIDs of temporary pre-auth users. +func (m *Manager) UsedGIDs() ([]uint32, error) { + var gids []uint32 -// AllShadows returns all shadow entries. -func (m *Manager) AllShadows() ([]types.ShadowEntry, error) { - usrs, err := m.db.AllUsers() + grpEntries, err := m.AllGroups() if err != nil { return nil, err } - - var shadowEntries []types.ShadowEntry - for _, usr := range usrs { - shadowEntries = append(shadowEntries, shadowEntryFromUserRow(usr)) - } - return shadowEntries, err -} - -func (m *Manager) generateUniqueID(ctx context.Context, generator func() (uint32, error)) (uint32, error) { - for range maxIDGenerateIterations { - uid, err := generator() - if err != nil { - return 0, err - } - - var unique bool - unique, err = m.isUniqueID(ctx, uid) - if err != nil { - return 0, err - } - if !unique { - log.Debugf(ctx, "UID %d is not unique", uid) - continue - } - - return uid, nil + for _, g := range grpEntries { + gids = append(gids, g.GID) } - return 0, fmt.Errorf("Cannot find an unique ID") -} - -func (m *Manager) generateUniqueUID(ctx context.Context) (uint32, error) { - return m.generateUniqueID(ctx, m.idGenerator.GenerateUID) -} - -func (m *Manager) generateUniqueGID(ctx context.Context) (uint32, error) { - return m.generateUniqueID(ctx, m.idGenerator.GenerateGID) -} - -func (m *Manager) isUniqueID(ctx context.Context, id uint32) (bool, error) { - oldUser, err := m.UserByID(id) - if err == nil { - log.Debugf(ctx, "Found duplicate user %v", oldUser) - return false, nil + allUsers, err := m.AllUsers() + if err != nil { + return nil, err } - if !errors.Is(err, db.NoDataFoundError{}) { - return false, err + for _, u := range allUsers { + gids = append(gids, u.GID) } - oldGroup, err := m.GroupByID(id) - if err == nil { - log.Debugf(ctx, "Found duplicate group %v", oldGroup) - return false, nil + // Add temporary groups from the pre-auth records. + tempUsers, err := m.preAuthRecords.AllUsers() + if err != nil { + return nil, fmt.Errorf("failed to get temporary groups: %w", err) } - if !errors.Is(err, db.NoDataFoundError{}) { - return false, err + for _, tu := range tempUsers { + gids = append(gids, tu.GID) } - return m.isUniqueSystemID(ctx, id) + return gids, nil } -func (m *Manager) isUniqueSystemID(ctx context.Context, id uint32) (unique bool, err error) { - users, err := localentries.GetUserDBLocked(ctx).GetUserEntries() +// ShadowByName returns the shadow information for the given user name. +func (m *Manager) ShadowByName(username string) (types.ShadowEntry, error) { + usr, err := m.db.UserByName(username) if err != nil { - return false, err - } - - if idx := slices.IndexFunc(users, func(p types.UserEntry) (found bool) { - return p.UID == id - }); idx != -1 { - log.Debugf(ctx, "ID %d already in use by user %q", id, users[idx].Name) - return false, nil + return types.ShadowEntry{}, err } + return shadowEntryFromUserRow(usr), nil +} - groups, err := localentries.GetUserDBLocked(ctx).GetGroupEntries() +// AllShadows returns all shadow entries. +func (m *Manager) AllShadows() ([]types.ShadowEntry, error) { + usrs, err := m.db.AllUsers() if err != nil { - return false, err + return nil, err } - if idx := slices.IndexFunc(groups, func(g types.GroupEntry) (found bool) { - return g.GID == id - }); idx != -1 { - log.Debugf(ctx, "ID %d already in use by user %q", id, groups[idx].Name) - return false, nil + var shadowEntries []types.ShadowEntry + for _, usr := range usrs { + shadowEntries = append(shadowEntries, shadowEntryFromUserRow(usr)) } - - return true, nil + return shadowEntries, err } // RegisterUserPreAuth registers a temporary user with a unique UID in our NSS handler (in memory, not in the database). @@ -606,10 +562,11 @@ func (m *Manager) RegisterUserPreAuth(name string) (uid uint32, err error) { return 0, fmt.Errorf("another system user exists with %q name", name) } - uid, err = m.generateUniqueUID(ctx) + uid, err = m.idGenerator.GenerateUID(ctx, m) if err != nil { return 0, err } + defer m.idGenerator.ClearPendingIDs() if err := m.preAuthRecords.RegisterPreAuthUser(name, uid); err != nil { return 0, err diff --git a/internal/users/manager_test.go b/internal/users/manager_test.go index 1f7dec024e..3169008bed 100644 --- a/internal/users/manager_test.go +++ b/internal/users/manager_test.go @@ -18,7 +18,6 @@ import ( "github.com/ubuntu/authd/internal/testutils/golden" "github.com/ubuntu/authd/internal/users" "github.com/ubuntu/authd/internal/users/db" - "github.com/ubuntu/authd/internal/users/idgenerator" "github.com/ubuntu/authd/internal/users/localentries" localgroupstestutils "github.com/ubuntu/authd/internal/users/localentries/testutils" userslocking "github.com/ubuntu/authd/internal/users/locking" @@ -219,7 +218,7 @@ func TestUpdateUser(t *testing.T) { } managerOpts := []users.Option{ - users.WithIDGenerator(&idgenerator.IDGeneratorMock{ + users.WithIDGenerator(&users.IDGeneratorMock{ UIDsToGenerate: []uint32{user.UID}, GIDsToGenerate: gids, }), @@ -300,7 +299,7 @@ func TestRegisterUserPreauth(t *testing.T) { } managerOpts := []users.Option{ - users.WithIDGenerator(&idgenerator.IDGeneratorMock{ + users.WithIDGenerator(&users.IDGeneratorMock{ UIDsToGenerate: []uint32{user.UID}, }), } @@ -365,7 +364,7 @@ func TestConcurrentUserUpdate(t *testing.T) { err = entriesUnlock() require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") - idGenerator := &idgenerator.IDGenerator{ + idGenerator := &users.IDGenerator{ UIDMin: 0, //nolint: gosec // we're in tests, overflow is very unlikely to happen. UIDMax: uint32(len(systemPasswd)) + nIterations*preAuthIterations, diff --git a/internal/users/tempentries/export_test.go b/internal/users/tempentries/export_test.go new file mode 100644 index 0000000000..61c11e3319 --- /dev/null +++ b/internal/users/tempentries/export_test.go @@ -0,0 +1,26 @@ +package tempentries + +type PreAuthUser = preAuthUser + +func NewPreAuthUser(uid uint32, loginName string) PreAuthUser { + return PreAuthUser{uid: uid, loginName: loginName} +} + +func (r *PreAuthUserRecords) GetUsers() map[uint32]PreAuthUser { + r.rwMu.RLock() + defer r.rwMu.RUnlock() + + return r.users +} + +func (r *PreAuthUserRecords) SetTestUsers(users map[uint32]PreAuthUser, uidByLogin map[string]uint32) { + r.users = users + r.uidByLogin = uidByLogin +} + +func (r *PreAuthUserRecords) DeletePreAuthUser(uid uint32) { + r.rwMu.Lock() + defer r.rwMu.Unlock() + + r.deletePreAuthUser(uid) +} diff --git a/internal/users/tempentries/preauth.go b/internal/users/tempentries/preauth.go index 6d717b8852..26aa323677 100644 --- a/internal/users/tempentries/preauth.go +++ b/internal/users/tempentries/preauth.go @@ -32,12 +32,6 @@ const ( // NoDataFoundError is the error returned when no entry is found in the database. type NoDataFoundError = db.NoDataFoundError -// IDGenerator is the interface that must be implemented by the ID generator. -type IDGenerator interface { - GenerateUID() (uint32, error) - GenerateGID() (uint32, error) -} - type preAuthUser struct { // name is the generated random name of the pre-auth user (which is returned by UserByID). name string @@ -97,6 +91,23 @@ func (r *PreAuthUserRecords) UserByLogin(name string) (types.UserEntry, error) { return r.userByLogin(name) } +// AllUsers returns all pre-auth users as a slice of UserEntry. +func (r *PreAuthUserRecords) AllUsers() ([]types.UserEntry, error) { + r.rwMu.RLock() + defer r.rwMu.RUnlock() + + if len(r.users) == 0 { + return nil, nil + } + + users := make([]types.UserEntry, 0, len(r.users)) + for _, user := range r.users { + users = append(users, preAuthUserEntry(user)) + } + + return users, nil +} + // RegisterPreAuthUser registers a temporary user with a unique UID in our NSS // handler (in memory, not in the database). // diff --git a/internal/users/tempentries/preauth_test.go b/internal/users/tempentries/preauth_test.go index af1a9fd65f..cd27338f15 100644 --- a/internal/users/tempentries/preauth_test.go +++ b/internal/users/tempentries/preauth_test.go @@ -1,6 +1,7 @@ -package tempentries +package tempentries_test import ( + "context" "fmt" "slices" "strings" @@ -8,8 +9,9 @@ import ( "github.com/stretchr/testify/require" "github.com/ubuntu/authd/internal/testutils/golden" - "github.com/ubuntu/authd/internal/users/idgenerator" + "github.com/ubuntu/authd/internal/users" userslocking "github.com/ubuntu/authd/internal/users/locking" + "github.com/ubuntu/authd/internal/users/tempentries" "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/authd/log" ) @@ -55,7 +57,7 @@ func TestPreAuthUser(t *testing.T) { wantErr: true, }, "Error_when_login_name_exceeds_maximum_length": { - users: []string{strings.Repeat("a", MaxPreAuthUserNameLength+1)}, + users: []string{strings.Repeat("a", tempentries.MaxPreAuthUserNameLength+1)}, wantErr: true, }, } @@ -80,17 +82,21 @@ func TestPreAuthUser(t *testing.T) { } t.Log("UIDs to generate", tc.uidsToGenerate) - idGeneratorMock := &idgenerator.IDGeneratorMock{UIDsToGenerate: tc.uidsToGenerate} - records := NewPreAuthUserRecords() + idGeneratorMock := &users.IDGeneratorMock{UIDsToGenerate: tc.uidsToGenerate} + records := tempentries.NewPreAuthUserRecords() if tc.maxUsers { - records.users = make(map[uint32]preAuthUser, MaxPreAuthUsers) - for i := range uint32(MaxPreAuthUsers) { + users := make(map[uint32]tempentries.PreAuthUser, tempentries.MaxPreAuthUsers) + uidByLogin := make(map[string]uint32) + + for i := range uint32(tempentries.MaxPreAuthUsers) { uid := uidToGenerate + i + 1 loginName := fmt.Sprintf("pre-auth-%d", uid) - records.users[uid] = preAuthUser{uid: uid, loginName: loginName} - records.uidByLogin[loginName] = uid + users[uid] = tempentries.NewPreAuthUser(uid, loginName) + uidByLogin[loginName] = uid } + + records.SetTestUsers(users, uidByLogin) } if tc.wantPanic == nil { tc.wantPanic = make([]bool, len(tc.users)) @@ -101,7 +107,7 @@ func TestPreAuthUser(t *testing.T) { for idx, loginName := range tc.users { t.Logf("Registering user %q", loginName) - uid, err := idGeneratorMock.GenerateUID() + uid, err := idGeneratorMock.GenerateUID(context.Background(), nil) require.NoError(t, err, "GenerateUID should not return an error, but it did") if tc.wantPanic[idx] { @@ -124,7 +130,7 @@ func TestPreAuthUser(t *testing.T) { require.NoError(t, err, "RegisterPreAuthUser should not return an error, but did") require.Equal(t, wantUID, uid, "UID should be the one generated by the IDGenerator") - require.Equal(t, wantRegistered, len(records.users), + require.Len(t, records.GetUsers(), wantRegistered, "Number of pre-auth registered, users should be %d", wantRegistered) if isDuplicated { @@ -136,7 +142,7 @@ func TestPreAuthUser(t *testing.T) { registeredUIDs = append(registeredUIDs, uid) // Check that the user was registered - user, err := records.userByLogin(loginName) + user, err := records.UserByLogin(loginName) require.NoError(t, err, "UserByID should not return an error, but did") var goldenOptions []golden.Option @@ -162,12 +168,15 @@ func TestPreAuthUser(t *testing.T) { removeUID := registeredUIDs[len(registeredUIDs)-wantRegistered-1] t.Logf("Removing user %q for UID %v", loginName, removeUID) - records.deletePreAuthUser(removeUID) - require.Equal(t, wantRegistered, len(records.users), + preauthUID, cleanup, err := records.MaybeCompletePreauthUser(loginName) + require.NoError(t, err, "MaybeCompletePreauthUser should not fail but it did") + require.Equal(t, removeUID, preauthUID, "MaybeCompletePreauthUser UID is not matching") + cleanup() + require.Len(t, records.GetUsers(), wantRegistered, "Number of pre-auth users should be %d", wantRegistered) // Check that the user was removed - _, err := records.userByLogin(loginName) + _, err = records.UserByLogin(loginName) require.Error(t, err, "UserByID should return an error, but did not") } }) @@ -196,11 +205,11 @@ func TestPreAuthUserByIDAndName(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - idGeneratorMock := &idgenerator.IDGeneratorMock{UIDsToGenerate: []uint32{uidToGenerate}} - records := NewPreAuthUserRecords() + idGeneratorMock := &users.IDGeneratorMock{UIDsToGenerate: []uint32{uidToGenerate}} + records := tempentries.NewPreAuthUserRecords() if tc.registerUser { - uid, err := idGeneratorMock.GenerateUID() + uid, err := idGeneratorMock.GenerateUID(context.Background(), nil) require.NoError(t, err, "GenerateUID should not return an error, but it did") err = records.RegisterPreAuthUser(loginName, uid) @@ -208,12 +217,12 @@ func TestPreAuthUserByIDAndName(t *testing.T) { } if tc.userAlreadyRemoved { - records.deletePreAuthUser(uidToGenerate) + records.DeletePreAuthUser(uidToGenerate) } else { - defer records.deletePreAuthUser(uidToGenerate) + defer records.DeletePreAuthUser(uidToGenerate) } - user, err := records.userByID(uidToGenerate) + user, err := records.UserByID(uidToGenerate) if tc.wantErr { require.Error(t, err, "UserByID should return an error, but did not") @@ -230,9 +239,9 @@ func checkPreAuthUser(t *testing.T, user types.UserEntry, options ...golden.Opti // The name field contains a randomly generated part, so we replace that part // before comparing the user with the golden file. - require.True(t, strings.HasPrefix(user.Name, UserPrefix), - "Name should have %q prefix", UserPrefix) - user.Name = UserPrefix + "-{RANDOM-ID}" + require.True(t, strings.HasPrefix(user.Name, tempentries.UserPrefix), + "Name should have %q prefix", tempentries.UserPrefix) + user.Name = tempentries.UserPrefix + "-{RANDOM-ID}" golden.CheckOrUpdateYAML(t, user, options...) } diff --git a/internal/users/idgenerator/testutils.go b/internal/users/testutils.go similarity index 57% rename from internal/users/idgenerator/testutils.go rename to internal/users/testutils.go index 81375cb288..503333c3a4 100644 --- a/internal/users/idgenerator/testutils.go +++ b/internal/users/testutils.go @@ -1,16 +1,22 @@ -package idgenerator +package users -import "fmt" +import ( + "context" + "fmt" + + "github.com/ubuntu/authd/internal/testsdetection" +) // IDGeneratorMock is a mock implementation of the IDGenerator interface. -// revive:disable-next-line:exported // We don't want to call this type just "Mock" type IDGeneratorMock struct { UIDsToGenerate []uint32 GIDsToGenerate []uint32 } // GenerateUID generates a UID. -func (g *IDGeneratorMock) GenerateUID() (uint32, error) { +func (g *IDGeneratorMock) GenerateUID(_ context.Context, _ IDOwner) (uint32, error) { + testsdetection.MustBeTesting() + if len(g.UIDsToGenerate) == 0 { return 0, fmt.Errorf("no more UIDs to generate") } @@ -20,7 +26,9 @@ func (g *IDGeneratorMock) GenerateUID() (uint32, error) { } // GenerateGID generates a GID. -func (g *IDGeneratorMock) GenerateGID() (uint32, error) { +func (g *IDGeneratorMock) GenerateGID(_ context.Context, _ IDOwner) (uint32, error) { + testsdetection.MustBeTesting() + if len(g.GIDsToGenerate) == 0 { return 0, fmt.Errorf("no more GIDs to generate") } @@ -28,3 +36,6 @@ func (g *IDGeneratorMock) GenerateGID() (uint32, error) { g.GIDsToGenerate = g.GIDsToGenerate[1:] return gid, nil } + +// ClearPendingIDs clears the pending IDs. +func (g *IDGeneratorMock) ClearPendingIDs() {} From 309c061d6bb12d2233e62c433d040f91e82fd8b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Wed, 2 Jul 2025 23:55:15 +0200 Subject: [PATCH 0575/1670] users/idgenerator: Fix handling of min ID value We had two main issues here: - highestUsed may ended up not taking care of the minimum ID value, if we had usedIDs values, this was leading to just ignoring the minimum limit in case we were using a minimum value that was major than the latest used ID value - If minID was 0, we were overflowing and thus the highestUsed value could have been set to math.MaxUint32, basically making our lookup code to iterate almost infinitely So handle the overflow case, and use the max between the latest used ID and the configured min ID to compute the highestUsed value --- internal/users/idgenerator.go | 9 +++-- internal/users/manager_test.go | 73 ++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/internal/users/idgenerator.go b/internal/users/idgenerator.go index c5679fbe31..8ae6d89114 100644 --- a/internal/users/idgenerator.go +++ b/internal/users/idgenerator.go @@ -52,7 +52,7 @@ type IDGenerator struct { // Avoid to loop forever if we can't find an UID for the user, it's just better // to fail after a limit is reached than hang or crash. -const maxIDGenerateIterations = 256 +const maxIDGenerateIterations = 1000000 // GenerateUID generates a random UID in the configured range. func (g *IDGenerator) GenerateUID(ctx context.Context, owner IDOwner) (uint32, error) { @@ -140,11 +140,12 @@ func getIDCandidate(minID, maxID uint32, usedIDs []uint32) (uint32, error) { // Find the highest used ID, if any var highestUsed uint32 - if len(usedIDs) > 0 { - highestUsed = usedIDs[len(usedIDs)-1] - } else { + if minID > 0 { highestUsed = minID - 1 // No used IDs } + if len(usedIDs) > 0 { + highestUsed = max(highestUsed, usedIDs[len(usedIDs)-1]) + } // Try IDs above the highest used for id := highestUsed + 1; id <= maxID; id++ { diff --git a/internal/users/manager_test.go b/internal/users/manager_test.go index 3169008bed..92f67be9f5 100644 --- a/internal/users/manager_test.go +++ b/internal/users/manager_test.go @@ -3,6 +3,7 @@ package users_test import ( "context" "fmt" + "math" "os" "path/filepath" "slices" @@ -594,6 +595,78 @@ func TestConcurrentUserUpdate(t *testing.T) { }) } +func TestUpdateWhenNoMoreIDsAreAvailable(t *testing.T) { + t.Parallel() + + const maxIDs = uint32(10) + + tests := map[string]struct { + idGenerator users.IDGeneratorIface + }{ + "Errors_after_registering_the_max_amount_of_users_for_lower_IDs": { + idGenerator: &users.IDGenerator{ + UIDMin: 0, + UIDMax: 0 + maxIDs - 1, + GIDMin: 0, + GIDMax: 0 + maxIDs - 1, + }, + }, + "Errors_after_registering_the_max_amount_of_users_for_highest_IDs": { + idGenerator: &users.IDGenerator{ + UIDMin: math.MaxUint32 - maxIDs + 1, + UIDMax: math.MaxUint32, + GIDMin: math.MaxUint32 - maxIDs + 1, + GIDMax: math.MaxUint32, + }, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + dbDir := t.TempDir() + const dbFile = "one_user_and_group_with_matching_gid" + err := db.Z_ForTests_CreateDBFromYAML(filepath.Join("testdata", "db", dbFile+".db.yaml"), dbDir) + require.NoError(t, err, "Setup: could not create database from testdata") + + m := newManagerForTests(t, dbDir, users.WithIDGenerator(tc.idGenerator)) + + // Let'ts fill the manager first... + for idx := range maxIDs { + userName := fmt.Sprintf("authd-test-lucky-user-%d", idx) + t.Logf("Updating user %q", userName) + + err := m.UpdateUser(types.UserInfo{ + Name: userName, + Dir: "/home-prefixes/" + userName, + Shell: "/usr/sbin/nologin", + }) + + // We do not care about the return value now... + t.Logf("UpdateUser for %q exited with %v", userName, err) + } + + // Now try to add more users, we must fail for all of them. + for idx := range maxIDs { + t.Run(fmt.Sprintf("Adding_more_users%d", idx), func(t *testing.T) { + t.Parallel() + + userName := fmt.Sprintf("authd-test-unlucky-user-%d", idx) + t.Logf("Updating user %q", userName) + + err := m.UpdateUser(types.UserInfo{ + Name: userName, + Dir: "/home-prefixes/" + userName, + Shell: "/usr/sbin/nologin", + }) + + require.Error(t, err, "UpdateUser should have failed for %q", userName) + }) + } + }) + } +} + func TestBrokerForUser(t *testing.T) { t.Parallel() From d4941945fac3e7b52e3c13ac3577761c2f0db5cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 3 Jul 2025 01:06:50 +0000 Subject: [PATCH 0576/1670] users/idgenerator: Drop the mocking support We don't have any test at the moment that really needs a mock implementation to generate the IDs, so we can just rely on the current interface-based replacement when we need to use custom IDs generation --- internal/users/idgenerator.go | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/internal/users/idgenerator.go b/internal/users/idgenerator.go index 8ae6d89114..70108756e3 100644 --- a/internal/users/idgenerator.go +++ b/internal/users/idgenerator.go @@ -8,7 +8,6 @@ import ( "sort" "sync" - "github.com/ubuntu/authd/internal/testsdetection" "github.com/ubuntu/authd/internal/users/localentries" "github.com/ubuntu/authd/log" ) @@ -43,11 +42,6 @@ type IDGenerator struct { // because those are also GIDs of the user private groups. pendingIDs []uint32 pendingIDsMu sync.Mutex - - getUsedUIDsMock func() ([]uint32, error) - getUsedGIDsMock func() ([]uint32, error) - isGIDAvailableMock func(gid uint32) (bool, error) - isUIDAvailableMock func(uid uint32) (bool, error) } // Avoid to loop forever if we can't find an UID for the user, it's just better @@ -167,12 +161,6 @@ func getIDCandidate(minID, maxID uint32, usedIDs []uint32) (uint32, error) { } func (g *IDGenerator) isUIDAvailable(ctx context.Context, uid uint32) (bool, error) { - if g.isUIDAvailableMock != nil { - // If a mock function is provided, use it to check if the UID is available - testsdetection.MustBeTesting() - return g.isUIDAvailableMock(uid) - } - lockedEntries := localentries.GetUserDBLocked(ctx) if unique, err := lockedEntries.IsUniqueUID(uid); !unique || err != nil { return false, err @@ -182,12 +170,6 @@ func (g *IDGenerator) isUIDAvailable(ctx context.Context, uid uint32) (bool, err } func (g *IDGenerator) isGIDAvailable(ctx context.Context, gid uint32) (bool, error) { - if g.isUIDAvailableMock != nil { - // If a mock function is provided, use it to check if the UID is available - testsdetection.MustBeTesting() - return g.isGIDAvailableMock(gid) - } - lockedEntries := localentries.GetUserDBLocked(ctx) if unique, err := lockedEntries.IsUniqueGID(gid); !unique || err != nil { return false, err @@ -214,12 +196,6 @@ func (g *IDGenerator) getUsedIDs(ctx context.Context, owner IDOwner) ([]uint32, } func (g *IDGenerator) getUsedUIDs(ctx context.Context, owner IDOwner) ([]uint32, error) { - if g.getUsedUIDsMock != nil { - // If a mock function is provided, use it to get the used UIDs - testsdetection.MustBeTesting() - return g.getUsedUIDsMock() - } - // Get the users from the authd database and pre-auth users. uids, err := owner.UsedUIDs() if err != nil { @@ -241,12 +217,6 @@ func (g *IDGenerator) getUsedUIDs(ctx context.Context, owner IDOwner) ([]uint32, } func (g *IDGenerator) getUsedGIDs(ctx context.Context, owner IDOwner) ([]uint32, error) { - if g.getUsedGIDsMock != nil { - // If a mock function is provided, use it to get the used GIDs - testsdetection.MustBeTesting() - return g.getUsedGIDsMock() - } - gids, err := owner.UsedGIDs() if err != nil { return nil, err From 650bb3cb0f894e587fdfdc62277bc794a0432628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 3 Jul 2025 04:56:41 +0200 Subject: [PATCH 0577/1670] users/idgenerator: Simplify the used IDs cleanup logic We used to drop the IDs in a single shot but this implied adding a further method to the interface that had had no clear scope and was too dangerous to use, so use a cleanup function instead so that we can achieve the same, but schedule to drop each element once we're done with it. Also, given we use this struct in a locked environment we can avoid any further locking, so dropping the mutex. The race tests would tell us if in future this will be needed --- internal/users/idgenerator.go | 41 ++++++++-------------- internal/users/manager.go | 14 ++++---- internal/users/tempentries/preauth_test.go | 4 +-- internal/users/testutils.go | 15 ++++---- 4 files changed, 31 insertions(+), 43 deletions(-) diff --git a/internal/users/idgenerator.go b/internal/users/idgenerator.go index 70108756e3..161d0fd515 100644 --- a/internal/users/idgenerator.go +++ b/internal/users/idgenerator.go @@ -6,7 +6,6 @@ import ( "fmt" "slices" "sort" - "sync" "github.com/ubuntu/authd/internal/users/localentries" "github.com/ubuntu/authd/log" @@ -14,9 +13,8 @@ import ( // IDGeneratorIface is the interface that must be implemented by the ID generator. type IDGeneratorIface interface { - GenerateUID(ctx context.Context, owner IDOwner) (uint32, error) - GenerateGID(ctx context.Context, owner IDOwner) (uint32, error) - ClearPendingIDs() + GenerateUID(ctx context.Context, owner IDOwner) (uid uint32, cleanup func(), err error) + GenerateGID(ctx context.Context, owner IDOwner) (gid uint32, cleanup func(), err error) } // IDOwner is the interface that must be implemented by the IDs owner to provide @@ -40,8 +38,7 @@ type IDGenerator struct { // because the UID is also used as the GID of the user private group. // * When picking a GID, we avoid IDs which we already used as UIDs, // because those are also GIDs of the user private groups. - pendingIDs []uint32 - pendingIDsMu sync.Mutex + pendingIDs []uint32 } // Avoid to loop forever if we can't find an UID for the user, it's just better @@ -49,7 +46,7 @@ type IDGenerator struct { const maxIDGenerateIterations = 1000000 // GenerateUID generates a random UID in the configured range. -func (g *IDGenerator) GenerateUID(ctx context.Context, owner IDOwner) (uint32, error) { +func (g *IDGenerator) GenerateUID(ctx context.Context, owner IDOwner) (uint32, func(), error) { return g.generateID(ctx, owner, generateID{ idType: "UID", minID: g.UIDMin, @@ -60,7 +57,7 @@ func (g *IDGenerator) GenerateUID(ctx context.Context, owner IDOwner) (uint32, e } // GenerateGID generates a random GID in the configured range. -func (g *IDGenerator) GenerateGID(ctx context.Context, owner IDOwner) (uint32, error) { +func (g *IDGenerator) GenerateGID(ctx context.Context, owner IDOwner) (uint32, func(), error) { return g.generateID(ctx, owner, generateID{ idType: "GID", minID: g.GIDMin, @@ -78,13 +75,10 @@ type generateID struct { getUsedIDs func(context.Context, IDOwner) ([]uint32, error) } -func (g *IDGenerator) generateID(ctx context.Context, owner IDOwner, args generateID) (id uint32, err error) { - g.pendingIDsMu.Lock() - defer g.pendingIDsMu.Unlock() - +func (g *IDGenerator) generateID(ctx context.Context, owner IDOwner, args generateID) (id uint32, cleanup func(), err error) { usedIDs, err := args.getUsedIDs(ctx, owner) if err != nil { - return 0, err + return 0, nil, err } // Add pending IDs to the used IDs to ensure we don't generate the same ID again @@ -95,12 +89,12 @@ func (g *IDGenerator) generateID(ctx context.Context, owner IDOwner, args genera for range maxIDGenerateIterations { id, err := getIDCandidate(args.minID, args.maxID, usedIDs) if err != nil { - return 0, err + return 0, nil, err } available, err := args.isAvailableID(ctx, id) if err != nil { - return 0, err + return 0, nil, err } if !available { @@ -111,22 +105,17 @@ func (g *IDGenerator) generateID(ctx context.Context, owner IDOwner, args genera } g.pendingIDs = append(g.pendingIDs, id) - return id, nil + cleanup = func() { + idx := slices.Index(g.pendingIDs, id) + g.pendingIDs = append(g.pendingIDs[:idx], g.pendingIDs[idx+1:]...) + } + return id, cleanup, nil } - return 0, fmt.Errorf("failed to find a valid %s for after %d attempts", + return 0, nil, fmt.Errorf("failed to find a valid %s for after %d attempts", args.idType, maxIDGenerateIterations) } -// ClearPendingIDs clears the pending UIDs and GIDs. -// This function should be called once the generated IDs have been saved to the database. -func (g *IDGenerator) ClearPendingIDs() { - g.pendingIDsMu.Lock() - defer g.pendingIDsMu.Unlock() - - g.pendingIDs = nil -} - func getIDCandidate(minID, maxID uint32, usedIDs []uint32) (uint32, error) { if minID > maxID { return 0, errors.New("minID must be less than or equal to maxID") diff --git a/internal/users/manager.go b/internal/users/manager.go index cc8726a5d2..2827fab75c 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -159,10 +159,12 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { return fmt.Errorf("another system user exists with %q name", u.Name) } - if uid, err = m.idGenerator.GenerateUID(ctx, m); err != nil { + var cleanupUID func() + uid, cleanupUID, err = m.idGenerator.GenerateUID(ctx, m) + if err != nil { return err } - defer m.idGenerator.ClearPendingIDs() + defer cleanupUID() log.Debugf(context.Background(), "Using new UID %d for user %q", uid, u.Name) } } else { @@ -225,7 +227,6 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { defer func() { err = errors.Join(err, unlockEntries()) }() lockedEntries := localentries.GetUserDBLocked(ctx) - defer m.idGenerator.ClearPendingIDs() for _, g := range newGroups { unique, err := lockedEntries.IsUniqueGroupName(g.Name) @@ -237,10 +238,11 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { return fmt.Errorf("another system group exists with %q name", g.Name) } - gid, err := m.idGenerator.GenerateGID(ctx, m) + gid, cleanupGID, err := m.idGenerator.GenerateGID(ctx, m) if err != nil { return err } + defer cleanupGID() g.GID = &gid groupRows = append(groupRows, db.NewGroupRow(g.Name, *g.GID, g.UGID)) @@ -562,11 +564,11 @@ func (m *Manager) RegisterUserPreAuth(name string) (uid uint32, err error) { return 0, fmt.Errorf("another system user exists with %q name", name) } - uid, err = m.idGenerator.GenerateUID(ctx, m) + uid, cleanupUID, err := m.idGenerator.GenerateUID(ctx, m) if err != nil { return 0, err } - defer m.idGenerator.ClearPendingIDs() + defer cleanupUID() if err := m.preAuthRecords.RegisterPreAuthUser(name, uid); err != nil { return 0, err diff --git a/internal/users/tempentries/preauth_test.go b/internal/users/tempentries/preauth_test.go index cd27338f15..578db8bbf9 100644 --- a/internal/users/tempentries/preauth_test.go +++ b/internal/users/tempentries/preauth_test.go @@ -107,7 +107,7 @@ func TestPreAuthUser(t *testing.T) { for idx, loginName := range tc.users { t.Logf("Registering user %q", loginName) - uid, err := idGeneratorMock.GenerateUID(context.Background(), nil) + uid, _, err := idGeneratorMock.GenerateUID(context.Background(), nil) require.NoError(t, err, "GenerateUID should not return an error, but it did") if tc.wantPanic[idx] { @@ -209,7 +209,7 @@ func TestPreAuthUserByIDAndName(t *testing.T) { records := tempentries.NewPreAuthUserRecords() if tc.registerUser { - uid, err := idGeneratorMock.GenerateUID(context.Background(), nil) + uid, _, err := idGeneratorMock.GenerateUID(context.Background(), nil) require.NoError(t, err, "GenerateUID should not return an error, but it did") err = records.RegisterPreAuthUser(loginName, uid) diff --git a/internal/users/testutils.go b/internal/users/testutils.go index 503333c3a4..855bce7518 100644 --- a/internal/users/testutils.go +++ b/internal/users/testutils.go @@ -14,28 +14,25 @@ type IDGeneratorMock struct { } // GenerateUID generates a UID. -func (g *IDGeneratorMock) GenerateUID(_ context.Context, _ IDOwner) (uint32, error) { +func (g *IDGeneratorMock) GenerateUID(_ context.Context, _ IDOwner) (uint32, func(), error) { testsdetection.MustBeTesting() if len(g.UIDsToGenerate) == 0 { - return 0, fmt.Errorf("no more UIDs to generate") + return 0, nil, fmt.Errorf("no more UIDs to generate") } uid := g.UIDsToGenerate[0] g.UIDsToGenerate = g.UIDsToGenerate[1:] - return uid, nil + return uid, func() {}, nil } // GenerateGID generates a GID. -func (g *IDGeneratorMock) GenerateGID(_ context.Context, _ IDOwner) (uint32, error) { +func (g *IDGeneratorMock) GenerateGID(_ context.Context, _ IDOwner) (uint32, func(), error) { testsdetection.MustBeTesting() if len(g.GIDsToGenerate) == 0 { - return 0, fmt.Errorf("no more GIDs to generate") + return 0, nil, fmt.Errorf("no more GIDs to generate") } gid := g.GIDsToGenerate[0] g.GIDsToGenerate = g.GIDsToGenerate[1:] - return gid, nil + return gid, func() {}, nil } - -// ClearPendingIDs clears the pending IDs. -func (g *IDGeneratorMock) ClearPendingIDs() {} From 5b0369ecd20b9c8ec24a258c7d0f0181b957f1a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 4 Jul 2025 04:30:58 +0200 Subject: [PATCH 0578/1670] users/idgenerator: Avoid looping forever if all the IDs have been checked --- internal/users/idgenerator.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/internal/users/idgenerator.go b/internal/users/idgenerator.go index 161d0fd515..60a80d7a6f 100644 --- a/internal/users/idgenerator.go +++ b/internal/users/idgenerator.go @@ -41,9 +41,11 @@ type IDGenerator struct { pendingIDs []uint32 } -// Avoid to loop forever if we can't find an UID for the user, it's just better -// to fail after a limit is reached than hang or crash. -const maxIDGenerateIterations = 1000000 +// If no valid ID is found after this many attempts, something is likely wrong. +// A possible cause is another NSS source using the same ID range. +// We want users to report such cases, so we can consider including IDs +// from other NSS sources when determining which candidates to exclude. +const maxIDGenerateIterations = 1000 // GenerateUID generates a random UID in the configured range. func (g *IDGenerator) GenerateUID(ctx context.Context, owner IDOwner) (uint32, func(), error) { @@ -76,6 +78,10 @@ type generateID struct { } func (g *IDGenerator) generateID(ctx context.Context, owner IDOwner, args generateID) (id uint32, cleanup func(), err error) { + if args.minID > args.maxID { + return 0, nil, errors.New("minID must be less than or equal to maxID") + } + usedIDs, err := args.getUsedIDs(ctx, owner) if err != nil { return 0, nil, err @@ -86,7 +92,9 @@ func (g *IDGenerator) generateID(ctx context.Context, owner IDOwner, args genera usedIDs = normalizeUsedIDs(usedIDs, args.minID, args.maxID) - for range maxIDGenerateIterations { + maxAttempts := min(maxIDGenerateIterations, args.maxID-args.minID+1) + + for range maxAttempts { id, err := getIDCandidate(args.minID, args.maxID, usedIDs) if err != nil { return 0, nil, err @@ -113,14 +121,10 @@ func (g *IDGenerator) generateID(ctx context.Context, owner IDOwner, args genera } return 0, nil, fmt.Errorf("failed to find a valid %s for after %d attempts", - args.idType, maxIDGenerateIterations) + args.idType, maxAttempts) } func getIDCandidate(minID, maxID uint32, usedIDs []uint32) (uint32, error) { - if minID > maxID { - return 0, errors.New("minID must be less than or equal to maxID") - } - // Find the highest used ID, if any var highestUsed uint32 if minID > 0 { From 9d9f452978e1dcb80cf6dd199c762759cc3adcbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 4 Jul 2025 04:31:46 +0200 Subject: [PATCH 0579/1670] users/idgenerator: Add more tests --- internal/users/idgenerator_test.go | 300 ++++++++++++++++++++++++++++- 1 file changed, 290 insertions(+), 10 deletions(-) diff --git a/internal/users/idgenerator_test.go b/internal/users/idgenerator_test.go index 63290e74ed..4987fa58a2 100644 --- a/internal/users/idgenerator_test.go +++ b/internal/users/idgenerator_test.go @@ -1,31 +1,311 @@ package users import ( + "context" + "errors" + "fmt" + "math" "testing" "github.com/stretchr/testify/require" ) -func TestGenerateID(t *testing.T) { +func TestGetIDCandidate(t *testing.T) { t.Parallel() tests := map[string]struct { - input string - idMin uint32 - idMax uint32 + idMin uint32 + idMax uint32 + used []uint32 + wantID uint32 + wantErr bool }{ - "Generated_ID_is_within_the_defined_range": {input: "test", idMin: 1000, idMax: 2000}, - "Generate_ID_with_minimum_ID_equal_to_maximum_ID": {input: "test", idMin: 1000, idMax: 1000}, + "Generated_ID_is_within_the_defined_range": { + idMin: 1000, idMax: 2000, + wantID: 1000, + }, + "Generate_ID_with_minimum_ID_equal_to_maximum_ID": { + idMin: 1000, idMax: 1000, + wantID: 1000, + }, + "UsedIDs_outside_range_are_ignored": { + idMin: 1000, idMax: 2000, + used: []uint32{1, 2, 3, 999, 2001, 3000}, + wantID: 1000, + }, + "UsedIDs_in_middle_of_range": { + idMin: 1000, idMax: 1005, + used: []uint32{1002, 1003}, + wantID: 1004, + }, + "UsedIDs_at_the_end_of_range": { + idMin: 1000, idMax: 1005, + used: []uint32{1002, 1005}, + wantID: 1000, + }, + "UsedIDs_minID_equals_maxID_and_unused": { + idMin: 1000, idMax: 1000, + wantID: 1000, + }, + "UsedIDs_last_value_is_smaller_than_the_minimum_id": { + idMin: 1000, idMax: 2000, + used: []uint32{20, 100}, + wantID: 1000, + }, + + "Error_if_no_available_ID_in_range": { + idMin: 1000, idMax: 2000, + used: func() []uint32 { + used := make([]uint32, 0, 1001) + for i := uint32(1000); i <= 2000; i++ { + used = append(used, i) + } + return used + }(), + wantErr: true, + }, + "Error_if_usedIDs_minID_equals_maxID_and_is_used": { + idMin: 1000, idMax: 1000, + used: []uint32{1000}, + wantErr: true, + }, + "Error_if_minID_greater_than_maxID": { + idMin: 100000, idMax: 10000, + wantErr: true, + }, } + for name, tc := range tests { t.Run(name, func(t *testing.T) { t.Parallel() - id, err := getIDCandidate(tc.idMin, tc.idMax, []uint32{}) - require.NoError(t, err, "GenerateID should not have failed") + id, err := getIDCandidate(tc.idMin, tc.idMax, tc.used) + if tc.wantErr { + require.Error(t, err, "getIDCandidate not returned an error as expected") + return + } + + require.NoError(t, err, "getIDCandidate returned an unexpected error") + require.GreaterOrEqual(t, int(id), int(tc.idMin), "ID is less than idMin") + require.LessOrEqual(t, int(id), int(tc.idMax), "ID is greater than idMax") + require.Equal(t, int(tc.wantID), int(id), "Generated ID does not match expected value") + }) + } +} + +type IDOwnerMock struct { + usedUIDs []uint32 + usedGIDs []uint32 +} + +func (m IDOwnerMock) UsedUIDs() ([]uint32, error) { return m.usedUIDs, nil } +func (m IDOwnerMock) UsedGIDs() ([]uint32, error) { return m.usedGIDs, nil } + +func TestGenerateIDMocked(t *testing.T) { + t.Parallel() + + allAvailableIDsFunc := func(ctx context.Context, u uint32) (bool, error) { + return true, nil + } + noAvailableIDFunc := func(ctx context.Context, u uint32) (bool, error) { + return false, nil + } + noUsedIDFunc := func(ctx context.Context, o IDOwner) ([]uint32, error) { + return nil, nil + } + getOwnerUsedUIDsFunc := func(ctx context.Context, o IDOwner) ([]uint32, error) { + return o.UsedUIDs() + } + getOwnerUsedGIDsFunc := func(ctx context.Context, o IDOwner) ([]uint32, error) { + return o.UsedGIDs() + } + + tests := map[string]struct { + genID generateID + owner IDOwner + generator IDGenerator + noCleanupCheck bool + + wantErr bool + wantID uint32 + }{ + "Generated_ID_is_within_the_defined_range": { + genID: generateID{ + idType: "UID", + minID: 10000, maxID: 10010, + isAvailableID: allAvailableIDsFunc, + getUsedIDs: noUsedIDFunc, + }, + wantID: 10000, + }, + "Generated_ID_when_only_one_possible_value_is_available": { + genID: generateID{ + idType: "UID", + minID: 10000, maxID: 10000, + isAvailableID: allAvailableIDsFunc, + getUsedIDs: noUsedIDFunc, + }, + wantID: 10000, + }, + "Owner_with_some_used_UIDs": { + genID: generateID{ + idType: "UID", + minID: 500, maxID: 505, + isAvailableID: allAvailableIDsFunc, + getUsedIDs: getOwnerUsedUIDsFunc, + }, + owner: IDOwnerMock{usedUIDs: []uint32{500, 501, 502}}, + wantID: 503, + }, + "Owner_with_some_used_GIDs": { + genID: generateID{ + idType: "GID", + minID: 200, maxID: 202, + isAvailableID: allAvailableIDsFunc, + getUsedIDs: getOwnerUsedGIDsFunc, + }, + owner: IDOwnerMock{usedGIDs: []uint32{200}}, + wantID: 201, + }, + "Owner_with_no_used_IDs": { + genID: generateID{ + idType: "UID", + minID: 300, maxID: 301, + isAvailableID: allAvailableIDsFunc, + getUsedIDs: getOwnerUsedUIDsFunc, + }, + owner: IDOwnerMock{usedUIDs: nil}, + wantID: 300, + }, + "PendingIDs_are_considered": { + genID: generateID{ + idType: "UID", + minID: 100, maxID: 105, + isAvailableID: allAvailableIDsFunc, + getUsedIDs: noUsedIDFunc, + }, + generator: IDGenerator{pendingIDs: []uint32{100, 101, 102}}, + wantID: 103, + noCleanupCheck: true, + }, + + // Error cases + "Error_if_minID_is_equal_to_maxID": { + genID: generateID{ + idType: "UID", + minID: 10001, maxID: 10000, + }, + wantErr: true, + }, + "Error_if_ID_not_available_due_to_isAvailableID": { + genID: generateID{ + idType: "UID", + minID: 10000, maxID: 10010, + isAvailableID: noAvailableIDFunc, + getUsedIDs: noUsedIDFunc, + }, + wantErr: true, + }, + "Error_if_ID_not_available_due_to_isAvailableID_error": { + genID: generateID{ + idType: "UID", + minID: 10000, maxID: 10010, + isAvailableID: func(ctx context.Context, u uint32) (bool, error) { + return false, errors.New("test error") + }, + getUsedIDs: noUsedIDFunc, + }, + wantErr: true, + }, + "Error_if_all_IDs_are_used": { + genID: generateID{ + idType: "UID", + minID: 10000, maxID: 10002, + isAvailableID: allAvailableIDsFunc, + getUsedIDs: func(ctx context.Context, o IDOwner) ([]uint32, error) { + return []uint32{10000, 10001, 10002}, nil + }, + }, + wantErr: true, + }, + "Error_if_all_the_IDs_in_range_are_unavailable": { + genID: generateID{ + idType: "ID", + minID: 0, maxID: 100, + isAvailableID: noAvailableIDFunc, + getUsedIDs: noUsedIDFunc, + }, + wantErr: true, + }, + "Error_if_all_the_IDs_in_range_are_unavailable_after_max_checks": { + genID: generateID{ + idType: "ID", + minID: 10000, maxID: math.MaxUint32, + isAvailableID: func(ctx context.Context, u uint32) (bool, error) { + return u < 10000, nil + }, + getUsedIDs: noUsedIDFunc, + }, + wantErr: true, + }, + "Error_if_getUsedIDs_returns_error": { + genID: generateID{ + idType: "UID", + minID: 10000, maxID: 10010, + isAvailableID: allAvailableIDsFunc, + getUsedIDs: func(ctx context.Context, o IDOwner) ([]uint32, error) { + return nil, errors.New("usedIDs error") + }, + }, + wantErr: true, + }, + "Owner_with_all_UIDs_used": { + genID: generateID{ + idType: "UID", + minID: 10, maxID: 12, + isAvailableID: allAvailableIDsFunc, + getUsedIDs: getOwnerUsedUIDsFunc, + }, + owner: IDOwnerMock{usedUIDs: []uint32{10, 11, 12}}, + wantErr: true, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + if tc.genID.idType == "" { + tc.genID.idType = t.Name() + } else { + tc.genID.idType = fmt.Sprintf("%s_%s", t.Name(), + tc.genID.idType) + } + + id, cleanup, err := tc.generator.generateID(context.Background(), + tc.owner, tc.genID) + if tc.wantErr { + require.Error(t, err, "Expected error but got none") + require.Zero(t, id, "Expected id to be zero") + return + } + require.NoError(t, err, "GenerateID should not fail") + + t.Cleanup(func() { + cleanup() + + if tc.noCleanupCheck { + return + } + require.Empty(t, tc.generator.pendingIDs, "Expected generator to be empty after cleanup") + }) - require.GreaterOrEqual(t, id, tc.idMin, "GenerateID should return an ID greater or equal to the minimum") - require.LessOrEqual(t, id, tc.idMax, "GenerateID should return an ID less or equal to the maximum") + require.GreaterOrEqual(t, int(id), int(tc.genID.minID), "Id %d is less than minID %d", + id, tc.genID.minID) + require.LessOrEqual(t, int(id), int(tc.genID.maxID), "Id %d is greater than maxID %d", + id, tc.genID.maxID) + require.Equal(t, int(tc.wantID), int(id), "Generated unexpected ID") + require.Contains(t, tc.generator.pendingIDs, id, "Id %d not found in pendingIDs", id) }) } } From cf2da51a10f0d3affbd8809164df888c3252a358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 4 Jul 2025 05:44:48 +0200 Subject: [PATCH 0580/1670] users/idgenerator: Do not overflow if the highest used value is MaxUint32 In case the highest used value was MaxUint32 we ended up overflowing and returning 0, despite that was not a valid value. So prevent looping in a way that we could overflow. --- internal/users/idgenerator.go | 5 +++-- internal/users/idgenerator_test.go | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/internal/users/idgenerator.go b/internal/users/idgenerator.go index 60a80d7a6f..5c2b4253ee 100644 --- a/internal/users/idgenerator.go +++ b/internal/users/idgenerator.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "math" "slices" "sort" @@ -135,7 +136,7 @@ func getIDCandidate(minID, maxID uint32, usedIDs []uint32) (uint32, error) { } // Try IDs above the highest used - for id := highestUsed + 1; id <= maxID; id++ { + for id := highestUsed + 1; id <= maxID && highestUsed < math.MaxUint32; id++ { if _, found := slices.BinarySearch(usedIDs, id); found { continue } @@ -143,7 +144,7 @@ func getIDCandidate(minID, maxID uint32, usedIDs []uint32) (uint32, error) { } // Fallback: try IDs from minID up to highestUsed - for id := minID; id <= highestUsed && id <= maxID; id++ { + for id := minID; id <= highestUsed && id <= maxID && id < math.MaxUint32; id++ { if _, found := slices.BinarySearch(usedIDs, id); found { continue } diff --git a/internal/users/idgenerator_test.go b/internal/users/idgenerator_test.go index 4987fa58a2..8f0e132ece 100644 --- a/internal/users/idgenerator_test.go +++ b/internal/users/idgenerator_test.go @@ -52,6 +52,16 @@ func TestGetIDCandidate(t *testing.T) { used: []uint32{20, 100}, wantID: 1000, }, + "Only_MaxUint32_is_available": { + idMin: math.MaxUint32, + idMax: math.MaxUint32, + wantID: math.MaxUint32, + }, + "Intermediate_value_after_MaxUint32_is_reached": { + idMin: math.MaxUint32 - 2, idMax: math.MaxUint32, + used: []uint32{math.MaxUint32 - 2, math.MaxUint32}, + wantID: math.MaxUint32 - 1, + }, "Error_if_no_available_ID_in_range": { idMin: 1000, idMax: 2000, @@ -73,6 +83,11 @@ func TestGetIDCandidate(t *testing.T) { idMin: 100000, idMax: 10000, wantErr: true, }, + "Error_if_all_the_values_next_to_MaxUint32_are_used": { + idMin: math.MaxUint32 - 2, idMax: math.MaxUint32, + used: []uint32{math.MaxUint32 - 2, math.MaxUint32 - 1, math.MaxUint32}, + wantErr: true, + }, } for name, tc := range tests { From 9714d3b51c050d403443716ff75d3e00e35ce781 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 4 Jul 2025 14:32:52 +0200 Subject: [PATCH 0581/1670] users/idgenerator: Cleanup the candidate ID computation --- internal/users/idgenerator.go | 42 ++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/internal/users/idgenerator.go b/internal/users/idgenerator.go index 5c2b4253ee..5687fa9ac7 100644 --- a/internal/users/idgenerator.go +++ b/internal/users/idgenerator.go @@ -126,29 +126,39 @@ func (g *IDGenerator) generateID(ctx context.Context, owner IDOwner, args genera } func getIDCandidate(minID, maxID uint32, usedIDs []uint32) (uint32, error) { - // Find the highest used ID, if any - var highestUsed uint32 - if minID > 0 { - highestUsed = minID - 1 // No used IDs - } + // Pick the preferred ID candidate, starting with minID. + preferredID := minID + if len(usedIDs) > 0 { - highestUsed = max(highestUsed, usedIDs[len(usedIDs)-1]) + // If there are used IDs, we prefer the next ID above the highest used. + // Note that this may overflow, and so go back to 0, but the next check + // will adjust the value to the minID. + preferredID = usedIDs[len(usedIDs)-1] + 1 + + // Ensure that the preferred ID is not less than the minimum ID. + preferredID = max(preferredID, minID) } - // Try IDs above the highest used - for id := highestUsed + 1; id <= maxID && highestUsed < math.MaxUint32; id++ { - if _, found := slices.BinarySearch(usedIDs, id); found { - continue + // Try IDs starting from the preferred ID up to the maximum ID. + for id := preferredID; id <= maxID; id++ { + if _, found := slices.BinarySearch(usedIDs, id); !found { + return id, nil + } + + if id == math.MaxUint32 { + break // Avoid overflow } - return id, nil } - // Fallback: try IDs from minID up to highestUsed - for id := minID; id <= highestUsed && id <= maxID && id < math.MaxUint32; id++ { - if _, found := slices.BinarySearch(usedIDs, id); found { - continue + // Fallback: try IDs from the minimum ID up to the preferred ID. + for id := minID; id < preferredID && id <= maxID; id++ { + if _, found := slices.BinarySearch(usedIDs, id); !found { + return id, nil } - return id, nil + + // Overflows are avoided by the loop condition (id < preferredID, where + // preferredID is a uint32, so the condition must be false when + // id == math.MaxUint32). } return 0, errors.New("no available ID in range") From fb465599fda2bf84851786e1a8a9beb952031882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 4 Jul 2025 21:26:15 +0200 Subject: [PATCH 0582/1670] users/idgenerator: Ensure we always use sorted insert when tracking IDs We use binary search to compare the generated IDs, so we must add them in a sorted way, or we'll break the assumptions --- internal/users/idgenerator.go | 19 +++++---- internal/users/idgenerator_test.go | 66 +++++++++++++++++++++--------- 2 files changed, 57 insertions(+), 28 deletions(-) diff --git a/internal/users/idgenerator.go b/internal/users/idgenerator.go index 5687fa9ac7..daed65c112 100644 --- a/internal/users/idgenerator.go +++ b/internal/users/idgenerator.go @@ -96,7 +96,7 @@ func (g *IDGenerator) generateID(ctx context.Context, owner IDOwner, args genera maxAttempts := min(maxIDGenerateIterations, args.maxID-args.minID+1) for range maxAttempts { - id, err := getIDCandidate(args.minID, args.maxID, usedIDs) + id, pos, err := getIDCandidate(args.minID, args.maxID, usedIDs) if err != nil { return 0, nil, err } @@ -107,8 +107,11 @@ func (g *IDGenerator) generateID(ctx context.Context, owner IDOwner, args genera } if !available { + // Keep track of the id, but preserving usedIDs sorted, since we + // use the binary search to add elements to it. + usedIDs = slices.Insert(usedIDs, pos, id) + // If the GID is not available, try the next candidate - usedIDs = append(usedIDs, id) log.Debugf(ctx, "%s %d is already used", args.idType, id) continue } @@ -125,7 +128,7 @@ func (g *IDGenerator) generateID(ctx context.Context, owner IDOwner, args genera args.idType, maxAttempts) } -func getIDCandidate(minID, maxID uint32, usedIDs []uint32) (uint32, error) { +func getIDCandidate(minID, maxID uint32, usedIDs []uint32) (id uint32, uniqueIDsPos int, err error) { // Pick the preferred ID candidate, starting with minID. preferredID := minID @@ -141,8 +144,8 @@ func getIDCandidate(minID, maxID uint32, usedIDs []uint32) (uint32, error) { // Try IDs starting from the preferred ID up to the maximum ID. for id := preferredID; id <= maxID; id++ { - if _, found := slices.BinarySearch(usedIDs, id); !found { - return id, nil + if pos, found := slices.BinarySearch(usedIDs, id); !found { + return id, pos, nil } if id == math.MaxUint32 { @@ -152,8 +155,8 @@ func getIDCandidate(minID, maxID uint32, usedIDs []uint32) (uint32, error) { // Fallback: try IDs from the minimum ID up to the preferred ID. for id := minID; id < preferredID && id <= maxID; id++ { - if _, found := slices.BinarySearch(usedIDs, id); !found { - return id, nil + if pos, found := slices.BinarySearch(usedIDs, id); !found { + return id, pos, nil } // Overflows are avoided by the loop condition (id < preferredID, where @@ -161,7 +164,7 @@ func getIDCandidate(minID, maxID uint32, usedIDs []uint32) (uint32, error) { // id == math.MaxUint32). } - return 0, errors.New("no available ID in range") + return 0, -1, errors.New("no available ID in range") } func (g *IDGenerator) isUIDAvailable(ctx context.Context, uid uint32) (bool, error) { diff --git a/internal/users/idgenerator_test.go b/internal/users/idgenerator_test.go index 8f0e132ece..06463de0c9 100644 --- a/internal/users/idgenerator_test.go +++ b/internal/users/idgenerator_test.go @@ -14,53 +14,64 @@ func TestGetIDCandidate(t *testing.T) { t.Parallel() tests := map[string]struct { - idMin uint32 - idMax uint32 - used []uint32 + idMin uint32 + idMax uint32 + used []uint32 + + wantPos int wantID uint32 wantErr bool }{ "Generated_ID_is_within_the_defined_range": { idMin: 1000, idMax: 2000, - wantID: 1000, + wantID: 1000, + wantPos: 0, }, "Generate_ID_with_minimum_ID_equal_to_maximum_ID": { idMin: 1000, idMax: 1000, - wantID: 1000, + wantID: 1000, + wantPos: 0, }, "UsedIDs_outside_range_are_ignored": { idMin: 1000, idMax: 2000, - used: []uint32{1, 2, 3, 999, 2001, 3000}, - wantID: 1000, + used: []uint32{1, 2, 3, 999, 2001, 3000}, + wantID: 1000, + wantPos: 4, }, "UsedIDs_in_middle_of_range": { idMin: 1000, idMax: 1005, - used: []uint32{1002, 1003}, - wantID: 1004, + used: []uint32{1002, 1003}, + wantID: 1004, + wantPos: 2, }, "UsedIDs_at_the_end_of_range": { idMin: 1000, idMax: 1005, - used: []uint32{1002, 1005}, - wantID: 1000, + used: []uint32{1002, 1005}, + wantID: 1000, + wantPos: 0, }, "UsedIDs_minID_equals_maxID_and_unused": { idMin: 1000, idMax: 1000, - wantID: 1000, + wantID: 1000, + wantPos: 0, }, "UsedIDs_last_value_is_smaller_than_the_minimum_id": { idMin: 1000, idMax: 2000, - used: []uint32{20, 100}, - wantID: 1000, + used: []uint32{20, 100}, + wantID: 1000, + wantPos: 2, }, "Only_MaxUint32_is_available": { - idMin: math.MaxUint32, - idMax: math.MaxUint32, - wantID: math.MaxUint32, + idMin: math.MaxUint32, + idMax: math.MaxUint32, + wantID: math.MaxUint32, + wantPos: 0, }, "Intermediate_value_after_MaxUint32_is_reached": { idMin: math.MaxUint32 - 2, idMax: math.MaxUint32, - used: []uint32{math.MaxUint32 - 2, math.MaxUint32}, - wantID: math.MaxUint32 - 1, + used: []uint32{math.MaxUint32 - 2, math.MaxUint32}, + wantID: math.MaxUint32 - 1, + wantPos: 1, }, "Error_if_no_available_ID_in_range": { @@ -94,16 +105,19 @@ func TestGetIDCandidate(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - id, err := getIDCandidate(tc.idMin, tc.idMax, tc.used) + id, pos, err := getIDCandidate(tc.idMin, tc.idMax, tc.used) if tc.wantErr { require.Error(t, err, "getIDCandidate not returned an error as expected") + require.Equal(t, -1, pos, "getIDCandidate did not return the expected usedIDs position") return } require.NoError(t, err, "getIDCandidate returned an unexpected error") require.GreaterOrEqual(t, int(id), int(tc.idMin), "ID is less than idMin") require.LessOrEqual(t, int(id), int(tc.idMax), "ID is greater than idMax") + require.GreaterOrEqual(t, pos, 0, "Position is not greater or equal 0") require.Equal(t, int(tc.wantID), int(id), "Generated ID does not match expected value") + require.Equal(t, tc.wantPos, pos, "getIDCandidate returned unexpected position in usedIDs") }) } } @@ -203,6 +217,18 @@ func TestGenerateIDMocked(t *testing.T) { wantID: 103, noCleanupCheck: true, }, + "Used_ids_are_always_sorted": { + genID: generateID{ + idType: "UID", + minID: 300, maxID: 303, + isAvailableID: func(ctx context.Context, u uint32) (bool, error) { + return u == 302, nil + }, + getUsedIDs: getOwnerUsedUIDsFunc, + }, + owner: IDOwnerMock{usedUIDs: []uint32{300, 303}}, + wantID: 302, + }, // Error cases "Error_if_minID_is_equal_to_maxID": { From 1da444e9f583793521532c6e0c0de8da1b4fcaac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 4 Jul 2025 07:11:50 +0200 Subject: [PATCH 0583/1670] users/localentries/localusers: Add passwd file parsing test --- internal/users/localentries/export_test.go | 7 + .../users/localentries/localusers_test.go | 170 ++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 internal/users/localentries/localusers_test.go diff --git a/internal/users/localentries/export_test.go b/internal/users/localentries/export_test.go index 6697371ba3..bd23a9fdb7 100644 --- a/internal/users/localentries/export_test.go +++ b/internal/users/localentries/export_test.go @@ -15,6 +15,13 @@ func WithGroupPath(p string) Option { } } +// WithPasswdInputPath overrides the default /etc/passwd path for input in tests. +func WithPasswdInputPath(p string) Option { + return func(o *options) { + o.inputPasswdPath = p + } +} + // WithGroupInputPath overrides the default /etc/group path for input in tests. func WithGroupInputPath(p string) Option { return func(o *options) { diff --git a/internal/users/localentries/localusers_test.go b/internal/users/localentries/localusers_test.go new file mode 100644 index 0000000000..8b96821757 --- /dev/null +++ b/internal/users/localentries/localusers_test.go @@ -0,0 +1,170 @@ +package localentries_test + +import ( + "context" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/require" + "github.com/ubuntu/authd/internal/users/localentries" + "github.com/ubuntu/authd/internal/users/types" +) + +func TestParseLocalPasswdFile(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + passwdLines []string + + wantEntries []types.UserEntry + wantErr bool + }{ + "Valid_empty_file": {passwdLines: []string{}}, + "Valid_single_entry": { + passwdLines: []string{ + "testuser:x:1000:1000:Test User:/home/testuser:/bin/bash", + }, + wantEntries: []types.UserEntry{ + { + Name: "testuser", + UID: 1000, + GID: 1000, + Gecos: "Test User", + Dir: "/home/testuser", + Shell: "/bin/bash", + }, + }, + }, + "Multiple_valid_entries": { + passwdLines: []string{ + "user1:x:1001:1001:User One:/home/user1:/bin/sh", + "user2:x:1002:1002:User Two:/home/user2:/bin/bash", + }, + wantEntries: []types.UserEntry{ + { + Name: "user1", + UID: 1001, + GID: 1001, + Gecos: "User One", + Dir: "/home/user1", + Shell: "/bin/sh", + }, + { + Name: "user2", + UID: 1002, + GID: 1002, + Gecos: "User Two", + Dir: "/home/user2", + Shell: "/bin/bash", + }, + }, + }, + "Skip_comment_and_empty_lines": { + passwdLines: []string{ + "", + "# This is a comment", + "user3:x:1003:1003:User Three:/home/user3:/bin/bash", + }, + wantEntries: []types.UserEntry{ + { + Name: "user3", + UID: 1003, + GID: 1003, + Gecos: "User Three", + Dir: "/home/user3", + Shell: "/bin/bash", + }, + }, + }, + + "Warn_if_invalid_entry_format_too_few_fields": { + passwdLines: []string{ + "badentry:x:1004:1004:User Four:/home/user4", // only 6 fields + "user4:x:1004:1004:User Four:/home/user4:/bin/bash", + }, + wantEntries: []types.UserEntry{ + { + Name: "user4", + UID: 1004, + GID: 1004, + Gecos: "User Four", + Dir: "/home/user4", + Shell: "/bin/bash", + }, + }, + }, + "Warn_if_invalid_uid": { + passwdLines: []string{ + "user5:x:notanuid:1005:User Five:/home/user5:/bin/bash", + "user6:x:1006:1006:User Six:/home/user6:/bin/bash", + }, + wantEntries: []types.UserEntry{ + { + Name: "user6", + UID: 1006, + GID: 1006, + Gecos: "User Six", + Dir: "/home/user6", + Shell: "/bin/bash", + }, + }, + }, + "Warn_if_invalid_gid": { + passwdLines: []string{ + "user7:x:1007:notagid:User Seven:/home/user7:/bin/bash", + "user8:x:1008:1008:User Eight:/home/user8:/bin/bash", + }, + wantEntries: []types.UserEntry{ + { + Name: "user8", + UID: 1008, + GID: 1008, + Gecos: "User Eight", + Dir: "/home/user8", + Shell: "/bin/bash", + }, + }, + }, + + // Error cases. + "Error_if_file_is_missing": { + wantErr: true, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + inputPasswdFilePath := filepath.Join(t.TempDir(), "passwd") + + if tc.passwdLines != nil { + tc.passwdLines = append(tc.passwdLines, "") + err := os.WriteFile(inputPasswdFilePath, []byte(strings.Join(tc.passwdLines, "\n")), 0600) + require.NoError(t, err, "Setup: Failed to write passwd file to %s", inputPasswdFilePath) + } + + ctx, entriesUnlock, err := localentries.ContextUserDBLocked( + context.Background(), + localentries.WithPasswdInputPath(inputPasswdFilePath), + localentries.WithMockUserDBLocking(), + ) + require.NoError(t, err, "Failed to lock the local entries") + t.Cleanup(func() { + err := entriesUnlock() + require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") + }) + + got, err := localentries.GetUserDBLocked(ctx).GetLocalUserEntries() + if tc.wantErr { + require.Error(t, err, "parseLocalPasswdFile() returned no error") + } else { + require.NoError(t, err, "parseLocalPasswdFile() returned error") + } + + require.Equal(t, tc.wantEntries, got, "entries") + }) + } +} From f456e144beaab748854f179abfc70fe3ffdad30b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 4 Jul 2025 17:20:51 +0200 Subject: [PATCH 0584/1670] users/localentries: Use UserDBLocked directly instead of a context We were hiding the locked entries in a context that served us as a wrapper, for doing some basic checks, but we can actually avoid it --- internal/users/db/migration.go | 6 +- internal/users/idgenerator.go | 46 ++++++------ internal/users/idgenerator_test.go | 25 +++---- internal/users/localentries/export_test.go | 6 +- internal/users/localentries/localgroups.go | 70 ++++++++++--------- .../users/localentries/localgroups_test.go | 52 ++++++-------- .../users/localentries/localusers_test.go | 6 +- internal/users/localentries/lockedentries.go | 60 ++++++---------- .../users/localentries/lockedentries_test.go | 23 ++---- internal/users/manager.go | 28 ++++---- internal/users/manager_test.go | 6 +- internal/users/tempentries/preauth_test.go | 6 +- internal/users/testutils.go | 6 +- 13 files changed, 151 insertions(+), 189 deletions(-) diff --git a/internal/users/db/migration.go b/internal/users/db/migration.go index aa30291637..1031cb1a8f 100644 --- a/internal/users/db/migration.go +++ b/internal/users/db/migration.go @@ -248,13 +248,13 @@ func renameUsersInGroupFile(oldNames, newNames []string) (err error) { return nil } - ctx, entriesUnlock, err := localentries.ContextUserDBLocked(context.Background()) + entries, entriesUnlock, err := localentries.WithUserDBLock() if err != nil { return err } defer func() { err = errors.Join(err, entriesUnlock()) }() - groups, err := localentries.GetGroupEntries(ctx) + groups, err := localentries.GetGroupEntries(entries) if err != nil { return err } @@ -268,7 +268,7 @@ func renameUsersInGroupFile(oldNames, newNames []string) (err error) { } } - return localentries.SaveGroupEntries(ctx, groups) + return localentries.SaveGroupEntries(entries, groups) } func removeGroupsWithNameConflicts(db queryable) error { diff --git a/internal/users/idgenerator.go b/internal/users/idgenerator.go index daed65c112..0bfce5fa08 100644 --- a/internal/users/idgenerator.go +++ b/internal/users/idgenerator.go @@ -14,8 +14,8 @@ import ( // IDGeneratorIface is the interface that must be implemented by the ID generator. type IDGeneratorIface interface { - GenerateUID(ctx context.Context, owner IDOwner) (uid uint32, cleanup func(), err error) - GenerateGID(ctx context.Context, owner IDOwner) (gid uint32, cleanup func(), err error) + GenerateUID(lockedEntries *localentries.UserDBLocked, owner IDOwner) (uid uint32, cleanup func(), err error) + GenerateGID(lockedEntries *localentries.UserDBLocked, owner IDOwner) (gid uint32, cleanup func(), err error) } // IDOwner is the interface that must be implemented by the IDs owner to provide @@ -49,8 +49,8 @@ type IDGenerator struct { const maxIDGenerateIterations = 1000 // GenerateUID generates a random UID in the configured range. -func (g *IDGenerator) GenerateUID(ctx context.Context, owner IDOwner) (uint32, func(), error) { - return g.generateID(ctx, owner, generateID{ +func (g *IDGenerator) GenerateUID(lockedEntries *localentries.UserDBLocked, owner IDOwner) (uint32, func(), error) { + return g.generateID(lockedEntries, owner, generateID{ idType: "UID", minID: g.UIDMin, maxID: g.UIDMax, @@ -60,8 +60,8 @@ func (g *IDGenerator) GenerateUID(ctx context.Context, owner IDOwner) (uint32, f } // GenerateGID generates a random GID in the configured range. -func (g *IDGenerator) GenerateGID(ctx context.Context, owner IDOwner) (uint32, func(), error) { - return g.generateID(ctx, owner, generateID{ +func (g *IDGenerator) GenerateGID(lockedEntries *localentries.UserDBLocked, owner IDOwner) (uint32, func(), error) { + return g.generateID(lockedEntries, owner, generateID{ idType: "GID", minID: g.GIDMin, maxID: g.GIDMax, @@ -74,16 +74,16 @@ func (g *IDGenerator) GenerateGID(ctx context.Context, owner IDOwner) (uint32, f type generateID struct { idType string minID, maxID uint32 - isAvailableID func(context.Context, uint32) (bool, error) - getUsedIDs func(context.Context, IDOwner) ([]uint32, error) + isAvailableID func(*localentries.UserDBLocked, uint32) (bool, error) + getUsedIDs func(*localentries.UserDBLocked, IDOwner) ([]uint32, error) } -func (g *IDGenerator) generateID(ctx context.Context, owner IDOwner, args generateID) (id uint32, cleanup func(), err error) { +func (g *IDGenerator) generateID(lockedEntries *localentries.UserDBLocked, owner IDOwner, args generateID) (id uint32, cleanup func(), err error) { if args.minID > args.maxID { return 0, nil, errors.New("minID must be less than or equal to maxID") } - usedIDs, err := args.getUsedIDs(ctx, owner) + usedIDs, err := args.getUsedIDs(lockedEntries, owner) if err != nil { return 0, nil, err } @@ -101,7 +101,7 @@ func (g *IDGenerator) generateID(ctx context.Context, owner IDOwner, args genera return 0, nil, err } - available, err := args.isAvailableID(ctx, id) + available, err := args.isAvailableID(lockedEntries, id) if err != nil { return 0, nil, err } @@ -112,7 +112,7 @@ func (g *IDGenerator) generateID(ctx context.Context, owner IDOwner, args genera usedIDs = slices.Insert(usedIDs, pos, id) // If the GID is not available, try the next candidate - log.Debugf(ctx, "%s %d is already used", args.idType, id) + log.Debugf(context.Background(), "%s %d is already used", args.idType, id) continue } @@ -167,8 +167,7 @@ func getIDCandidate(minID, maxID uint32, usedIDs []uint32) (id uint32, uniqueIDs return 0, -1, errors.New("no available ID in range") } -func (g *IDGenerator) isUIDAvailable(ctx context.Context, uid uint32) (bool, error) { - lockedEntries := localentries.GetUserDBLocked(ctx) +func (g *IDGenerator) isUIDAvailable(lockedEntries *localentries.UserDBLocked, uid uint32) (bool, error) { if unique, err := lockedEntries.IsUniqueUID(uid); !unique || err != nil { return false, err } @@ -176,8 +175,7 @@ func (g *IDGenerator) isUIDAvailable(ctx context.Context, uid uint32) (bool, err return true, nil } -func (g *IDGenerator) isGIDAvailable(ctx context.Context, gid uint32) (bool, error) { - lockedEntries := localentries.GetUserDBLocked(ctx) +func (g *IDGenerator) isGIDAvailable(lockedEntries *localentries.UserDBLocked, gid uint32) (bool, error) { if unique, err := lockedEntries.IsUniqueGID(gid); !unique || err != nil { return false, err } @@ -185,8 +183,8 @@ func (g *IDGenerator) isGIDAvailable(ctx context.Context, gid uint32) (bool, err return true, nil } -func (g *IDGenerator) getUsedIDs(ctx context.Context, owner IDOwner) ([]uint32, error) { - usedUIDs, err := g.getUsedUIDs(ctx, owner) +func (g *IDGenerator) getUsedIDs(lockedEntries *localentries.UserDBLocked, owner IDOwner) ([]uint32, error) { + usedUIDs, err := g.getUsedUIDs(lockedEntries, owner) if err != nil { return nil, err } @@ -194,7 +192,7 @@ func (g *IDGenerator) getUsedIDs(ctx context.Context, owner IDOwner) ([]uint32, // For the user ID we also need to exclude all the GIDs, since the user // private group ID will match its own uid, so if we don't do this, we may // have a clash later on, when trying to add the group for this user. - usedGIDs, err := g.getUsedGIDs(ctx, owner) + usedGIDs, err := g.getUsedGIDs(lockedEntries, owner) if err != nil { return nil, err } @@ -202,7 +200,7 @@ func (g *IDGenerator) getUsedIDs(ctx context.Context, owner IDOwner) ([]uint32, return append(usedUIDs, usedGIDs...), nil } -func (g *IDGenerator) getUsedUIDs(ctx context.Context, owner IDOwner) ([]uint32, error) { +func (g *IDGenerator) getUsedUIDs(lockedEntries *localentries.UserDBLocked, owner IDOwner) ([]uint32, error) { // Get the users from the authd database and pre-auth users. uids, err := owner.UsedUIDs() if err != nil { @@ -212,7 +210,7 @@ func (g *IDGenerator) getUsedUIDs(ctx context.Context, owner IDOwner) ([]uint32, // Get the user entries from the passwd file. We don't use NSS here, // because for picking the next higher ID we only want to consider the users // in /etc/passwd and in the authd database, not from other sources like LDAP. - userEntries, err := localentries.GetUserDBLocked(ctx).GetLocalUserEntries() + userEntries, err := lockedEntries.GetLocalUserEntries() if err != nil { return nil, err } @@ -223,7 +221,7 @@ func (g *IDGenerator) getUsedUIDs(ctx context.Context, owner IDOwner) ([]uint32, return uids, nil } -func (g *IDGenerator) getUsedGIDs(ctx context.Context, owner IDOwner) ([]uint32, error) { +func (g *IDGenerator) getUsedGIDs(lockedEntries *localentries.UserDBLocked, owner IDOwner) ([]uint32, error) { gids, err := owner.UsedGIDs() if err != nil { return nil, err @@ -233,7 +231,7 @@ func (g *IDGenerator) getUsedGIDs(ctx context.Context, owner IDOwner) ([]uint32, // because for picking the next higher ID we only want to consider the groups // in /etc/group and the users in /etc/group and in the authd database, not // from other sources like LDAP (in case merge method is used). - groupEntries, err := localentries.GetUserDBLocked(ctx).GetLocalGroupEntries() + groupEntries, err := lockedEntries.GetLocalGroupEntries() if err != nil { return nil, err } @@ -242,7 +240,7 @@ func (g *IDGenerator) getUsedGIDs(ctx context.Context, owner IDOwner) ([]uint32, } // And include users GIDs too. - userEntries, err := localentries.GetUserDBLocked(ctx).GetLocalUserEntries() + userEntries, err := lockedEntries.GetLocalUserEntries() if err != nil { return nil, err } diff --git a/internal/users/idgenerator_test.go b/internal/users/idgenerator_test.go index 06463de0c9..412552f3a4 100644 --- a/internal/users/idgenerator_test.go +++ b/internal/users/idgenerator_test.go @@ -1,13 +1,13 @@ package users import ( - "context" "errors" "fmt" "math" "testing" "github.com/stretchr/testify/require" + "github.com/ubuntu/authd/internal/users/localentries" ) func TestGetIDCandidate(t *testing.T) { @@ -133,19 +133,19 @@ func (m IDOwnerMock) UsedGIDs() ([]uint32, error) { return m.usedGIDs, nil } func TestGenerateIDMocked(t *testing.T) { t.Parallel() - allAvailableIDsFunc := func(ctx context.Context, u uint32) (bool, error) { + allAvailableIDsFunc := func(_ *localentries.UserDBLocked, u uint32) (bool, error) { return true, nil } - noAvailableIDFunc := func(ctx context.Context, u uint32) (bool, error) { + noAvailableIDFunc := func(_ *localentries.UserDBLocked, u uint32) (bool, error) { return false, nil } - noUsedIDFunc := func(ctx context.Context, o IDOwner) ([]uint32, error) { + noUsedIDFunc := func(_ *localentries.UserDBLocked, o IDOwner) ([]uint32, error) { return nil, nil } - getOwnerUsedUIDsFunc := func(ctx context.Context, o IDOwner) ([]uint32, error) { + getOwnerUsedUIDsFunc := func(_ *localentries.UserDBLocked, o IDOwner) ([]uint32, error) { return o.UsedUIDs() } - getOwnerUsedGIDsFunc := func(ctx context.Context, o IDOwner) ([]uint32, error) { + getOwnerUsedGIDsFunc := func(_ *localentries.UserDBLocked, o IDOwner) ([]uint32, error) { return o.UsedGIDs() } @@ -221,7 +221,7 @@ func TestGenerateIDMocked(t *testing.T) { genID: generateID{ idType: "UID", minID: 300, maxID: 303, - isAvailableID: func(ctx context.Context, u uint32) (bool, error) { + isAvailableID: func(_ *localentries.UserDBLocked, u uint32) (bool, error) { return u == 302, nil }, getUsedIDs: getOwnerUsedUIDsFunc, @@ -251,7 +251,7 @@ func TestGenerateIDMocked(t *testing.T) { genID: generateID{ idType: "UID", minID: 10000, maxID: 10010, - isAvailableID: func(ctx context.Context, u uint32) (bool, error) { + isAvailableID: func(_ *localentries.UserDBLocked, u uint32) (bool, error) { return false, errors.New("test error") }, getUsedIDs: noUsedIDFunc, @@ -263,7 +263,7 @@ func TestGenerateIDMocked(t *testing.T) { idType: "UID", minID: 10000, maxID: 10002, isAvailableID: allAvailableIDsFunc, - getUsedIDs: func(ctx context.Context, o IDOwner) ([]uint32, error) { + getUsedIDs: func(_ *localentries.UserDBLocked, o IDOwner) ([]uint32, error) { return []uint32{10000, 10001, 10002}, nil }, }, @@ -282,7 +282,7 @@ func TestGenerateIDMocked(t *testing.T) { genID: generateID{ idType: "ID", minID: 10000, maxID: math.MaxUint32, - isAvailableID: func(ctx context.Context, u uint32) (bool, error) { + isAvailableID: func(_ *localentries.UserDBLocked, u uint32) (bool, error) { return u < 10000, nil }, getUsedIDs: noUsedIDFunc, @@ -294,7 +294,7 @@ func TestGenerateIDMocked(t *testing.T) { idType: "UID", minID: 10000, maxID: 10010, isAvailableID: allAvailableIDsFunc, - getUsedIDs: func(ctx context.Context, o IDOwner) ([]uint32, error) { + getUsedIDs: func(_ *localentries.UserDBLocked, o IDOwner) ([]uint32, error) { return nil, errors.New("usedIDs error") }, }, @@ -323,7 +323,8 @@ func TestGenerateIDMocked(t *testing.T) { tc.genID.idType) } - id, cleanup, err := tc.generator.generateID(context.Background(), + lockedMock := &localentries.UserDBLocked{} + id, cleanup, err := tc.generator.generateID(lockedMock, tc.owner, tc.genID) if tc.wantErr { require.Error(t, err, "Expected error but got none") diff --git a/internal/users/localentries/export_test.go b/internal/users/localentries/export_test.go index bd23a9fdb7..1dcb0ec3b7 100644 --- a/internal/users/localentries/export_test.go +++ b/internal/users/localentries/export_test.go @@ -1,8 +1,6 @@ package localentries import ( - "context" - userslocking "github.com/ubuntu/authd/internal/users/locking" "github.com/ubuntu/authd/internal/users/types" ) @@ -60,6 +58,6 @@ func GroupFileBackupPath(groupFilePath string) string { } // ValidateChangedGroups validates the new groups given the current, changed and new groups. -func ValidateChangedGroups(ctx context.Context, currentGroups, newGroups []types.GroupEntry) error { - return validateChangedGroups(ctx, currentGroups, newGroups) +func ValidateChangedGroups(currentGroups, newGroups []types.GroupEntry) error { + return validateChangedGroups(currentGroups, newGroups) } diff --git a/internal/users/localentries/localgroups.go b/internal/users/localentries/localgroups.go index 24a648b97b..e6e086082d 100644 --- a/internal/users/localentries/localgroups.go +++ b/internal/users/localentries/localgroups.go @@ -20,17 +20,19 @@ import ( ) // GetGroupEntries returns a copy of the current group entries. -func GetGroupEntries(ctx context.Context) (entries []types.GroupEntry, err error) { +func GetGroupEntries(dbLocked *UserDBLocked) (entries []types.GroupEntry, err error) { defer decorate.OnError(&err, "could not get groups") - unlock := GetUserDBLocked(ctx).lockGroupFile() + dbLocked.MustBeLocked() + + unlock := dbLocked.lockGroupFile() defer unlock() - return getGroupEntriesWithContext(ctx) + return getGroupEntriesWithContext(dbLocked) } -func getGroupEntriesWithContext(ctx context.Context) (entries []types.GroupEntry, err error) { - entries, err = GetUserDBLocked(ctx).GetLocalGroupEntries() +func getGroupEntriesWithContext(dbLocked *UserDBLocked) (entries []types.GroupEntry, err error) { + entries, err = dbLocked.GetLocalGroupEntries() if err != nil { return nil, err } @@ -39,30 +41,34 @@ func getGroupEntriesWithContext(ctx context.Context) (entries []types.GroupEntry } // SaveGroupEntries saves the provided group entries to the local group file. -func SaveGroupEntries(ctx context.Context, entries []types.GroupEntry) (err error) { +func SaveGroupEntries(dbLocked *UserDBLocked, entries []types.GroupEntry) (err error) { defer decorate.OnError(&err, "could not save groups") - unlock := GetUserDBLocked(ctx).lockGroupFile() + dbLocked.MustBeLocked() + + unlock := dbLocked.lockGroupFile() defer unlock() - return saveLocalGroups(ctx, entries) + return saveLocalGroups(dbLocked, entries) } // UpdateGroups updates the local groups for a user, adding them to the groups in // newGroups which they are not already part of, and removing them from the // groups in oldGroups which are not in newGroups. -func UpdateGroups(ctx context.Context, username string, newGroups []string, oldGroups []string) (err error) { - log.Debugf(ctx, "Updating local groups for user %q, new groups: %v, old groups: %v", username, newGroups, oldGroups) +func UpdateGroups(dbLocked *UserDBLocked, username string, newGroups []string, oldGroups []string) (err error) { + log.Debugf(context.Background(), "Updating local groups for user %q, new groups: %v, old groups: %v", username, newGroups, oldGroups) defer decorate.OnError(&err, "could not update local groups for user %q", username) - unlock := GetUserDBLocked(ctx).lockGroupFile() + dbLocked.MustBeLocked() + + unlock := dbLocked.lockGroupFile() defer unlock() if len(newGroups) == 0 && len(oldGroups) == 0 { return nil } - allGroups, err := getGroupEntriesWithContext(ctx) + allGroups, err := getGroupEntriesWithContext(dbLocked) if err != nil { return err } @@ -73,11 +79,11 @@ func UpdateGroups(ctx context.Context, username string, newGroups []string, oldG }) groupsToAdd := sliceutils.Difference(newGroups, currentGroupsNames) - log.Debugf(ctx, "Adding %q to local groups: %v", username, groupsToAdd) + log.Debugf(context.Background(), "Adding %q to local groups: %v", username, groupsToAdd) groupsToRemove := sliceutils.Difference(oldGroups, newGroups) // Only remove user from groups which they are part of groupsToRemove = sliceutils.Intersection(groupsToRemove, currentGroupsNames) - log.Debugf(ctx, "Removing %q from local groups: %v", username, groupsToRemove) + log.Debugf(context.Background(), "Removing %q from local groups: %v", username, groupsToRemove) if len(groupsToRemove) == 0 && len(groupsToAdd) == 0 { return nil @@ -108,7 +114,7 @@ func UpdateGroups(ctx context.Context, username string, newGroups []string, oldG group.Users = append(group.Users, username) } - return saveLocalGroups(ctx, allGroups) + return saveLocalGroups(dbLocked, allGroups) } func parseLocalGroups(groupPath string) (groups []types.GroupEntry, invalidEntries []invalidEntry, err error) { @@ -188,12 +194,12 @@ func groupFileBackupPath(groupPath string) string { return fmt.Sprintf("%s-", groupPath) } -func formatGroupEntries(ctx context.Context, groups []types.GroupEntry) string { +func formatGroupEntries(dbLocked *UserDBLocked, groups []types.GroupEntry) string { groupLines := sliceutils.Map(groups, func(group types.GroupEntry) string { return group.String() }) - for _, entry := range GetUserDBLocked(ctx).localGroupInvalidEntries { + for _, entry := range dbLocked.localGroupInvalidEntries { groupLines = slices.Insert(groupLines, min(entry.lineNum, len(groupLines)-1), entry.line) } @@ -204,43 +210,43 @@ func formatGroupEntries(ctx context.Context, groups []types.GroupEntry) string { return strings.Join(groupLines, "\n") } -func saveLocalGroups(ctx context.Context, groups []types.GroupEntry) (err error) { - lockedEntries := GetUserDBLocked(ctx) +func saveLocalGroups(dbLocked *UserDBLocked, groups []types.GroupEntry) (err error) { + lockedEntries := dbLocked inputPath := lockedEntries.options.inputGroupPath groupPath := lockedEntries.options.outputGroupPath defer decorate.OnError(&err, "could not write local groups to %q", groupPath) - currentGroups, err := getGroupEntriesWithContext(ctx) + currentGroups, err := getGroupEntriesWithContext(dbLocked) if err != nil { return err } if slices.EqualFunc(currentGroups, groups, types.GroupEntry.Equals) { - log.Debugf(ctx, "Nothing to do, groups are equal") + log.Debugf(context.Background(), "Nothing to do, groups are equal") return nil } - if err := validateChangedGroups(ctx, currentGroups, groups); err != nil { - log.Debugf(ctx, "New groups are not valid: %v", err) + if err := validateChangedGroups(currentGroups, groups); err != nil { + log.Debugf(context.Background(), "New groups are not valid: %v", err) return err } backupPath := groupFileBackupPath(groupPath) - groupsEntries := formatGroupEntries(ctx, groups) + groupsEntries := formatGroupEntries(dbLocked, groups) - log.Debugf(ctx, "Saving group entries %#v to %q", groups, groupPath) + log.Debugf(context.Background(), "Saving group entries %#v to %q", groups, groupPath) if len(groupsEntries) > 0 { - log.Debugf(ctx, "Group file content:\n%s", groupsEntries) + log.Debugf(context.Background(), "Group file content:\n%s", groupsEntries) } if err := os.Remove(backupPath); err != nil && !errors.Is(err, os.ErrNotExist) { - log.Warningf(ctx, "Failed to remove group file backup: %v", err) + log.Warningf(context.Background(), "Failed to remove group file backup: %v", err) } - log.Debugf(ctx, "Backing up %q to %q", inputPath, backupPath) + log.Debugf(context.Background(), "Backing up %q to %q", inputPath, backupPath) if err := fileutils.CopyFile(inputPath, backupPath); err != nil { - log.Warningf(ctx, "Failed make a backup for the group file: %v", err) + log.Warningf(context.Background(), "Failed make a backup for the group file: %v", err) } tempPath := groupFileTemporaryPath(groupPath) @@ -257,15 +263,15 @@ func saveLocalGroups(ctx context.Context, groups []types.GroupEntry) (err error) return nil } -func validateChangedGroups(ctx context.Context, currentGroups, newGroups []types.GroupEntry) error { +func validateChangedGroups(currentGroups, newGroups []types.GroupEntry) error { changedGroups := sliceutils.DifferenceFunc(newGroups, currentGroups, types.GroupEntry.Equals) if len(changedGroups) == 0 { - log.Debugf(ctx, "No new groups added to validate") + log.Debugf(context.Background(), "No new groups added to validate") return nil } - log.Debugf(ctx, "Groups added or modified: %#v", changedGroups) + log.Debugf(context.Background(), "Groups added or modified: %#v", changedGroups) if err := types.ValidateGroupEntries(changedGroups); err != nil { // One of the group that has been changed is not valid. diff --git a/internal/users/localentries/localgroups_test.go b/internal/users/localentries/localgroups_test.go index ffef3bd83a..67da754491 100644 --- a/internal/users/localentries/localgroups_test.go +++ b/internal/users/localentries/localgroups_test.go @@ -1,7 +1,6 @@ package localentries_test import ( - "context" "fmt" "os" "path/filepath" @@ -113,8 +112,7 @@ func TestUpdatelocalentries(t *testing.T) { defer localentriestestutils.RequireGroupFile(t, outputGroupFilePath, golden.Path(t)) - ctx, entriesUnlock, err := localentries.ContextUserDBLocked( - context.Background(), + entries, entriesUnlock, err := localentries.WithUserDBLock( localentries.WithGroupInputPath(inputGroupFilePath), localentries.WithGroupOutputPath(outputGroupFilePath), localentries.WithMockUserDBLocking(), @@ -125,7 +123,7 @@ func TestUpdatelocalentries(t *testing.T) { require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") }) - err = localentries.UpdateGroups(ctx, tc.username, tc.newGroups, tc.oldGroups) + err = localentries.UpdateGroups(entries, tc.username, tc.newGroups, tc.oldGroups) if tc.wantErr { require.Error(t, err, "Updatelocalentries should have failed") } else { @@ -307,8 +305,7 @@ func TestGetAndSaveLocalGroups(t *testing.T) { defer localentriestestutils.RequireGroupFile(t, outputGroupFilePath, golden.Path(t)) - ctx, entriesUnlock, err := localentries.ContextUserDBLocked( - context.Background(), + entries, entriesUnlock, err := localentries.WithUserDBLock( localentries.WithGroupInputPath(inputGroupFilePath), localentries.WithGroupOutputPath(outputGroupFilePath), localentries.WithMockUserDBLocking(), @@ -319,7 +316,7 @@ func TestGetAndSaveLocalGroups(t *testing.T) { require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") }() - groups, err := localentries.GetGroupEntries(ctx) + groups, err := localentries.GetGroupEntries(entries) if tc.wantGetErr { require.Error(t, err, "GetEntries should return an error, but did not") return @@ -338,10 +335,10 @@ func TestGetAndSaveLocalGroups(t *testing.T) { groups[idx].Users = append(groups[idx].Users, userNames...) } - err = localentries.SaveGroupEntries(ctx, groups) + err = localentries.SaveGroupEntries(entries, groups) if tc.wantSetErr { require.Error(t, err, "SaveEntries should have failed") - updatedGroups, err := localentries.GetGroupEntries(ctx) + updatedGroups, err := localentries.GetGroupEntries(entries) require.NoError(t, err, "GetEntries should not return an error, but did") require.Equal(t, initialGroups, updatedGroups, "Cached groups have been changed") return @@ -353,7 +350,7 @@ func TestGetAndSaveLocalGroups(t *testing.T) { require.NoError(t, err, "SaveEntries should not have failed") // Ensure we also saved the cached version of the groups... - updatedGroups, err := localentries.GetGroupEntries(ctx) + updatedGroups, err := localentries.GetGroupEntries(entries) require.NoError(t, err, "GetEntries should not return an error, but did") require.Equal(t, groups, updatedGroups, "Cached groups are not saved") @@ -399,15 +396,14 @@ func TestRacingGroupsLockingActions(t *testing.T) { wantGroup = types.GroupEntry{Name: "localgroup1", GID: 41, Passwd: "x"} } - ctx, entriesUnlock, err := localentries.ContextUserDBLocked( - context.Background(), opts...) + entries, entriesUnlock, err := localentries.WithUserDBLock(opts...) require.NoError(t, err, "Failed to lock the local entries") t.Cleanup(func() { err := entriesUnlock() require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") }) - groups, err := localentries.GetGroupEntries(ctx) + groups, err := localentries.GetGroupEntries(entries) require.NoError(t, err, "GetEntries should not return an error, but did") require.NotEmpty(t, groups, "Got empty groups (test groups: %v)", useTestGroupFile) require.Contains(t, groups, wantGroup, "Expected group was not found (test groups: %v)", useTestGroupFile) @@ -419,7 +415,7 @@ func TestRacingGroupsLockingActions(t *testing.T) { wg.Wait() // Get a last unlock function, to see if we're all good... - ctx, entriesUnlock, err := localentries.ContextUserDBLocked(context.Background()) + entries, entriesUnlock, err := localentries.WithUserDBLock() require.NoError(t, err, "Failed to lock the local entries") require.NoError(t, err, "Unlock should not fail to lock the users group") @@ -428,21 +424,21 @@ func TestRacingGroupsLockingActions(t *testing.T) { require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") // Ensure that we had cleaned up all the locks correctly! - require.Panics(t, func() { _, _ = localentries.GetGroupEntries(ctx) }) + require.Panics(t, func() { _, _ = localentries.GetGroupEntries(entries) }) }) } func TestLockedInvalidActions(t *testing.T) { // This cannot be parallel - require.Panics(t, func() { _ = localentries.UpdateGroups(context.Background(), "", nil, nil) }, + require.Panics(t, func() { _ = localentries.UpdateGroups(nil, "", nil, nil) }, "Update should panic but did not") - require.Panics(t, func() { _, _ = localentries.GetGroupEntries(context.Background()) }, + require.Panics(t, func() { _, _ = localentries.GetGroupEntries(nil) }, "GetEntries should panic but did not") - require.Panics(t, func() { _ = localentries.SaveGroupEntries(context.Background(), nil) }, + require.Panics(t, func() { _ = localentries.SaveGroupEntries(nil, nil) }, "SaveEntries should panic but did not") - ctx, entriesUnlock, err := localentries.ContextUserDBLocked(context.Background()) + entries, entriesUnlock, err := localentries.WithUserDBLock() require.NoError(t, err, "Failed to lock the local entries") err = entriesUnlock() @@ -451,16 +447,16 @@ func TestLockedInvalidActions(t *testing.T) { err = entriesUnlock() require.Error(t, err, "Unlocking twice should fail") - require.Panics(t, func() { _ = localentries.UpdateGroups(ctx, "", nil, nil) }, + require.Panics(t, func() { _ = localentries.UpdateGroups(entries, "", nil, nil) }, "Update should panic but did not") - require.Panics(t, func() { _, _ = localentries.GetGroupEntries(ctx) }, + require.Panics(t, func() { _, _ = localentries.GetGroupEntries(entries) }, "GetEntries should panic but did not") - require.Panics(t, func() { _ = localentries.SaveGroupEntries(ctx, nil) }, + require.Panics(t, func() { _ = localentries.SaveGroupEntries(entries, nil) }, "SaveEntries should panic but did not") // This is to ensure that we're in a good state, despite the actions above for range 10 { - _, entriesUnlock, err := localentries.ContextUserDBLocked(context.Background()) + _, entriesUnlock, err := localentries.WithUserDBLock() require.NoError(t, err, "Failed to lock the local entries") defer func() { err := entriesUnlock() @@ -624,18 +620,10 @@ func TestValidateChangedGroups(t *testing.T) { }, } - ctx, entriesUnlock, err := localentries.ContextUserDBLocked(context.Background()) - require.NoError(t, err, "Failed to lock the local entries") - t.Cleanup(func() { - err := entriesUnlock() - require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") - }) - for name, tc := range tests { t.Run(name, func(t *testing.T) { t.Parallel() - err := localentries.ValidateChangedGroups(ctx, tc.currentGroups, - tc.newGroups) + err := localentries.ValidateChangedGroups(tc.currentGroups, tc.newGroups) if tc.wantErr { require.Error(t, err, "expected error but got nil") t.Logf("Validation failed with error: %v", err) diff --git a/internal/users/localentries/localusers_test.go b/internal/users/localentries/localusers_test.go index 8b96821757..c422bc2ab9 100644 --- a/internal/users/localentries/localusers_test.go +++ b/internal/users/localentries/localusers_test.go @@ -1,7 +1,6 @@ package localentries_test import ( - "context" "os" "path/filepath" "strings" @@ -146,8 +145,7 @@ func TestParseLocalPasswdFile(t *testing.T) { require.NoError(t, err, "Setup: Failed to write passwd file to %s", inputPasswdFilePath) } - ctx, entriesUnlock, err := localentries.ContextUserDBLocked( - context.Background(), + le, entriesUnlock, err := localentries.WithUserDBLock( localentries.WithPasswdInputPath(inputPasswdFilePath), localentries.WithMockUserDBLocking(), ) @@ -157,7 +155,7 @@ func TestParseLocalPasswdFile(t *testing.T) { require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") }) - got, err := localentries.GetUserDBLocked(ctx).GetLocalUserEntries() + got, err := le.GetLocalUserEntries() if tc.wantErr { require.Error(t, err, "parseLocalPasswdFile() returned no error") } else { diff --git a/internal/users/localentries/lockedentries.go b/internal/users/localentries/lockedentries.go index 8f692ed960..745a3ae9d6 100644 --- a/internal/users/localentries/lockedentries.go +++ b/internal/users/localentries/lockedentries.go @@ -104,48 +104,35 @@ type UserDBLocked struct { options options } -type userDBLockKey struct{} - -// GetUserDBLocked retrieves the [WithLock] from context, if present. -func GetUserDBLocked(ctx context.Context) *UserDBLocked { - if l, ok := ctx.Value(userDBLockKey{}).(*UserDBLocked); ok { - l.MustBeLocked() - return l - } - return nil -} - -// ContextUserDBLocked gets a context instance with a lock on the system's +// WithUserDBLock gets an [UserDBLocked] instance with a lock on the system's // user database. // It returns an unlock function that should be called to unlock it. // -// It can called safely multiple times and will return always a new context that -// is always bound to the same instance of [UserDBLocked] with increased -// reference counting (that is released through the unlock function). -// Use [GetUserDBLocked] to retrieve it. -func ContextUserDBLocked(parent context.Context, args ...Option) (ctx context.Context, unlock func() error, err error) { +// It can called safely multiple times and will return always a the same +// [UserDBLocked] with increased reference counting (that must be released +// through returned the unlock function). +func WithUserDBLock(args ...Option) (userDB *UserDBLocked, unlock func() error, err error) { defer decorate.OnError(&err, "could not lock local groups") - var entries *UserDBLocked unlock = func() error { - entries.mu.Lock() - defer entries.mu.Unlock() + userDB.mu.Lock() + defer userDB.mu.Unlock() - if entries.refCount == 0 { + if userDB.refCount == 0 { return fmt.Errorf("groups were already unlocked") } - entries.refCount-- - if entries.refCount != 0 { + userDB.refCount-- + if userDB.refCount != 0 { return nil } log.Debug(context.Background(), "Unlocking local entries") - entries.userEntries = nil - entries.localGroupEntries = nil - entries.groupEntries = nil + userDB.userEntries = nil + userDB.localGroupEntries = nil + userDB.groupEntries = nil - return entries.options.writeUnlockFunc() + return userDB.options.writeUnlockFunc() } opts := defaultOptions @@ -160,15 +147,14 @@ func ContextUserDBLocked(parent context.Context, args ...Option) (ctx context.Co } } - entries = opts.userDBLocked - ctx = context.WithValue(parent, userDBLockKey{}, entries) + userDB = opts.userDBLocked - entries.mu.Lock() - defer entries.mu.Unlock() + userDB.mu.Lock() + defer userDB.mu.Unlock() - if entries.refCount != 0 { - entries.refCount++ - return ctx, unlock, nil + if userDB.refCount != 0 { + userDB.refCount++ + return userDB, unlock, nil } log.Debug(context.Background(), "Locking local entries") @@ -177,10 +163,10 @@ func ContextUserDBLocked(parent context.Context, args ...Option) (ctx context.Co return nil, nil, err } - entries.options = opts - entries.refCount++ + userDB.options = opts + userDB.refCount++ - return ctx, unlock, nil + return userDB, unlock, nil } // MustBeLocked ensures wether the entries are locked. diff --git a/internal/users/localentries/lockedentries_test.go b/internal/users/localentries/lockedentries_test.go index cd6251157c..38408912a3 100644 --- a/internal/users/localentries/lockedentries_test.go +++ b/internal/users/localentries/lockedentries_test.go @@ -1,7 +1,6 @@ package localentries_test import ( - "context" "crypto/rand" "fmt" "path/filepath" @@ -26,8 +25,7 @@ func TestEntriesWithLockInvalidActions(t *testing.T) { require.Panics(t, func() { _, _ = (&localentries.UserDBLocked{}).GetLocalGroupEntries() }, "GetLocalGroupEntries should panic but did not") - ctx, unlock, err := localentries.ContextUserDBLocked(context.Background()) - le := localentries.GetUserDBLocked(ctx) + le, unlock, err := localentries.WithUserDBLock() require.NotNil(t, le, "GetWithLock should not return nil but it did") require.NoError(t, err, "Setup: failed to lock the users group") @@ -37,8 +35,6 @@ func TestEntriesWithLockInvalidActions(t *testing.T) { err = unlock() require.Error(t, err, "Unlocking twice should fail") - require.Panics(t, func() { _ = localentries.GetUserDBLocked(ctx) }, - "GetWithLock should panic but did not") require.Panics(t, func() { _, _ = le.GetUserEntries() }, "GetUserEntries should panic but did not") require.Panics(t, func() { _, _ = le.GetGroupEntries() }, @@ -48,7 +44,7 @@ func TestEntriesWithLockInvalidActions(t *testing.T) { // This is to ensure that we're in a good state, despite the actions above for range 10 { - _, unlock, err = localentries.ContextUserDBLocked(context.Background()) + _, unlock, err = localentries.WithUserDBLock() require.NoError(t, err, "Failed to lock the users group") defer func() { err := unlock() @@ -88,8 +84,7 @@ func TestRacingEntriesLockingActions(t *testing.T) { wantGroup = types.GroupEntry{Name: "localgroup1", GID: 41, Passwd: "x"} } - ctx, entriesUnlock, err := localentries.ContextUserDBLocked( - context.Background(), opts...) + ctx, entriesUnlock, err := localentries.WithUserDBLock(opts...) require.NoError(t, err, "Failed to lock the local entries") groups, err := localentries.GetGroupEntries(ctx) @@ -106,7 +101,7 @@ func TestRacingEntriesLockingActions(t *testing.T) { func TestIsUniqueUserName(t *testing.T) { t.Parallel() - ctx, unlock, err := localentries.ContextUserDBLocked(context.Background()) + le, unlock, err := localentries.WithUserDBLock() require.NoError(t, err, "Setup: NewUserDBLocked should not fail to lock the users group") t.Cleanup(func() { @@ -114,7 +109,6 @@ func TestIsUniqueUserName(t *testing.T) { require.NoError(t, err, "TearDown: Unlock should not fail, but it did") }) - le := localentries.GetUserDBLocked(ctx) users, err := le.GetUserEntries() require.NoError(t, err, "Setup: GetUserEntries should not fail, but it did") @@ -142,7 +136,7 @@ func TestIsUniqueUserName(t *testing.T) { func TestIsUniqueGroupName(t *testing.T) { t.Parallel() - ctx, unlock, err := localentries.ContextUserDBLocked(context.Background()) + le, unlock, err := localentries.WithUserDBLock() require.NoError(t, err, "Setup: NewUserDBLocked should not fail to lock the users group") t.Cleanup(func() { @@ -150,7 +144,6 @@ func TestIsUniqueGroupName(t *testing.T) { require.NoError(t, err, "TearDown: Unlock should not fail, but it did") }) - le := localentries.GetUserDBLocked(ctx) groups, err := le.GetGroupEntries() require.NoError(t, err, "Setup: GetGroupEntries should not fail, but it did") @@ -178,7 +171,7 @@ func TestIsUniqueGroupName(t *testing.T) { func TestIsUniqueUID(t *testing.T) { t.Parallel() - ctx, unlock, err := localentries.ContextUserDBLocked(context.Background()) + le, unlock, err := localentries.WithUserDBLock() require.NoError(t, err, "Setup: NewUserDBLocked should not fail to lock the users group") t.Cleanup(func() { @@ -186,7 +179,6 @@ func TestIsUniqueUID(t *testing.T) { require.NoError(t, err, "TearDown: Unlock should not fail, but it did") }) - le := localentries.GetUserDBLocked(ctx) users, err := le.GetUserEntries() require.NoError(t, err, "Setup: GetUserEntries should not fail, but it did") @@ -223,7 +215,7 @@ func TestIsUniqueUID(t *testing.T) { func TestIsUniqueGID(t *testing.T) { t.Parallel() - ctx, unlock, err := localentries.ContextUserDBLocked(context.Background()) + le, unlock, err := localentries.WithUserDBLock() require.NoError(t, err, "Setup: NewUserDBLocked should not fail to lock the users group") t.Cleanup(func() { @@ -231,7 +223,6 @@ func TestIsUniqueGID(t *testing.T) { require.NoError(t, err, "TearDown: Unlock should not fail, but it did") }) - le := localentries.GetUserDBLocked(ctx) groups, err := le.GetGroupEntries() require.NoError(t, err, "Setup: GetUserEntries should not fail, but it did") diff --git a/internal/users/manager.go b/internal/users/manager.go index 2827fab75c..05d9749ff7 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -144,13 +144,13 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { uid = preauthUID defer cleanup() } else { - ctx, unlockEntries, err := localentries.ContextUserDBLocked(context.Background()) + lockedEntries, unlockEntries, err := localentries.WithUserDBLock() if err != nil { return err } defer func() { err = errors.Join(err, unlockEntries()) }() - unique, err := localentries.GetUserDBLocked(ctx).IsUniqueUserName(u.Name) + unique, err := lockedEntries.IsUniqueUserName(u.Name) if err != nil { return err } @@ -160,7 +160,7 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { } var cleanupUID func() - uid, cleanupUID, err = m.idGenerator.GenerateUID(ctx, m) + uid, cleanupUID, err = m.idGenerator.GenerateUID(lockedEntries, m) if err != nil { return err } @@ -220,25 +220,23 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { } if len(newGroups) > 0 { - ctx, unlockEntries, err := localentries.ContextUserDBLocked(context.Background()) + lockedEntries, unlockEntries, err := localentries.WithUserDBLock() if err != nil { return err } defer func() { err = errors.Join(err, unlockEntries()) }() - lockedEntries := localentries.GetUserDBLocked(ctx) - for _, g := range newGroups { unique, err := lockedEntries.IsUniqueGroupName(g.Name) if err != nil { return err } if !unique { - log.Warningf(ctx, "Group %q already exists", g.Name) + log.Warningf(context.Background(), "Group %q already exists", g.Name) return fmt.Errorf("another system group exists with %q name", g.Name) } - gid, cleanupGID, err := m.idGenerator.GenerateGID(ctx, m) + gid, cleanupGID, err := m.idGenerator.GenerateGID(lockedEntries, m) if err != nil { return err } @@ -246,7 +244,7 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { g.GID = &gid groupRows = append(groupRows, db.NewGroupRow(g.Name, *g.GID, g.UGID)) - log.Debugf(ctx, "Using new GID %d for group %q", gid, u.Name) + log.Debugf(context.Background(), "Using new GID %d for group %q", gid, u.Name) } } @@ -263,13 +261,13 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { } // Update local groups. - ctx, unlockEntries, err := localentries.ContextUserDBLocked(context.Background()) + lockedEntries, unlockEntries, err := localentries.WithUserDBLock() if err != nil { return err } defer func() { err = errors.Join(err, unlockEntries()) }() - if err := localentries.UpdateGroups(ctx, u.Name, localGroups, oldLocalGroups); err != nil { + if err := localentries.UpdateGroups(lockedEntries, u.Name, localGroups, oldLocalGroups); err != nil { return err } @@ -550,13 +548,13 @@ func (m *Manager) RegisterUserPreAuth(name string) (uid uint32, err error) { return 0, err } - ctx, unlockEntries, err := localentries.ContextUserDBLocked(context.Background()) + lockedEntries, unlockEntries, err := localentries.WithUserDBLock() if err != nil { return 0, err } defer func() { err = errors.Join(err, unlockEntries()) }() - unique, err := localentries.GetUserDBLocked(ctx).IsUniqueUserName(name) + unique, err := lockedEntries.IsUniqueUserName(name) if err != nil { return 0, err } @@ -564,7 +562,7 @@ func (m *Manager) RegisterUserPreAuth(name string) (uid uint32, err error) { return 0, fmt.Errorf("another system user exists with %q name", name) } - uid, cleanupUID, err := m.idGenerator.GenerateUID(ctx, m) + uid, cleanupUID, err := m.idGenerator.GenerateUID(lockedEntries, m) if err != nil { return 0, err } @@ -574,6 +572,6 @@ func (m *Manager) RegisterUserPreAuth(name string) (uid uint32, err error) { return 0, err } - log.Debugf(ctx, "Using new UID %d for temporary user %q", uid, name) + log.Debugf(context.Background(), "Using new UID %d for temporary user %q", uid, name) return uid, nil } diff --git a/internal/users/manager_test.go b/internal/users/manager_test.go index 92f67be9f5..63cbca072c 100644 --- a/internal/users/manager_test.go +++ b/internal/users/manager_test.go @@ -354,9 +354,8 @@ func TestConcurrentUserUpdate(t *testing.T) { const registeredUserPrefix = "authd-test-maybe-pre-check-user" - ctx, entriesUnlock, err := localentries.ContextUserDBLocked(context.Background()) + lockedEntries, entriesUnlock, err := localentries.WithUserDBLock() require.NoError(t, err, "Failed to lock the local entries") - lockedEntries := localentries.GetUserDBLocked(ctx) systemPasswd, err := lockedEntries.GetUserEntries() require.NoError(t, err, "GetPasswdEntries should not fail but it did") systemGroups, err := lockedEntries.GetGroupEntries() @@ -509,14 +508,13 @@ func TestConcurrentUserUpdate(t *testing.T) { require.NoError(t, err, "AllGroups should not fail but it did") require.Len(t, groups, nIterations*3+1, "Number of registered groups mismatch") - ctx, entriesUnlock, err := localentries.ContextUserDBLocked(context.Background()) + lockedEntries, entriesUnlock, err := localentries.WithUserDBLock() require.NoError(t, err, "Failed to lock the local entries") defer func() { err := entriesUnlock() require.NoError(t, err, "entriesUnlock should not fail to unlock the local entries") }() - lockedEntries := localentries.GetUserDBLocked(ctx) localPasswd, err := lockedEntries.GetUserEntries() require.NoError(t, err, "GetPasswdEntries should not fail but it did") localGroups, err := lockedEntries.GetGroupEntries() diff --git a/internal/users/tempentries/preauth_test.go b/internal/users/tempentries/preauth_test.go index 578db8bbf9..53ed314e05 100644 --- a/internal/users/tempentries/preauth_test.go +++ b/internal/users/tempentries/preauth_test.go @@ -1,7 +1,6 @@ package tempentries_test import ( - "context" "fmt" "slices" "strings" @@ -10,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ubuntu/authd/internal/testutils/golden" "github.com/ubuntu/authd/internal/users" + "github.com/ubuntu/authd/internal/users/localentries" userslocking "github.com/ubuntu/authd/internal/users/locking" "github.com/ubuntu/authd/internal/users/tempentries" "github.com/ubuntu/authd/internal/users/types" @@ -107,7 +107,7 @@ func TestPreAuthUser(t *testing.T) { for idx, loginName := range tc.users { t.Logf("Registering user %q", loginName) - uid, _, err := idGeneratorMock.GenerateUID(context.Background(), nil) + uid, _, err := idGeneratorMock.GenerateUID(&localentries.UserDBLocked{}, nil) require.NoError(t, err, "GenerateUID should not return an error, but it did") if tc.wantPanic[idx] { @@ -209,7 +209,7 @@ func TestPreAuthUserByIDAndName(t *testing.T) { records := tempentries.NewPreAuthUserRecords() if tc.registerUser { - uid, _, err := idGeneratorMock.GenerateUID(context.Background(), nil) + uid, _, err := idGeneratorMock.GenerateUID(&localentries.UserDBLocked{}, nil) require.NoError(t, err, "GenerateUID should not return an error, but it did") err = records.RegisterPreAuthUser(loginName, uid) diff --git a/internal/users/testutils.go b/internal/users/testutils.go index 855bce7518..021b20d3a9 100644 --- a/internal/users/testutils.go +++ b/internal/users/testutils.go @@ -1,10 +1,10 @@ package users import ( - "context" "fmt" "github.com/ubuntu/authd/internal/testsdetection" + "github.com/ubuntu/authd/internal/users/localentries" ) // IDGeneratorMock is a mock implementation of the IDGenerator interface. @@ -14,7 +14,7 @@ type IDGeneratorMock struct { } // GenerateUID generates a UID. -func (g *IDGeneratorMock) GenerateUID(_ context.Context, _ IDOwner) (uint32, func(), error) { +func (g *IDGeneratorMock) GenerateUID(_ *localentries.UserDBLocked, _ IDOwner) (uint32, func(), error) { testsdetection.MustBeTesting() if len(g.UIDsToGenerate) == 0 { @@ -26,7 +26,7 @@ func (g *IDGeneratorMock) GenerateUID(_ context.Context, _ IDOwner) (uint32, fun } // GenerateGID generates a GID. -func (g *IDGeneratorMock) GenerateGID(_ context.Context, _ IDOwner) (uint32, func(), error) { +func (g *IDGeneratorMock) GenerateGID(_ *localentries.UserDBLocked, _ IDOwner) (uint32, func(), error) { testsdetection.MustBeTesting() if len(g.GIDsToGenerate) == 0 { From d8a337dadd697866881ec939239a42949015b9d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Fri, 4 Jul 2025 20:12:13 +0200 Subject: [PATCH 0585/1670] users/testdata: Do not set the UGID one group So we can test the legacy cases in which the UGID was not set --- internal/users/testdata/db/multiple_users_and_groups.db.yaml | 4 ++-- .../testdata/golden/TestAllGroups/Successfully_get_all_groups | 2 +- .../Successfully_create_manager_with_custom_config | 4 ++-- .../Successfully_create_manager_with_default_config | 4 ++-- .../Successfully_update_broker_for_user | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/internal/users/testdata/db/multiple_users_and_groups.db.yaml b/internal/users/testdata/db/multiple_users_and_groups.db.yaml index eea979e6e0..1233e7b8af 100644 --- a/internal/users/testdata/db/multiple_users_and_groups.db.yaml +++ b/internal/users/testdata/db/multiple_users_and_groups.db.yaml @@ -32,9 +32,9 @@ groups: - name: group1 gid: 11111 ugid: "12345678" - - name: group2 + - name: group2withoutugid gid: 22222 - ugid: "56781234" + ugid: "" - name: group3 gid: 33333 ugid: "34567812" diff --git a/internal/users/testdata/golden/TestAllGroups/Successfully_get_all_groups b/internal/users/testdata/golden/TestAllGroups/Successfully_get_all_groups index f911029941..8b7f2e2cf9 100644 --- a/internal/users/testdata/golden/TestAllGroups/Successfully_get_all_groups +++ b/internal/users/testdata/golden/TestAllGroups/Successfully_get_all_groups @@ -3,7 +3,7 @@ users: - user1 passwd: "" -- name: group2 +- name: group2withoutugid gid: 22222 users: - user2 diff --git a/internal/users/testdata/golden/TestNewManager/Successfully_create_manager_with_custom_config b/internal/users/testdata/golden/TestNewManager/Successfully_create_manager_with_custom_config index 9a59a4ead0..5d465349a9 100644 --- a/internal/users/testdata/golden/TestNewManager/Successfully_create_manager_with_custom_config +++ b/internal/users/testdata/golden/TestNewManager/Successfully_create_manager_with_custom_config @@ -32,9 +32,9 @@ groups: - name: group1 gid: 11111 ugid: "12345678" - - name: group2 + - name: group2withoutugid gid: 22222 - ugid: "56781234" + ugid: "" - name: group3 gid: 33333 ugid: "34567812" diff --git a/internal/users/testdata/golden/TestNewManager/Successfully_create_manager_with_default_config b/internal/users/testdata/golden/TestNewManager/Successfully_create_manager_with_default_config index 9a59a4ead0..5d465349a9 100644 --- a/internal/users/testdata/golden/TestNewManager/Successfully_create_manager_with_default_config +++ b/internal/users/testdata/golden/TestNewManager/Successfully_create_manager_with_default_config @@ -32,9 +32,9 @@ groups: - name: group1 gid: 11111 ugid: "12345678" - - name: group2 + - name: group2withoutugid gid: 22222 - ugid: "56781234" + ugid: "" - name: group3 gid: 33333 ugid: "34567812" diff --git a/internal/users/testdata/golden/TestUpdateBrokerForUser/Successfully_update_broker_for_user b/internal/users/testdata/golden/TestUpdateBrokerForUser/Successfully_update_broker_for_user index 5077945380..8f27fbf1b5 100644 --- a/internal/users/testdata/golden/TestUpdateBrokerForUser/Successfully_update_broker_for_user +++ b/internal/users/testdata/golden/TestUpdateBrokerForUser/Successfully_update_broker_for_user @@ -32,9 +32,9 @@ groups: - name: group1 gid: 11111 ugid: "12345678" - - name: group2 + - name: group2withoutugid gid: 22222 - ugid: "56781234" + ugid: "" - name: group3 gid: 33333 ugid: "34567812" From 8322d71e648a82da8b7342d7609412ae066cecb5 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 4 Jul 2025 20:03:44 +0200 Subject: [PATCH 0586/1670] testutils/golden: Use recursive approach to check golden file name validity In this way also subtests of subtests can be checked --- internal/testutils/golden/golden.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/internal/testutils/golden/golden.go b/internal/testutils/golden/golden.go index 773cc770c7..8f33392678 100644 --- a/internal/testutils/golden/golden.go +++ b/internal/testutils/golden/golden.go @@ -160,17 +160,12 @@ func Path(t *testing.T) string { cwd, err := os.Getwd() require.NoError(t, err, "Cannot get current working directory") - topLevelTest, subtest, subtestFound := strings.Cut(t.Name(), "/") - CheckValidGoldenFileName(t, topLevelTest) - - path := filepath.Join(cwd, "testdata", "golden", topLevelTest) - - if !subtestFound { - return path + parts := strings.Split(t.Name(), "/") + for _, part := range parts { + CheckValidGoldenFileName(t, part) } - CheckValidGoldenFileName(t, subtest) - return filepath.Join(path, subtest) + return filepath.Join(append([]string{cwd, "testdata", "golden"}, parts...)...) } // runDelta pipes the unified diff through the `delta` command for word-level diff and coloring. From 4e8e57bd2bd85b09462b6d3d7bc3547d221fb160 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 3 Jul 2025 18:43:29 +0200 Subject: [PATCH 0587/1670] users/types: Add equality functions for UserInfo and GroupInfo We can use them to do some optimizations --- internal/users/types/groupinfo.go | 15 +++ internal/users/types/groupinfo_test.go | 91 ++++++++++++++++ internal/users/types/userinfo.go | 13 +++ internal/users/types/userinfo_test.go | 138 +++++++++++++++++++++++++ 4 files changed, 257 insertions(+) create mode 100644 internal/users/types/groupinfo.go create mode 100644 internal/users/types/groupinfo_test.go create mode 100644 internal/users/types/userinfo.go create mode 100644 internal/users/types/userinfo_test.go diff --git a/internal/users/types/groupinfo.go b/internal/users/types/groupinfo.go new file mode 100644 index 0000000000..0bf2bff8bc --- /dev/null +++ b/internal/users/types/groupinfo.go @@ -0,0 +1,15 @@ +package types + +// Equals checks that two groups are equal. +func (u GroupInfo) Equals(other GroupInfo) bool { + if u.Name != other.Name || + u.UGID != other.UGID { + return false + } + + if u.GID == nil || other.GID == nil { + return u.GID == other.GID + } + + return *u.GID == *other.GID +} diff --git a/internal/users/types/groupinfo_test.go b/internal/users/types/groupinfo_test.go new file mode 100644 index 0000000000..16f801fc39 --- /dev/null +++ b/internal/users/types/groupinfo_test.go @@ -0,0 +1,91 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGroupInfoEquals(t *testing.T) { + t.Parallel() + + gid0 := uint32(0) + gid1 := uint32(1) + gid1Again := uint32(1) + gid2 := uint32(2) + + tests := map[string]struct { + a GroupInfo + b GroupInfo + want bool + }{ + "Empty groups": { + want: true, + }, + "All_fields_equal_GID_non-nil_and_equal": { + a: GroupInfo{Name: "group", UGID: "u1", GID: &gid1}, + b: GroupInfo{Name: "group", UGID: "u1", GID: &gid1}, + want: true, + }, + "Both_GID_nil": { + a: GroupInfo{Name: "group", UGID: "u1", GID: nil}, + b: GroupInfo{Name: "group", UGID: "u1", GID: nil}, + want: true, + }, + "GID_pointers_different_but_values_equal": { + a: GroupInfo{Name: "group", UGID: "u1", GID: &gid1}, + b: GroupInfo{Name: "group", UGID: "u1", GID: &gid1Again}, + want: true, + }, + "GID_is_zero_value": { + a: GroupInfo{Name: "group", UGID: "u1", GID: &gid0}, + b: GroupInfo{Name: "group", UGID: "u1", GID: &gid0}, + want: true, + }, + + // Failing cases. + "Fails_if_Empty_not_equals_other": { + a: GroupInfo{Name: "group", UGID: "u1", GID: &gid1}, + want: false, + }, + "Fails_if_Name_differs": { + a: GroupInfo{Name: "group1", UGID: "u1", GID: &gid1}, + b: GroupInfo{Name: "group2", UGID: "u1", GID: &gid1}, + want: false, + }, + "Fails_if_UGID_differs": { + a: GroupInfo{Name: "group", UGID: "u1", GID: &gid1}, + b: GroupInfo{Name: "group", UGID: "u2", GID: &gid1}, + want: false, + }, + "Fails_if_GID_one_nil_one_non-nil_a_nil": { + a: GroupInfo{Name: "group", UGID: "u1", GID: nil}, + b: GroupInfo{Name: "group", UGID: "u1", GID: &gid1}, + want: false, + }, + "Fails_if_GID_one_nil_one_non-nil_b_nil": { + a: GroupInfo{Name: "group", UGID: "u1", GID: &gid1}, + b: GroupInfo{Name: "group", UGID: "u1", GID: nil}, + want: false, + }, + "Fails_if_GID_non-nil_values_differ": { + a: GroupInfo{Name: "group", UGID: "u1", GID: &gid1}, + b: GroupInfo{Name: "group", UGID: "u1", GID: &gid2}, + want: false, + }, + "Fails_if_All_fields_differ": { + a: GroupInfo{Name: "groupA", UGID: "uA", GID: &gid1}, + b: GroupInfo{Name: "groupB", UGID: "uB", GID: &gid2}, + want: false, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tc.a.Equals(tc.b) + require.Equal(t, tc.want, got) + }) + } +} diff --git a/internal/users/types/userinfo.go b/internal/users/types/userinfo.go new file mode 100644 index 0000000000..9797e41ed5 --- /dev/null +++ b/internal/users/types/userinfo.go @@ -0,0 +1,13 @@ +package types + +import "github.com/ubuntu/authd/internal/sliceutils" + +// Equals checks that two users are equal. +func (u UserInfo) Equals(other UserInfo) bool { + return u.Name == other.Name && + u.UID == other.UID && + u.Gecos == other.Gecos && + u.Dir == other.Dir && + u.Shell == other.Shell && + sliceutils.EqualContentFunc(u.Groups, other.Groups, GroupInfo.Equals) +} diff --git a/internal/users/types/userinfo_test.go b/internal/users/types/userinfo_test.go new file mode 100644 index 0000000000..fc7840150b --- /dev/null +++ b/internal/users/types/userinfo_test.go @@ -0,0 +1,138 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/ubuntu/authd/internal/users/types" +) + +func ptrValue[T any](value T) *T { + return &value +} + +func TestUserInfoEquals(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + u1, u2 types.UserInfo + want bool + }{ + "Equal_when_all_fields_and_groups_are_equal": { + u1: types.UserInfo{ + Name: "user1", + UID: 1000, + Gecos: "User1", + Dir: "/home/user1", + Shell: "/bin/bash", + Groups: []types.GroupInfo{ + {Name: "sudo", GID: ptrValue[uint32](27)}, + {Name: "users", GID: ptrValue[uint32](100)}, + }, + }, + u2: types.UserInfo{ + Name: "user1", + UID: 1000, + Gecos: "User1", + Dir: "/home/user1", + Shell: "/bin/bash", + Groups: []types.GroupInfo{ + {Name: "sudo", GID: ptrValue[uint32](27)}, + {Name: "users", GID: ptrValue[uint32](100)}, + }, + }, + want: true, + }, + "Equal_when_both_have_no_groups": { + u1: types.UserInfo{Name: "user1"}, + u2: types.UserInfo{Name: "user1"}, + want: true, + }, + "Equal_when_groups_are_equal_but_in_different_pointer_instances": { + u1: types.UserInfo{ + Name: "user1", + Groups: []types.GroupInfo{{Name: "sudo", GID: ptrValue[uint32](27)}}, + }, + u2: types.UserInfo{ + Name: "user1", + Groups: []types.GroupInfo{{Name: "sudo", GID: ptrValue[uint32](27)}}, + }, + want: true, + }, + "Equal_when_groups_are_equal_but_in_different_order": { + u1: types.UserInfo{ + Name: "user1", + Groups: []types.GroupInfo{ + {Name: "group1", GID: ptrValue[uint32](1)}, + {Name: "group2", GID: ptrValue[uint32](2)}, + }, + }, + u2: types.UserInfo{ + Name: "user1", + Groups: []types.GroupInfo{ + {Name: "group2", GID: ptrValue[uint32](2)}, + {Name: "group1", GID: ptrValue[uint32](1)}, + }, + }, + want: true, + }, + + // Failing cases. + "Fails_if_names_differ": { + u1: types.UserInfo{Name: "user1"}, + u2: types.UserInfo{Name: "user2"}, + want: false, + }, + "Fails_if_UIDs_differ": { + u1: types.UserInfo{Name: "user1", UID: 1000}, + u2: types.UserInfo{Name: "user1", UID: 1001}, + want: false, + }, + "Fails_if_Gecos_differ": { + u1: types.UserInfo{Name: "user1", Gecos: "User1"}, + u2: types.UserInfo{Name: "user1", Gecos: "User3"}, + want: false, + }, + "Fails_if_Dir_differ": { + u1: types.UserInfo{Name: "user1", Dir: "/home/user1"}, + u2: types.UserInfo{Name: "user1", Dir: "/home/user2"}, + want: false, + }, + "Fails_if_Shell_differ": { + u1: types.UserInfo{Name: "user1", Shell: "/bin/bash"}, + u2: types.UserInfo{Name: "user1", Shell: "/bin/zsh"}, + want: false, + }, + "Fails_if_Groups_differ_in_length": { + u1: types.UserInfo{ + Name: "user1", + Groups: []types.GroupInfo{{Name: "sudo", GID: ptrValue[uint32](27)}}, + }, + u2: types.UserInfo{ + Name: "user1", + Groups: []types.GroupInfo{}, + }, + want: false, + }, + "Fails_if_Groups_differ_in_content": { + u1: types.UserInfo{ + Name: "user1", + Groups: []types.GroupInfo{{Name: "sudo", GID: ptrValue[uint32](27)}}, + }, + u2: types.UserInfo{ + Name: "user1", + Groups: []types.GroupInfo{{Name: "users", GID: ptrValue[uint32](100)}}, + }, + want: false, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := tc.u1.Equals(tc.u2) + require.Equal(t, tc.want, got, "Equals() returned unexpected result") + }) + } +} From b8db924bff98233558ae198e8a3ccc98339aa21d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Thu, 3 Jul 2025 21:55:43 +0200 Subject: [PATCH 0588/1670] users: Avoid locking and updating an user if the same entry is in the DB With the current implementation we are always locking our users instance every time that one has authenticated and we do potentially unneeded DB queries and IO operations to lock the users system database. This may also have the side effect that if there are multiple SSH requests that are trying to DDOS us, we may slow down normal users authentication for no reason (potentially until we reach the maximum of pre-auth users that we have set). So, to prevent this: - Check if the updating user is matching the one that we have in the database - If we get a positive result, we can just continue without further mutex or user DB locking (indeed some other request may change things meanwhile, but that would still suceed) - On failure, we need to lock the manager instance to ensure that no other request that may have arrived concurrently might have changed the state, and so check again for user existence. - If this further check fails, we are now sure that we need to lock also the users system database, since we are required to perform operations that may invalidate the current system users state and so continue as before. This has also a good performance impact as can be measured by the TestConcurrentUserUpdate test (from ~2.5s to ~1.7s here), since avoids useless locks or IO and DB operations in all the cases in which we're just re-authenticating an user, and these are still way slower than the extra checks that we are adding --- internal/users/defs.go | 27 +++ internal/users/export_test.go | 9 + internal/users/localentries/lockedentries.go | 3 +- internal/users/manager.go | 176 +++++++++++++----- internal/users/manager_test.go | 84 +++++++++ .../Compare_all_not_matching_users/user1 | 15 ++ .../Compare_all_not_matching_users/user2 | 12 ++ .../Compare_all_not_matching_users/user3 | 12 ++ .../userwithoutbroker | 14 ++ .../Compare_all_valid_users/user1 | 14 ++ .../Compare_all_valid_users/user2 | 10 + .../Compare_all_valid_users/user3 | 11 ++ .../Compare_all_valid_users/userwithoutbroker | 11 ++ .../user1-from-getOldUserInfoFromDB | 14 ++ .../user2-from-getOldUserInfoFromDB | 12 ++ .../user3-from-getOldUserInfoFromDB | 12 ++ ...serwithoutbroker-from-getOldUserInfoFromDB | 12 ++ .../user1-from-getOldUserInfoFromDB | 14 ++ .../user2-from-getOldUserInfoFromDB | 12 ++ .../user3-from-getOldUserInfoFromDB | 12 ++ ...serwithoutbroker-from-getOldUserInfoFromDB | 12 ++ 21 files changed, 454 insertions(+), 44 deletions(-) create mode 100644 internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/user1 create mode 100644 internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/user2 create mode 100644 internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/user3 create mode 100644 internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/userwithoutbroker create mode 100644 internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_valid_users/user1 create mode 100644 internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_valid_users/user2 create mode 100644 internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_valid_users/user3 create mode 100644 internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_valid_users/userwithoutbroker create mode 100644 internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/user1-from-getOldUserInfoFromDB create mode 100644 internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/user2-from-getOldUserInfoFromDB create mode 100644 internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/user3-from-getOldUserInfoFromDB create mode 100644 internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/userwithoutbroker-from-getOldUserInfoFromDB create mode 100644 internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_valid_users/user1-from-getOldUserInfoFromDB create mode 100644 internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_valid_users/user2-from-getOldUserInfoFromDB create mode 100644 internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_valid_users/user3-from-getOldUserInfoFromDB create mode 100644 internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_valid_users/userwithoutbroker-from-getOldUserInfoFromDB diff --git a/internal/users/defs.go b/internal/users/defs.go index d566c2c0a1..da63db157d 100644 --- a/internal/users/defs.go +++ b/internal/users/defs.go @@ -1,6 +1,7 @@ package users import ( + "github.com/ubuntu/authd/internal/sliceutils" "github.com/ubuntu/authd/internal/users/db" "github.com/ubuntu/authd/internal/users/types" ) @@ -17,6 +18,32 @@ func userEntryFromUserRow(u db.UserRow) types.UserEntry { } } +// userInfoFromUserRow returns a UserInfo from a [db.UserRow] and [db.GroupRow] +// and local groups slices. +func userInfoFromUserAndGroupRows(u db.UserRow, groups []db.GroupRow, localGroups []string) *types.UserInfo { + ui := &types.UserInfo{ + Name: u.Name, + UID: u.UID, + Gecos: u.Gecos, + Dir: u.Dir, + Shell: u.Shell, + Groups: sliceutils.Map(groups, func(g db.GroupRow) types.GroupInfo { + gid := g.GID + return types.GroupInfo{ + Name: g.Name, + GID: &gid, + UGID: g.UGID, + } + }), + } + + for _, lg := range localGroups { + ui.Groups = append(ui.Groups, types.GroupInfo{Name: lg}) + } + + return ui +} + // shadowEntryFromUserRow returns a ShadowEntry from a UserRow. func shadowEntryFromUserRow(u db.UserRow) types.ShadowEntry { return types.ShadowEntry{ diff --git a/internal/users/export_test.go b/internal/users/export_test.go index c1d5632ac6..1e301e0e7d 100644 --- a/internal/users/export_test.go +++ b/internal/users/export_test.go @@ -2,8 +2,17 @@ package users import ( "github.com/ubuntu/authd/internal/users/db" + "github.com/ubuntu/authd/internal/users/types" ) func (m *Manager) DB() *db.Manager { return m.db } + +func (m *Manager) GetOldUserInfoFromDB(name string) (oldUserInfo *types.UserInfo, err error) { + return m.getOldUserInfoFromDB(name) +} + +func CompareNewUserInfoWithUserInfoFromDB(newUserInfo, dbUserInfo types.UserInfo) bool { + return compareNewUserInfoWithUserInfoFromDB(newUserInfo, dbUserInfo) +} diff --git a/internal/users/localentries/lockedentries.go b/internal/users/localentries/lockedentries.go index 745a3ae9d6..33dfcbe4e3 100644 --- a/internal/users/localentries/lockedentries.go +++ b/internal/users/localentries/lockedentries.go @@ -350,7 +350,8 @@ func (l *UserDBLocked) IsUniqueUID(uid uint32) (unique bool, err error) { return false, err } - // Also check that the UID is not used by a group. + // Also check if there is a group with the same ID, because the UID is + // also used as the GID of the user private group. return l.IsUniqueGID(uid) } diff --git a/internal/users/manager.go b/internal/users/manager.go index 05d9749ff7..c4c21f7b9c 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "os" + "slices" "strings" "sync" "syscall" @@ -117,39 +118,77 @@ func (m *Manager) Stop() error { func (m *Manager) UpdateUser(u types.UserInfo) (err error) { defer decorate.OnError(&err, "failed to update user %q", u.Name) - m.userManagementMu.Lock() - defer m.userManagementMu.Unlock() - log.Debugf(context.TODO(), "Updating user %q", u.Name) + if u.Name == "" { + return errors.New("empty username") + } + // authd uses lowercase usernames u.Name = strings.ToLower(u.Name) - if u.Name == "" { - return errors.New("empty username") + // Prepend the user private group + u.Groups = append([]types.GroupInfo{{Name: u.Name, UGID: u.Name}}, u.Groups...) + userPrivateGroup := &u.Groups[0] + + var oldUserInfo *types.UserInfo + checkEqualUserExists := func() (exists bool, err error) { + // Check if the user already exists in the database. + oldUserInfo, err = m.getOldUserInfoFromDB(u.Name) + if err != nil { + return false, err + } + if oldUserInfo == nil { + return false, nil + } + if !compareNewUserInfoWithUserInfoFromDB(u, *oldUserInfo) { + log.Debugf(context.TODO(), "User %q is already in our database", u.Name) + return false, nil + } + + log.Debugf(context.TODO(), "User %q in database already matches current", u.Name) + return true, nil } - var uid uint32 - // Check if the user already exists in the database - oldUser, err := m.db.UserByName(u.Name) - if err != nil && !errors.Is(err, db.NoDataFoundError{}) { - return fmt.Errorf("could not get user %q: %w", u.Name, err) + // Do a first check before locking, so that if the user is already there and + // matches the DB entry, we can avoid any kind of locking (and so being + // blocked by other pre-auth users that may try to login meanwhile). + exists, err := checkEqualUserExists() + if exists || err != nil { + // The user already exists, so no update needed, or an error occurred. + return err } - if errors.Is(err, db.NoDataFoundError{}) { + + m.userManagementMu.Lock() + defer m.userManagementMu.Unlock() + + // Now that we're locked, check again if meanwhile some other request + // created the same user, if not we can do all the kinds of locking since + // we're sure that the user needs to be added or updated in the database. + if exists, err := checkEqualUserExists(); exists || err != nil { + return err + } + + log.Debugf(context.TODO(), "User %q needs update", u.Name) + + lockedEntries, unlockEntries, err := localentries.WithUserDBLock() + if err != nil { + return err + } + defer func() { err = errors.Join(err, unlockEntries()) }() + + if oldUserInfo != nil { + // The user already exists in the database, use the existing UID to avoid permission issues. + u.UID = oldUserInfo.UID + } else { preauthUID, cleanup, err := m.preAuthRecords.MaybeCompletePreauthUser(u.Name) if err != nil && !errors.Is(err, tempentries.NoDataFoundError{}) { return err } if preauthUID != 0 { - uid = preauthUID + u.UID = preauthUID defer cleanup() } else { - lockedEntries, unlockEntries, err := localentries.WithUserDBLock() - if err != nil { - return err - } - defer func() { err = errors.Join(err, unlockEntries()) }() - unique, err := lockedEntries.IsUniqueUserName(u.Name) if err != nil { return err @@ -160,20 +199,17 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { } var cleanupUID func() - uid, cleanupUID, err = m.idGenerator.GenerateUID(lockedEntries, m) + u.UID, cleanupUID, err = m.idGenerator.GenerateUID(lockedEntries, m) if err != nil { return err } defer cleanupUID() - log.Debugf(context.Background(), "Using new UID %d for user %q", uid, u.Name) + log.Debugf(context.Background(), "Using new UID %d for user %q", u.UID, u.Name) } - } else { - // The user already exists in the database, use the existing UID to avoid permission issues. - uid = oldUser.UID } - // Prepend the user private group - u.Groups = append([]types.GroupInfo{{Name: u.Name, GID: &uid, UGID: u.Name}}, u.Groups...) + // User private Group GID is the same of the user UID. + userPrivateGroup.GID = &u.UID var groupRows []db.GroupRow var localGroups []string @@ -220,12 +256,6 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { } if len(newGroups) > 0 { - lockedEntries, unlockEntries, err := localentries.WithUserDBLock() - if err != nil { - return err - } - defer func() { err = errors.Join(err, unlockEntries()) }() - for _, g := range newGroups { unique, err := lockedEntries.IsUniqueGroupName(g.Name) if err != nil { @@ -248,25 +278,23 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { } } - oldLocalGroups, err := m.db.UserLocalGroups(uid) - if err != nil && !errors.Is(err, db.NoDataFoundError{}) { - return err + var oldLocalGroups []string + if oldUserInfo != nil { + for _, g := range oldUserInfo.Groups { + if g.UGID != "" { + // A non-empty UGID means that it's an authd group + continue + } + oldLocalGroups = append(oldLocalGroups, g.Name) + } } - // Update user information in the db. - userPrivateGroup := groupRows[0] - userRow := db.NewUserRow(u.Name, uid, userPrivateGroup.GID, u.Gecos, u.Dir, u.Shell) + userRow := db.NewUserRow(u.Name, u.UID, *userPrivateGroup.GID, u.Gecos, u.Dir, u.Shell) if err = m.db.UpdateUserEntry(userRow, groupRows, localGroups); err != nil { return err } // Update local groups. - lockedEntries, unlockEntries, err := localentries.WithUserDBLock() - if err != nil { - return err - } - defer func() { err = errors.Join(err, unlockEntries()) }() - if err := localentries.UpdateGroups(lockedEntries, u.Name, localGroups, oldLocalGroups); err != nil { return err } @@ -278,6 +306,59 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { return nil } +func (m *Manager) getOldUserInfoFromDB(name string) (oldUserInfo *types.UserInfo, err error) { + // Check if the user already exists in the database. + u, err := m.db.UserByName(name) + if errors.Is(err, db.NoDataFoundError{}) { + return nil, nil + } + if err != nil { + return nil, fmt.Errorf("could not get user %q: %w", name, err) + } + + userGroups, err := m.db.UserGroups(u.UID) + if err != nil && !errors.Is(err, db.NoDataFoundError{}) { + return nil, fmt.Errorf("could not get users groups for %q: %w", name, err) + } + + oldLocalGroups, err := m.db.UserLocalGroups(u.UID) + if err != nil && !errors.Is(err, db.NoDataFoundError{}) { + return nil, err + } + + return userInfoFromUserAndGroupRows(u, userGroups, oldLocalGroups), nil +} + +func compareNewUserInfoWithUserInfoFromDB(newUserInfo, dbUserInfo types.UserInfo) bool { + if len(dbUserInfo.Groups) != len(newUserInfo.Groups) { + return false + } + + // The new user UID may be set or un set, but despite that we're going to use + // the one we saved, so compare against it. + newUserInfo.UID = dbUserInfo.UID + + // We then need to normalize the new user groups in order to be able to + // compare the two users, in fact we may receive from the broker user entries + // with unset or different GIDs, so let's + for idx, g := range newUserInfo.Groups { + oldGroupIdx := slices.IndexFunc(dbUserInfo.Groups, func(dg types.GroupInfo) bool { + if dg.UGID == "" { + // Do not compare through UGID if the one of the existing group is + // empty, because we didn't store the UGID in 0.3.7 and earlier. + return dg.Name == g.Name + } + return dg.UGID == g.UGID + }) + if oldGroupIdx < 0 { + continue + } + newUserInfo.Groups[idx].GID = dbUserInfo.Groups[oldGroupIdx].GID + } + + return dbUserInfo.Equals(newUserInfo) +} + // checkGroupNameConflict checks if a group with the given name already exists. // If it does, it checks if it has the same UGID. func (m *Manager) checkGroupNameConflict(name string, ugid string) error { @@ -531,9 +612,18 @@ func (m *Manager) AllShadows() ([]types.ShadowEntry, error) { func (m *Manager) RegisterUserPreAuth(name string) (uid uint32, err error) { defer decorate.OnError(&err, "failed to register pre-auth user %q", name) + // Do a first check without the lock, so that if the user is already there + // we don't have to go through actual locking. + if userRow, err := m.db.UserByName(name); err == nil { + log.Debugf(context.Background(), "user %q already exists on the system", name) + return userRow.UID, nil + } + m.userManagementMu.Lock() defer m.userManagementMu.Unlock() + // Repeat the check once locked, so that we are really sure that the user + // has not been registered meanwhile. if userRow, err := m.db.UserByName(name); err == nil { log.Debugf(context.Background(), "user %q already exists on the system", name) return userRow.UID, nil diff --git a/internal/users/manager_test.go b/internal/users/manager_test.go index 63cbca072c..6f0bc9b609 100644 --- a/internal/users/manager_test.go +++ b/internal/users/manager_test.go @@ -26,6 +26,7 @@ import ( userstestutils "github.com/ubuntu/authd/internal/users/testutils" "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/authd/log" + "gopkg.in/yaml.v3" ) func TestNewManager(t *testing.T) { @@ -988,6 +989,89 @@ func TestAllShadows(t *testing.T) { } } +func TestCompareNewUserInfoWithDB(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + dbFile string + + wantUserExactMatch map[string]bool + wantUserNoMatch map[string]bool + }{ + "Compare_all_valid_users": { + dbFile: "multiple_users_and_groups", + wantUserExactMatch: map[string]bool{"user1": true}, + }, + "Compare_all_not_matching_users": { + dbFile: "multiple_users_and_groups", + wantUserNoMatch: map[string]bool{ + "user1": true, "user2": true, "user3": true, "userwithoutbroker": true, + }, + }, + } + for name, tc := range tests { + dbDir := t.TempDir() + err := db.Z_ForTests_CreateDBFromYAML(filepath.Join("testdata", "db", tc.dbFile+".db.yaml"), dbDir) + require.NoError(t, err, "Setup: could not create database from testdata") + + m := newManagerForTests(t, dbDir) + + t.Run(name, func(t *testing.T) { + t.Parallel() + + userEntries, err := m.AllUsers() + require.NoError(t, err, "AllUsers should not fail but it did") + + for _, u := range userEntries { + t.Run(u.Name, func(t *testing.T) { + t.Parallel() + + u, err := m.GetOldUserInfoFromDB(u.Name) + require.NoError(t, err, "GetOldUserInfoFromDB should not fail but it did") + require.NotNil(t, u, "GetOldUserInfoFromDB user should not be nil but it is") + + dbUserInfo := *u + golden.CheckOrUpdateYAML(t, dbUserInfo, + golden.WithSuffix("-from-getOldUserInfoFromDB")) + + userInfoFile := filepath.Join("testdata", t.Name()) + content, err := os.ReadFile(userInfoFile) + require.NoError(t, err, "ReadFile should not fail opening %q", userInfoFile) + + var wantUserInfo types.UserInfo + err = yaml.Unmarshal(content, &wantUserInfo) + require.NoError(t, err, "Cannot deserialize user info") + + if tc.wantUserExactMatch[u.Name] { + require.Equal(t, wantUserInfo, dbUserInfo, + "User infos be strictly equal, but they are not") + require.True(t, wantUserInfo.Equals(dbUserInfo), + "User infos be strictly equal, but they are not") + } else { + require.NotEqual(t, wantUserInfo, dbUserInfo, + "User infos should not be strictly equal, but they are") + require.False(t, wantUserInfo.Equals(dbUserInfo), + "User infos should not be strictly equal, but they are") + } + + got := users.CompareNewUserInfoWithUserInfoFromDB(wantUserInfo, dbUserInfo) + require.Equal(t, !tc.wantUserNoMatch[u.Name], got, + "User infos does not respect wanted equality check:"+ + "\nNew: %#v\n Old: %#v", wantUserInfo, dbUserInfo) + }) + } + }) + + t.Run("not_existing_user", func(t *testing.T) { + t.Parallel() + + user, err := m.GetOldUserInfoFromDB("ImustNot-exist") + require.NoError(t, err, "GetOldUserInfoFromDB should not fail but it did") + require.Nil(t, user, "returned user should be nil, but it was not") + }) + } +} + func TestRegisterUserPreAuthWhenLocked(t *testing.T) { // This cannot be parallel diff --git a/internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/user1 b/internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/user1 new file mode 100644 index 0000000000..c4fd722fba --- /dev/null +++ b/internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/user1 @@ -0,0 +1,15 @@ +name: user1 +uid: 1111 +gecos: |- + User1 gecos + On multiple lines + But they are too many! +dir: /home/user1 +shell: /bin/bash +groups: + - name: group1 + gid: 11111 + ugid: "12345678" + - name: commongroup + gid: 99999 + ugid: "87654321" diff --git a/internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/user2 b/internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/user2 new file mode 100644 index 0000000000..85f2f49706 --- /dev/null +++ b/internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/user2 @@ -0,0 +1,12 @@ +name: user2 +uid: 2222 +gecos: User2 +dir: /home/user2/new/home_is_here +shell: /bin/dash +groups: + - name: group2withoutugid + gid: 22222 + ugid: "" + - name: commongroup + gid: 99999 + ugid: "87654321" diff --git a/internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/user3 b/internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/user3 new file mode 100644 index 0000000000..9797b8253e --- /dev/null +++ b/internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/user3 @@ -0,0 +1,12 @@ +name: user3 +uid: 3333 +gecos: User3 +dir: /home/user3 +shell: /bin/zsh +groups: + - name: group3 + gid: 33333 + ugid: "my-new-ugid-is-better!" + - name: commongroup + gid: 99999 + ugid: "87654321" diff --git a/internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/userwithoutbroker b/internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/userwithoutbroker new file mode 100644 index 0000000000..447d9b1db3 --- /dev/null +++ b/internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/userwithoutbroker @@ -0,0 +1,14 @@ +name: userwithoutbroker +uid: 4444 +gecos: userwithoutbroker +dir: /home/userwithoutbroker +shell: /bin/sh +groups: + - name: group4 + gid: 44444 + ugid: "45678123" + - name: commongroup + gid: 99999 + ugid: "87654321" + - name: but-with-a-new-group + gid: 12456 diff --git a/internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_valid_users/user1 b/internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_valid_users/user1 new file mode 100644 index 0000000000..9713e55ab9 --- /dev/null +++ b/internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_valid_users/user1 @@ -0,0 +1,14 @@ +name: user1 +uid: 1111 +gecos: |- + User1 gecos + On multiple lines +dir: /home/user1 +shell: /bin/bash +groups: + - name: group1 + gid: 11111 + ugid: "12345678" + - name: commongroup + gid: 99999 + ugid: "87654321" diff --git a/internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_valid_users/user2 b/internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_valid_users/user2 new file mode 100644 index 0000000000..8f9697ba53 --- /dev/null +++ b/internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_valid_users/user2 @@ -0,0 +1,10 @@ +name: user2 +gecos: User2 +dir: /home/user2 +shell: /bin/dash +groups: + - name: group2withoutugid + ugid: "" + - name: commongroup + gid: 99999 + ugid: "87654321" diff --git a/internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_valid_users/user3 b/internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_valid_users/user3 new file mode 100644 index 0000000000..4b82609224 --- /dev/null +++ b/internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_valid_users/user3 @@ -0,0 +1,11 @@ +name: user3 +uid: 333333333 +gecos: User3 +dir: /home/user3 +shell: /bin/zsh +groups: + - name: group3 + gid: 333333333 + ugid: "34567812" + - name: commongroup + ugid: "87654321" diff --git a/internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_valid_users/userwithoutbroker b/internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_valid_users/userwithoutbroker new file mode 100644 index 0000000000..2207fcf831 --- /dev/null +++ b/internal/users/testdata/TestCompareNewUserInfoWithDB/Compare_all_valid_users/userwithoutbroker @@ -0,0 +1,11 @@ +name: userwithoutbroker +uid: 0 +gecos: userwithoutbroker +dir: /home/userwithoutbroker +shell: /bin/sh +groups: + - name: group4 + gid: 44444 + ugid: "45678123" + - name: commongroup + ugid: "87654321" diff --git a/internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/user1-from-getOldUserInfoFromDB b/internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/user1-from-getOldUserInfoFromDB new file mode 100644 index 0000000000..9713e55ab9 --- /dev/null +++ b/internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/user1-from-getOldUserInfoFromDB @@ -0,0 +1,14 @@ +name: user1 +uid: 1111 +gecos: |- + User1 gecos + On multiple lines +dir: /home/user1 +shell: /bin/bash +groups: + - name: group1 + gid: 11111 + ugid: "12345678" + - name: commongroup + gid: 99999 + ugid: "87654321" diff --git a/internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/user2-from-getOldUserInfoFromDB b/internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/user2-from-getOldUserInfoFromDB new file mode 100644 index 0000000000..d3176fced3 --- /dev/null +++ b/internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/user2-from-getOldUserInfoFromDB @@ -0,0 +1,12 @@ +name: user2 +uid: 2222 +gecos: User2 +dir: /home/user2 +shell: /bin/dash +groups: + - name: group2withoutugid + gid: 22222 + ugid: "" + - name: commongroup + gid: 99999 + ugid: "87654321" diff --git a/internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/user3-from-getOldUserInfoFromDB b/internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/user3-from-getOldUserInfoFromDB new file mode 100644 index 0000000000..2b87a14afc --- /dev/null +++ b/internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/user3-from-getOldUserInfoFromDB @@ -0,0 +1,12 @@ +name: user3 +uid: 3333 +gecos: User3 +dir: /home/user3 +shell: /bin/zsh +groups: + - name: group3 + gid: 33333 + ugid: "34567812" + - name: commongroup + gid: 99999 + ugid: "87654321" diff --git a/internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/userwithoutbroker-from-getOldUserInfoFromDB b/internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/userwithoutbroker-from-getOldUserInfoFromDB new file mode 100644 index 0000000000..efeb313eb0 --- /dev/null +++ b/internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_not_matching_users/userwithoutbroker-from-getOldUserInfoFromDB @@ -0,0 +1,12 @@ +name: userwithoutbroker +uid: 4444 +gecos: userwithoutbroker +dir: /home/userwithoutbroker +shell: /bin/sh +groups: + - name: group4 + gid: 44444 + ugid: "45678123" + - name: commongroup + gid: 99999 + ugid: "87654321" diff --git a/internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_valid_users/user1-from-getOldUserInfoFromDB b/internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_valid_users/user1-from-getOldUserInfoFromDB new file mode 100644 index 0000000000..9713e55ab9 --- /dev/null +++ b/internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_valid_users/user1-from-getOldUserInfoFromDB @@ -0,0 +1,14 @@ +name: user1 +uid: 1111 +gecos: |- + User1 gecos + On multiple lines +dir: /home/user1 +shell: /bin/bash +groups: + - name: group1 + gid: 11111 + ugid: "12345678" + - name: commongroup + gid: 99999 + ugid: "87654321" diff --git a/internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_valid_users/user2-from-getOldUserInfoFromDB b/internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_valid_users/user2-from-getOldUserInfoFromDB new file mode 100644 index 0000000000..d3176fced3 --- /dev/null +++ b/internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_valid_users/user2-from-getOldUserInfoFromDB @@ -0,0 +1,12 @@ +name: user2 +uid: 2222 +gecos: User2 +dir: /home/user2 +shell: /bin/dash +groups: + - name: group2withoutugid + gid: 22222 + ugid: "" + - name: commongroup + gid: 99999 + ugid: "87654321" diff --git a/internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_valid_users/user3-from-getOldUserInfoFromDB b/internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_valid_users/user3-from-getOldUserInfoFromDB new file mode 100644 index 0000000000..2b87a14afc --- /dev/null +++ b/internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_valid_users/user3-from-getOldUserInfoFromDB @@ -0,0 +1,12 @@ +name: user3 +uid: 3333 +gecos: User3 +dir: /home/user3 +shell: /bin/zsh +groups: + - name: group3 + gid: 33333 + ugid: "34567812" + - name: commongroup + gid: 99999 + ugid: "87654321" diff --git a/internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_valid_users/userwithoutbroker-from-getOldUserInfoFromDB b/internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_valid_users/userwithoutbroker-from-getOldUserInfoFromDB new file mode 100644 index 0000000000..efeb313eb0 --- /dev/null +++ b/internal/users/testdata/golden/TestCompareNewUserInfoWithDB/Compare_all_valid_users/userwithoutbroker-from-getOldUserInfoFromDB @@ -0,0 +1,12 @@ +name: userwithoutbroker +uid: 4444 +gecos: userwithoutbroker +dir: /home/userwithoutbroker +shell: /bin/sh +groups: + - name: group4 + gid: 44444 + ugid: "45678123" + - name: commongroup + gid: 99999 + ugid: "87654321" From 7e98cdde9a3475a291414ce3a0380617bef4cebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Sat, 5 Jul 2025 02:07:51 +0200 Subject: [PATCH 0589/1670] cleanup: There's no more room for... An user --- examplebroker/users.go | 4 ++-- internal/users/db/bbolt/getusers.go | 2 +- pam/integration-tests/exec_test.go | 2 +- pam/internal/adapter/nativemodel.go | 2 +- pam/internal/adapter/userselection.go | 2 +- pam/internal/gdm/gdm.pb.go | 2 +- pam/internal/gdm/gdm.proto | 2 +- pam/internal/pam_test/module-transaction-dummy_test.go | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examplebroker/users.go b/examplebroker/users.go index 8917bfcce3..d8d5776627 100644 --- a/examplebroker/users.go +++ b/examplebroker/users.go @@ -27,7 +27,7 @@ var ( ) const ( - // UserIntegrationPrefix is the prefix for an user for integration tests. + // UserIntegrationPrefix is the prefix for a user for integration tests. UserIntegrationPrefix = "user-integration-" // UserIntegrationMfaPrefix is the prefix for an mfa user for integration tests. UserIntegrationMfaPrefix = "user-mfa-integration-" @@ -49,7 +49,7 @@ const ( UserIntegrationPreCheckPrefix = UserIntegrationPrefix + UserIntegrationPreCheckValue + "-" // UserIntegrationUnexistent is an unexistent user leading to a non-existent user error. UserIntegrationUnexistent = "user-unexistent" - // UserIntegrationAuthModesPrefix is the prefix for an user listing for supported auth modes. + // UserIntegrationAuthModesPrefix is the prefix for a user listing for supported auth modes. // The modes can be exposed as list, in the form: `user-auth-modes-id1,id2,id3-integration-whatever`. UserIntegrationAuthModesPrefix = "user-auth-modes-" ) diff --git a/internal/users/db/bbolt/getusers.go b/internal/users/db/bbolt/getusers.go index eb3582a435..e4602bedc0 100644 --- a/internal/users/db/bbolt/getusers.go +++ b/internal/users/db/bbolt/getusers.go @@ -39,7 +39,7 @@ func (c *Database) AllUsers() (all []UserDB, err error) { return all, nil } -// getUser returns an user matching the key or an error if the database is corrupted or no entry was found. +// getUser returns a user matching the key or an error if the database is corrupted or no entry was found. func getUser[K uint32 | string](c *Database, bucketName string, key K) (u UserDB, err error) { c.mu.RLock() defer c.mu.RUnlock() diff --git a/pam/integration-tests/exec_test.go b/pam/integration-tests/exec_test.go index 2df0bf1821..4e9ec28a1f 100644 --- a/pam/integration-tests/exec_test.go +++ b/pam/integration-tests/exec_test.go @@ -332,7 +332,7 @@ func TestExecModule(t *testing.T) { }{ "Set_user": { item: pam.User, - value: ptrValue("an user"), + value: ptrValue("a user"), }, "Returns_empty_when_getting_an_unset_user": { item: pam.User, diff --git a/pam/internal/adapter/nativemodel.go b/pam/internal/adapter/nativemodel.go index 328967f0c3..5e835ada58 100644 --- a/pam/internal/adapter/nativemodel.go +++ b/pam/internal/adapter/nativemodel.go @@ -55,7 +55,7 @@ const ( // nativeStageChangeRequest is the internal event to request that a stage change. type nativeStageChangeRequest ChangeStage -// nativeUserSelection is the internal event that an user needs to be (re)set. +// nativeUserSelection is the internal event that a user needs to be (re)set. type nativeUserSelection struct{} // nativeBrokerSelection is the internal event that a broker needs to be (re)selected. diff --git a/pam/internal/adapter/userselection.go b/pam/internal/adapter/userselection.go index 5ce6ff9551..8b702c73f6 100644 --- a/pam/internal/adapter/userselection.go +++ b/pam/internal/adapter/userselection.go @@ -13,7 +13,7 @@ import ( "github.com/ubuntu/authd/pam/internal/proto" ) -// userSelectionModel allows selecting from PAM or interactively an user. +// userSelectionModel allows selecting from PAM or interactively a user. type userSelectionModel struct { textinput.Model diff --git a/pam/internal/gdm/gdm.pb.go b/pam/internal/gdm/gdm.pb.go index a6bfccaf4f..c04a24b2f5 100644 --- a/pam/internal/gdm/gdm.pb.go +++ b/pam/internal/gdm/gdm.pb.go @@ -160,7 +160,7 @@ type EventType int32 const ( // EventType_unknownEvent is an unknown EventType. EventType_unknownEvent EventType = 0 - // EventType_userSelected is an user selected EventType. + // EventType_userSelected is a user selected EventType. EventType_userSelected EventType = 1 // EventType_brokersReceived is a broker received EventType. EventType_brokersReceived EventType = 2 diff --git a/pam/internal/gdm/gdm.proto b/pam/internal/gdm/gdm.proto index b000430b34..b463d1bd0d 100644 --- a/pam/internal/gdm/gdm.proto +++ b/pam/internal/gdm/gdm.proto @@ -85,7 +85,7 @@ message ResponseData { enum EventType { // EventType_unknownEvent is an unknown EventType. unknownEvent = 0; - // EventType_userSelected is an user selected EventType. + // EventType_userSelected is a user selected EventType. userSelected = 1; // EventType_brokersReceived is a broker received EventType. brokersReceived = 2; diff --git a/pam/internal/pam_test/module-transaction-dummy_test.go b/pam/internal/pam_test/module-transaction-dummy_test.go index 6dc29cd425..95eec073d4 100644 --- a/pam/internal/pam_test/module-transaction-dummy_test.go +++ b/pam/internal/pam_test/module-transaction-dummy_test.go @@ -33,7 +33,7 @@ func TestSetGetItem(t *testing.T) { }{ "Set_user": { item: pam.User, - value: ptrValue("an user"), + value: ptrValue("a user"), }, "Returns_empty_when_getting_an_unset_user": { item: pam.User, From 6293b81aaab44d4d07e0099e0e2cce1a0b320bb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Sat, 5 Jul 2025 05:54:51 +0200 Subject: [PATCH 0590/1670] users/idgenerator: Do not allow using Linux reserved IDs Some IDs are reserved in linux, and so we should never use them, even if they are in the configured range --- internal/users/idgenerator.go | 50 ++++++++++++++++++++++++++++++ internal/users/idgenerator_test.go | 28 +++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/internal/users/idgenerator.go b/internal/users/idgenerator.go index 0bfce5fa08..dc57118dbb 100644 --- a/internal/users/idgenerator.go +++ b/internal/users/idgenerator.go @@ -48,6 +48,14 @@ type IDGenerator struct { // from other NSS sources when determining which candidates to exclude. const maxIDGenerateIterations = 1000 +// Reserved IDs. +const ( + rootID uint32 = 0 + nobodyID uint32 = 65534 + uidT32MinusOne uint32 = math.MaxUint32 + uidT16MinusOne uint32 = math.MaxUint16 +) + // GenerateUID generates a random UID in the configured range. func (g *IDGenerator) GenerateUID(lockedEntries *localentries.UserDBLocked, owner IDOwner) (uint32, func(), error) { return g.generateID(lockedEntries, owner, generateID{ @@ -101,6 +109,13 @@ func (g *IDGenerator) generateID(lockedEntries *localentries.UserDBLocked, owner return 0, nil, err } + if !isReservedID(id) { + // Keep track of the id, but preserving usedIDs sorted, since we + // use the binary search to add elements to it. + usedIDs = slices.Insert(usedIDs, pos, id) + continue + } + available, err := args.isAvailableID(lockedEntries, id) if err != nil { return 0, nil, err @@ -128,6 +143,41 @@ func (g *IDGenerator) generateID(lockedEntries *localentries.UserDBLocked, owner args.idType, maxAttempts) } +// isReservedID checks if the ID is a value is not a linux system reserved value. +// Note that we are not listing here the system IDs (1…999), as this the job +// for the [Manager], being a wrong configuration. +// See: https://systemd.io/UIDS-GIDS/ +func isReservedID(id uint32) bool { + switch id { + case rootID: + // The root super-user. + log.Warningf(context.Background(), + "ID %d cannot be used: it is the root super-user.", id) + return false + + case nobodyID: + // Nobody user. + log.Warningf(context.Background(), + "ID %d cannot be used: it is nobody user.", id) + return false + + case uidT32MinusOne: + // uid_t-1 (32): Special non-valid ID for `setresuid` and `chown`. + log.Warningf(context.Background(), + "ID %d cannot be used: it is uid_t-1 (32bit).") + return false + + case uidT16MinusOne: + // uid_t-1 (16): As before, but comes from legacy 16bit programs. + log.Warningf(context.Background(), + "ID %d cannot be used: it is uid_t-1 (16bit)", id) + return false + + default: + return true + } +} + func getIDCandidate(minID, maxID uint32, usedIDs []uint32) (id uint32, uniqueIDsPos int, err error) { // Pick the preferred ID candidate, starting with minID. preferredID := minID diff --git a/internal/users/idgenerator_test.go b/internal/users/idgenerator_test.go index 412552f3a4..bcd49c2c19 100644 --- a/internal/users/idgenerator_test.go +++ b/internal/users/idgenerator_test.go @@ -229,6 +229,34 @@ func TestGenerateIDMocked(t *testing.T) { owner: IDOwnerMock{usedUIDs: []uint32{300, 303}}, wantID: 302, }, + "Root_uid_is_always_skipped": { + genID: generateID{ + idType: "UID", + minID: 0, maxID: 1, + isAvailableID: allAvailableIDsFunc, + getUsedIDs: noUsedIDFunc, + }, + wantID: 1, + }, + "Nobody_and_uid-t_16bit_are_always_skipped": { + genID: generateID{ + idType: "UID", + minID: nobodyID, maxID: nobodyID + 2, + isAvailableID: allAvailableIDsFunc, + getUsedIDs: noUsedIDFunc, + }, + wantID: nobodyID + 2, + }, + "uid-t_32bit_is_always_skipped": { + genID: generateID{ + idType: "UID", + minID: uidT32MinusOne - 2, maxID: uidT32MinusOne, + isAvailableID: allAvailableIDsFunc, + getUsedIDs: getOwnerUsedUIDsFunc, + }, + owner: IDOwnerMock{usedUIDs: []uint32{uidT32MinusOne}}, + wantID: uidT32MinusOne - 2, + }, // Error cases "Error_if_minID_is_equal_to_maxID": { From 821604fbcd4a023898a9ca3b807da7a3282f8edd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Sat, 5 Jul 2025 06:00:17 +0200 Subject: [PATCH 0591/1670] users/idgenerator: Do not allow generating user IDs in reserved ranges We now may happen to generate IDs in reserved ranges, while these ranges can be configured, it is not correct for us to try to invade other well known ID spaces, so let's just disallow them. Following https://systemd.io/UIDS-GIDS/ --- internal/users/idgenerator.go | 80 ++++++++++++++++- internal/users/idgenerator_test.go | 135 +++++++++++++++++++++++++++++ 2 files changed, 214 insertions(+), 1 deletion(-) diff --git a/internal/users/idgenerator.go b/internal/users/idgenerator.go index dc57118dbb..e9d387fe6a 100644 --- a/internal/users/idgenerator.go +++ b/internal/users/idgenerator.go @@ -56,6 +56,33 @@ const ( uidT16MinusOne uint32 = math.MaxUint16 ) +// Systemd used ranges. +const ( + // FIXME: Do not hardcode them, use go-generate script to define these + // values as constants using pkg-config instead. + + // Human users (homed) (nss-systemd). + nssSystemdHomedMin uint32 = 60001 + nssSystemdHomedMax uint32 = 60513 + + // Host users mapped into containers (systemd-nspawn). + systemdContainersUsersMin uint32 = 60514 + systemdContainersUsersMax uint32 = 60577 + + // Dynamic service users (nss-systemd). + nssSystemdDynamicServiceUsersMin uint32 = 61184 + nssSystemdDynamicServiceUsersMax uint32 = 65519 + + // Container UID ranges (nss-systemd). + // According to https://systemd.io/UIDS-GIDS/, systemd-nspawn will check NSS + // for collisions before allocating a UID in this range, which would make it + // safe for us to use. However, it also says that, for performance reasons, + // it will only check for the first UID of the range it allocates, so we do + // need to avoid using the whole range. + nssSystemdContainerMin uint32 = 524288 + nssSystemdContainerMax uint32 = 1879048191 +) + // GenerateUID generates a random UID in the configured range. func (g *IDGenerator) GenerateUID(lockedEntries *localentries.UserDBLocked, owner IDOwner) (uint32, func(), error) { return g.generateID(lockedEntries, owner, generateID{ @@ -91,6 +118,11 @@ func (g *IDGenerator) generateID(lockedEntries *localentries.UserDBLocked, owner return 0, nil, errors.New("minID must be less than or equal to maxID") } + args.minID = adjustIDForSafeRanges(args.minID) + if args.minID > args.maxID { + return 0, nil, errors.New("no usable ID in range") + } + usedIDs, err := args.getUsedIDs(lockedEntries, owner) if err != nil { return 0, nil, err @@ -178,6 +210,45 @@ func isReservedID(id uint32) bool { } } +// adjustIDForSafeRanges verifies if the ID value can be safely used, by +// checking if it's part of any of the well known the ID ranges that can't be +// used, as per being part of the linux (systemd) reserved ranges. +// See (again) https://systemd.io/UIDS-GIDS/ +func adjustIDForSafeRanges(id uint32) (adjustedID uint32) { + initialID := id + defer func() { + if adjustedID == initialID { + return + } + + log.Noticef(context.Background(), + "ID %d is within a range used by systemd and cannot be used; "+ + "skipping to the next available ID (%d)", initialID, adjustedID) + }() + + // Human users (homed) (nss-systemd) - adjacent to containers users! + if id >= nssSystemdHomedMin && id <= nssSystemdHomedMax { + id = nssSystemdHomedMax + 1 + } + + // Host users mapped into containers (systemd-nspawn) - adjacent to homed! + if id >= systemdContainersUsersMin && id <= systemdContainersUsersMax { + id = systemdContainersUsersMax + 1 + } + + // Dynamic service users (nss-systemd) + if id >= nssSystemdDynamicServiceUsersMin && id <= nssSystemdDynamicServiceUsersMax { + id = nssSystemdDynamicServiceUsersMax + 1 + } + + // Container UID ranges (nss-systemd) + if id >= nssSystemdContainerMin && id <= nssSystemdContainerMax { + id = nssSystemdContainerMax + 1 + } + + return id +} + func getIDCandidate(minID, maxID uint32, usedIDs []uint32) (id uint32, uniqueIDsPos int, err error) { // Pick the preferred ID candidate, starting with minID. preferredID := minID @@ -193,7 +264,14 @@ func getIDCandidate(minID, maxID uint32, usedIDs []uint32) (id uint32, uniqueIDs } // Try IDs starting from the preferred ID up to the maximum ID. - for id := preferredID; id <= maxID; id++ { + for id := preferredID; ; id++ { + // Sanitize the ID to ensure that it's not in a restricted range. + id = adjustIDForSafeRanges(id) + + if id > maxID { + break + } + if pos, found := slices.BinarySearch(usedIDs, id); !found { return id, pos, nil } diff --git a/internal/users/idgenerator_test.go b/internal/users/idgenerator_test.go index bcd49c2c19..cc1bd26544 100644 --- a/internal/users/idgenerator_test.go +++ b/internal/users/idgenerator_test.go @@ -257,6 +257,94 @@ func TestGenerateIDMocked(t *testing.T) { owner: IDOwnerMock{usedUIDs: []uint32{uidT32MinusOne}}, wantID: uidT32MinusOne - 2, }, + "IDs_in_systemd_homed_and_systemd_containers_range_are_skipped": { + genID: generateID{ + idType: "UID", + minID: nssSystemdHomedMin, maxID: systemdContainersUsersMax + 86, + isAvailableID: allAvailableIDsFunc, + getUsedIDs: noUsedIDFunc, + }, + wantID: systemdContainersUsersMax + 1, + }, + "IDs_in_systemd_homed_and_systemd_containers_range_are_skipped_when_next_to_preferred_id": { + genID: generateID{ + idType: "UID", + minID: nssSystemdHomedMin - 10, maxID: systemdContainersUsersMax + 10, + isAvailableID: allAvailableIDsFunc, + getUsedIDs: func(ud *localentries.UserDBLocked, i IDOwner) ([]uint32, error) { + return []uint32{nssSystemdHomedMin - 1}, nil + }, + }, + wantID: systemdContainersUsersMax + 1, + }, + "IDs_in_systemd_homed_and_systemd_containers_range_are_skipped_when_next_to_preferred_id_minus_one": { + genID: generateID{ + idType: "UID", + minID: nssSystemdHomedMin - 10, maxID: systemdContainersUsersMax + 10, + isAvailableID: allAvailableIDsFunc, + getUsedIDs: func(ud *localentries.UserDBLocked, i IDOwner) ([]uint32, error) { + return []uint32{nssSystemdHomedMin - 1}, nil + }, + }, + wantID: systemdContainersUsersMax + 1, + }, + "IDs_in_systemd_homed_and_systemd_containers_range_are_not_skipped_when_next_to_min_id": { + genID: generateID{ + idType: "UID", + minID: nssSystemdHomedMin - 1, maxID: systemdContainersUsersMax + 10, + isAvailableID: allAvailableIDsFunc, + getUsedIDs: noUsedIDFunc, + }, + wantID: nssSystemdHomedMin - 1, + }, + "IDs_in_systemd_homed_and_systemd_containers_range_are_not_skipped_when_next_to_min_id_minus_one": { + genID: generateID{ + idType: "UID", + minID: nssSystemdHomedMin - 2, maxID: systemdContainersUsersMax + 10, + isAvailableID: allAvailableIDsFunc, + getUsedIDs: noUsedIDFunc, + }, + wantID: nssSystemdHomedMin - 2, + }, + "IDs_in_systemd_dynamic_service_users_range_are_skipped": { + genID: generateID{ + idType: "UID", + minID: nssSystemdDynamicServiceUsersMin - 55, maxID: nssSystemdDynamicServiceUsersMax + 1, + isAvailableID: func(ud *localentries.UserDBLocked, u uint32) (bool, error) { + return u != nssSystemdDynamicServiceUsersMax+1, nil + }, + getUsedIDs: getOwnerUsedUIDsFunc, + }, + owner: IDOwnerMock{usedUIDs: []uint32{nssSystemdDynamicServiceUsersMin + 1}}, + wantID: nssSystemdDynamicServiceUsersMin - 55, + }, + "IDs_in_systemd_containers_and_dynamic_service_user_range_are_skipped": { + genID: generateID{ + idType: "UID", + minID: systemdContainersUsersMin, maxID: nssSystemdDynamicServiceUsersMax, + isAvailableID: func(ud *localentries.UserDBLocked, u uint32) (bool, error) { + return u != systemdContainersUsersMax+2, nil + }, + getUsedIDs: getOwnerUsedUIDsFunc, + }, + owner: IDOwnerMock{usedUIDs: []uint32{ + nssSystemdDynamicServiceUsersMax - 10, + systemdContainersUsersMax + 1, + }}, + wantID: systemdContainersUsersMax + 3, + }, + "IDs_in_systemd_container_range_are_skipped": { + genID: generateID{ + idType: "UID", + minID: nssSystemdContainerMin - 8, maxID: nssSystemdContainerMax, + isAvailableID: func(ud *localentries.UserDBLocked, u uint32) (bool, error) { + return u != nssSystemdContainerMin-8, nil + }, + getUsedIDs: getOwnerUsedUIDsFunc, + }, + owner: IDOwnerMock{usedUIDs: []uint32{(nssSystemdContainerMin + nssSystemdContainerMax) / 2}}, + wantID: nssSystemdContainerMin - 7, + }, // Error cases "Error_if_minID_is_equal_to_maxID": { @@ -338,6 +426,53 @@ func TestGenerateIDMocked(t *testing.T) { owner: IDOwnerMock{usedUIDs: []uint32{10, 11, 12}}, wantErr: true, }, + "Errors_if_IDs_only_in_systemd_homed_range": { + genID: generateID{ + idType: "UID", + minID: nssSystemdHomedMin, maxID: nssSystemdHomedMax, + isAvailableID: allAvailableIDsFunc, + getUsedIDs: noUsedIDFunc, + }, + wantErr: true, + }, + "Errors_if_IDs_only_in_systemd_containers_range": { + genID: generateID{ + idType: "UID", + minID: systemdContainersUsersMin, maxID: systemdContainersUsersMax, + isAvailableID: allAvailableIDsFunc, + getUsedIDs: noUsedIDFunc, + }, + wantErr: true, + }, + "Errors_if_IDs_only_in_systemd_dynamic_service_user_range": { + genID: generateID{ + idType: "UID", + minID: nssSystemdDynamicServiceUsersMin, maxID: nssSystemdDynamicServiceUsersMax, + isAvailableID: allAvailableIDsFunc, + getUsedIDs: noUsedIDFunc, + }, + wantErr: true, + }, + "Errors_if_IDs_only_in_systemd_containers_and_dynamic_service_user_range_with_other_used_IDs": { + genID: generateID{ + idType: "UID", + minID: systemdContainersUsersMin, maxID: nssSystemdDynamicServiceUsersMax, + isAvailableID: func(ud *localentries.UserDBLocked, u uint32) (bool, error) { + return u <= systemdContainersUsersMax && u >= nssSystemdDynamicServiceUsersMin, nil + }, + getUsedIDs: noUsedIDFunc, + }, + wantErr: true, + }, + "Errors_if_IDs_only_in_container_nss-systemd_range": { + genID: generateID{ + idType: "UID", + minID: nssSystemdContainerMin, maxID: nssSystemdContainerMax, + isAvailableID: allAvailableIDsFunc, + getUsedIDs: noUsedIDFunc, + }, + wantErr: true, + }, } for name, tc := range tests { From 031897574976e7e8b10c143d25438ed220140834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Sat, 5 Jul 2025 06:02:57 +0200 Subject: [PATCH 0592/1670] users/idgenerator: Warn if a generated ID is above the signed-32bit value They may have problems with some programs, so inform about it. --- internal/users/idgenerator.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/users/idgenerator.go b/internal/users/idgenerator.go index e9d387fe6a..0b625693fb 100644 --- a/internal/users/idgenerator.go +++ b/internal/users/idgenerator.go @@ -163,6 +163,12 @@ func (g *IDGenerator) generateID(lockedEntries *localentries.UserDBLocked, owner continue } + if id > maxSuggestedID { + log.Warningf(context.Background(), + "Generated ID %d is outside the signed 32-bit range, "+ + "it may not work as expected", id) + } + g.pendingIDs = append(g.pendingIDs, id) cleanup = func() { idx := slices.Index(g.pendingIDs, id) From 3dd198cb19c186bb26fbb85046fb120fd1d33db1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Sat, 5 Jul 2025 06:05:17 +0200 Subject: [PATCH 0593/1670] users/manager: Do not allow to configure ID ranges owned by the system IDs <= 999 are reserved so authd should never generate them, thus add a check that enforces that they cannot be used, and if they are enabled, just don't allow them to be used. --- internal/users/export_test.go | 11 ++++ internal/users/manager.go | 35 +++++++++- internal/users/manager_test.go | 24 ++++++- ..._manager_with_partially_invalid_GID_ranges | 64 +++++++++++++++++++ ..._manager_with_partially_invalid_UID_ranges | 64 +++++++++++++++++++ ...g_manager_with_partially_invalid_id_ranges | 64 +++++++++++++++++++ ...anager_with_potentially_invalid_GID_ranges | 64 +++++++++++++++++++ ...anager_with_potentially_invalid_UID_ranges | 64 +++++++++++++++++++ 8 files changed, 387 insertions(+), 3 deletions(-) create mode 100644 internal/users/testdata/golden/TestNewManager/Warns_creating_manager_with_partially_invalid_GID_ranges create mode 100644 internal/users/testdata/golden/TestNewManager/Warns_creating_manager_with_partially_invalid_UID_ranges create mode 100644 internal/users/testdata/golden/TestNewManager/Warns_creating_manager_with_partially_invalid_id_ranges create mode 100644 internal/users/testdata/golden/TestNewManager/Warns_creating_manager_with_potentially_invalid_GID_ranges create mode 100644 internal/users/testdata/golden/TestNewManager/Warns_creating_manager_with_potentially_invalid_UID_ranges diff --git a/internal/users/export_test.go b/internal/users/export_test.go index 1e301e0e7d..f68b301339 100644 --- a/internal/users/export_test.go +++ b/internal/users/export_test.go @@ -5,10 +5,21 @@ import ( "github.com/ubuntu/authd/internal/users/types" ) +const ( + MinAllowedUID = minAllowedUID + MinAllowedGID = minAllowedGID + MaxSuggestedID = maxSuggestedID +) + func (m *Manager) DB() *db.Manager { return m.db } +func (m *Manager) RealIDGenerator() *IDGenerator { + //nolint:forcetypeassert // We really want to panic if it's not true. + return m.idGenerator.(*IDGenerator) +} + func (m *Manager) GetOldUserInfoFromDB(name string) (oldUserInfo *types.UserInfo, err error) { return m.getOldUserInfoFromDB(name) } diff --git a/internal/users/manager.go b/internal/users/manager.go index c4c21f7b9c..8d521254f8 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "math" "os" "slices" "strings" @@ -27,6 +28,15 @@ type Config struct { GIDMax uint32 `mapstructure:"gid_max" yaml:"gid_max"` } +const ( + // minAllowedUID is the minimum value used to generate the user and group IDs. + // See https://systemd.io/UIDS-GIDS/ for reference. + minAllowedUID uint32 = 1000 + minAllowedGID uint32 = 1000 + + maxSuggestedID uint32 = math.MaxInt32 +) + // DefaultConfig is the default configuration for the user manager. var DefaultConfig = Config{ UIDMin: 1000000000, @@ -80,8 +90,31 @@ func NewManager(config Config, dbDir string, args ...Option) (m *Manager, err er if config.GIDMin >= config.GIDMax { return nil, errors.New("GID_MIN must be less than GID_MAX") } + + // See https://systemd.io/UIDS-GIDS/ for reference. + if config.UIDMin < minAllowedUID { + log.Warning(context.Background(), "authd is configured to potentially use "+ + "system users IDs (as per the default SYS_UID_MIN and SYS_UID_MAX values). "+ + "This may not be safe.") + } + if config.GIDMin < minAllowedGID { + log.Warning(context.Background(), "authd is configured to potentially use "+ + "system group IDs (as per the default SYS_GID_MIN and SYS_GID_MAX values). "+ + "This may not be safe.") + } + if config.UIDMax > maxSuggestedID { + log.Warningf(context.Background(), "authd is configured to use maximum user ID values "+ + "outside the signed 32-bit range, this is not safe and may lead some programs no to work "+ + "as expected. Adjust UID_MAX to a value minor or equal than %d", maxSuggestedID) + } + if config.GIDMax > maxSuggestedID { + log.Warningf(context.Background(), "authd is configured to use maximum groups ID values "+ + "outside the signed 32-bit range, this is not safe and may lead some programs no to work "+ + "as expected. Adjust GID_MAX to a value minor or equal than %d", maxSuggestedID) + } + // Check that the number of possible UIDs is at least twice the number of possible pre-auth users. - numUIDs := config.UIDMax - config.UIDMin + numUIDs := config.UIDMax - config.UIDMin + 1 minNumUIDs := uint32(tempentries.MaxPreAuthUsers * 2) if numUIDs < minNumUIDs { return nil, fmt.Errorf("UID range configured via UID_MIN and UID_MAX is too small (%d), must be at least %d", numUIDs, minNumUIDs) diff --git a/internal/users/manager_test.go b/internal/users/manager_test.go index 6f0bc9b609..08b985ba75 100644 --- a/internal/users/manager_test.go +++ b/internal/users/manager_test.go @@ -43,9 +43,17 @@ func TestNewManager(t *testing.T) { "Successfully_create_manager_with_default_config": {}, "Successfully_create_manager_with_custom_config": {uidMin: 10000, uidMax: 20000, gidMin: 10000, gidMax: 20000}, + "Warns_creating_manager_with_partially_invalid_UID_ranges": {uidMin: 1, uidMax: 20000}, + "Warns_creating_manager_with_partially_invalid_GID_ranges": {gidMin: 1, gidMax: 20000}, + + "Warns_creating_manager_with_potentially_invalid_UID_ranges": {uidMin: 20000, uidMax: users.MaxSuggestedID + 1}, + "Warns_creating_manager_with_potentially_invalid_GID_ranges": {gidMin: 20000, gidMax: users.MaxSuggestedID + 1}, + // Corrupted databases - "Error_when_database_is_corrupted": {corruptedDbFile: true, wantErr: true}, - "Error_if_dbDir_does_not_exist": {dbFile: "-", wantErr: true}, + "Error_when_database_is_corrupted": {corruptedDbFile: true, wantErr: true}, + "Error_if_dbDir_does_not_exist": {dbFile: "-", wantErr: true}, + + // Invalid UIDs/GIDs ranges "Error_if_UID_MIN_is_equal_to_UID_MAX": {uidMin: 1000, uidMax: 1000, wantErr: true}, "Error_if_GID_MIN_is_equal_to_GID_MAX": {gidMin: 1000, gidMax: 1000, wantErr: true}, "Error_if_UID_range_is_too_small": {uidMin: 1000, uidMax: 2000, wantErr: true}, @@ -87,6 +95,7 @@ func TestNewManager(t *testing.T) { m, err := users.NewManager(config, dbDir) if tc.wantErr { + t.Logf("Manager creation exited with %v", err) require.Error(t, err, "NewManager should return an error, but did not") return } @@ -97,6 +106,17 @@ func TestNewManager(t *testing.T) { golden.CheckOrUpdate(t, got) + idGenerator := m.RealIDGenerator() + + require.Equal(t, int(config.UIDMin), int(idGenerator.UIDMin), + "ID generator UIDMin has not the expected value") + require.Equal(t, int(config.UIDMax), int(idGenerator.UIDMax), + "ID generator UIDMax has not the expected value") + require.Equal(t, int(config.GIDMin), int(idGenerator.GIDMin), + "ID generator GIDMin has not the expected value") + require.Equal(t, int(config.GIDMax), int(idGenerator.GIDMax), + "ID generator GIDMax has not the expected value") + localgroupstestutils.RequireGroupFile(t, destGroupFile, golden.Path(t)) }) } diff --git a/internal/users/testdata/golden/TestNewManager/Warns_creating_manager_with_partially_invalid_GID_ranges b/internal/users/testdata/golden/TestNewManager/Warns_creating_manager_with_partially_invalid_GID_ranges new file mode 100644 index 0000000000..5d465349a9 --- /dev/null +++ b/internal/users/testdata/golden/TestNewManager/Warns_creating_manager_with_partially_invalid_GID_ranges @@ -0,0 +1,64 @@ +users: + - name: user1 + uid: 1111 + gid: 11111 + gecos: |- + User1 gecos + On multiple lines + dir: /home/user1 + shell: /bin/bash + broker_id: broker-id + - name: user2 + uid: 2222 + gid: 22222 + gecos: User2 + dir: /home/user2 + shell: /bin/dash + broker_id: broker-id + - name: user3 + uid: 3333 + gid: 33333 + gecos: User3 + dir: /home/user3 + shell: /bin/zsh + broker_id: broker-id + - name: userwithoutbroker + uid: 4444 + gid: 44444 + gecos: userwithoutbroker + dir: /home/userwithoutbroker + shell: /bin/sh +groups: + - name: group1 + gid: 11111 + ugid: "12345678" + - name: group2withoutugid + gid: 22222 + ugid: "" + - name: group3 + gid: 33333 + ugid: "34567812" + - name: group4 + gid: 44444 + ugid: "45678123" + - name: commongroup + gid: 99999 + ugid: "87654321" +users_to_groups: + - uid: 1111 + gid: 11111 + - uid: 1111 + gid: 99999 + - uid: 2222 + gid: 22222 + - uid: 2222 + gid: 99999 + - uid: 3333 + gid: 33333 + - uid: 3333 + gid: 99999 + - uid: 4444 + gid: 44444 + - uid: 4444 + gid: 99999 +schema_version: 1 diff --git a/internal/users/testdata/golden/TestNewManager/Warns_creating_manager_with_partially_invalid_UID_ranges b/internal/users/testdata/golden/TestNewManager/Warns_creating_manager_with_partially_invalid_UID_ranges new file mode 100644 index 0000000000..5d465349a9 --- /dev/null +++ b/internal/users/testdata/golden/TestNewManager/Warns_creating_manager_with_partially_invalid_UID_ranges @@ -0,0 +1,64 @@ +users: + - name: user1 + uid: 1111 + gid: 11111 + gecos: |- + User1 gecos + On multiple lines + dir: /home/user1 + shell: /bin/bash + broker_id: broker-id + - name: user2 + uid: 2222 + gid: 22222 + gecos: User2 + dir: /home/user2 + shell: /bin/dash + broker_id: broker-id + - name: user3 + uid: 3333 + gid: 33333 + gecos: User3 + dir: /home/user3 + shell: /bin/zsh + broker_id: broker-id + - name: userwithoutbroker + uid: 4444 + gid: 44444 + gecos: userwithoutbroker + dir: /home/userwithoutbroker + shell: /bin/sh +groups: + - name: group1 + gid: 11111 + ugid: "12345678" + - name: group2withoutugid + gid: 22222 + ugid: "" + - name: group3 + gid: 33333 + ugid: "34567812" + - name: group4 + gid: 44444 + ugid: "45678123" + - name: commongroup + gid: 99999 + ugid: "87654321" +users_to_groups: + - uid: 1111 + gid: 11111 + - uid: 1111 + gid: 99999 + - uid: 2222 + gid: 22222 + - uid: 2222 + gid: 99999 + - uid: 3333 + gid: 33333 + - uid: 3333 + gid: 99999 + - uid: 4444 + gid: 44444 + - uid: 4444 + gid: 99999 +schema_version: 1 diff --git a/internal/users/testdata/golden/TestNewManager/Warns_creating_manager_with_partially_invalid_id_ranges b/internal/users/testdata/golden/TestNewManager/Warns_creating_manager_with_partially_invalid_id_ranges new file mode 100644 index 0000000000..5d465349a9 --- /dev/null +++ b/internal/users/testdata/golden/TestNewManager/Warns_creating_manager_with_partially_invalid_id_ranges @@ -0,0 +1,64 @@ +users: + - name: user1 + uid: 1111 + gid: 11111 + gecos: |- + User1 gecos + On multiple lines + dir: /home/user1 + shell: /bin/bash + broker_id: broker-id + - name: user2 + uid: 2222 + gid: 22222 + gecos: User2 + dir: /home/user2 + shell: /bin/dash + broker_id: broker-id + - name: user3 + uid: 3333 + gid: 33333 + gecos: User3 + dir: /home/user3 + shell: /bin/zsh + broker_id: broker-id + - name: userwithoutbroker + uid: 4444 + gid: 44444 + gecos: userwithoutbroker + dir: /home/userwithoutbroker + shell: /bin/sh +groups: + - name: group1 + gid: 11111 + ugid: "12345678" + - name: group2withoutugid + gid: 22222 + ugid: "" + - name: group3 + gid: 33333 + ugid: "34567812" + - name: group4 + gid: 44444 + ugid: "45678123" + - name: commongroup + gid: 99999 + ugid: "87654321" +users_to_groups: + - uid: 1111 + gid: 11111 + - uid: 1111 + gid: 99999 + - uid: 2222 + gid: 22222 + - uid: 2222 + gid: 99999 + - uid: 3333 + gid: 33333 + - uid: 3333 + gid: 99999 + - uid: 4444 + gid: 44444 + - uid: 4444 + gid: 99999 +schema_version: 1 diff --git a/internal/users/testdata/golden/TestNewManager/Warns_creating_manager_with_potentially_invalid_GID_ranges b/internal/users/testdata/golden/TestNewManager/Warns_creating_manager_with_potentially_invalid_GID_ranges new file mode 100644 index 0000000000..5d465349a9 --- /dev/null +++ b/internal/users/testdata/golden/TestNewManager/Warns_creating_manager_with_potentially_invalid_GID_ranges @@ -0,0 +1,64 @@ +users: + - name: user1 + uid: 1111 + gid: 11111 + gecos: |- + User1 gecos + On multiple lines + dir: /home/user1 + shell: /bin/bash + broker_id: broker-id + - name: user2 + uid: 2222 + gid: 22222 + gecos: User2 + dir: /home/user2 + shell: /bin/dash + broker_id: broker-id + - name: user3 + uid: 3333 + gid: 33333 + gecos: User3 + dir: /home/user3 + shell: /bin/zsh + broker_id: broker-id + - name: userwithoutbroker + uid: 4444 + gid: 44444 + gecos: userwithoutbroker + dir: /home/userwithoutbroker + shell: /bin/sh +groups: + - name: group1 + gid: 11111 + ugid: "12345678" + - name: group2withoutugid + gid: 22222 + ugid: "" + - name: group3 + gid: 33333 + ugid: "34567812" + - name: group4 + gid: 44444 + ugid: "45678123" + - name: commongroup + gid: 99999 + ugid: "87654321" +users_to_groups: + - uid: 1111 + gid: 11111 + - uid: 1111 + gid: 99999 + - uid: 2222 + gid: 22222 + - uid: 2222 + gid: 99999 + - uid: 3333 + gid: 33333 + - uid: 3333 + gid: 99999 + - uid: 4444 + gid: 44444 + - uid: 4444 + gid: 99999 +schema_version: 1 diff --git a/internal/users/testdata/golden/TestNewManager/Warns_creating_manager_with_potentially_invalid_UID_ranges b/internal/users/testdata/golden/TestNewManager/Warns_creating_manager_with_potentially_invalid_UID_ranges new file mode 100644 index 0000000000..5d465349a9 --- /dev/null +++ b/internal/users/testdata/golden/TestNewManager/Warns_creating_manager_with_potentially_invalid_UID_ranges @@ -0,0 +1,64 @@ +users: + - name: user1 + uid: 1111 + gid: 11111 + gecos: |- + User1 gecos + On multiple lines + dir: /home/user1 + shell: /bin/bash + broker_id: broker-id + - name: user2 + uid: 2222 + gid: 22222 + gecos: User2 + dir: /home/user2 + shell: /bin/dash + broker_id: broker-id + - name: user3 + uid: 3333 + gid: 33333 + gecos: User3 + dir: /home/user3 + shell: /bin/zsh + broker_id: broker-id + - name: userwithoutbroker + uid: 4444 + gid: 44444 + gecos: userwithoutbroker + dir: /home/userwithoutbroker + shell: /bin/sh +groups: + - name: group1 + gid: 11111 + ugid: "12345678" + - name: group2withoutugid + gid: 22222 + ugid: "" + - name: group3 + gid: 33333 + ugid: "34567812" + - name: group4 + gid: 44444 + ugid: "45678123" + - name: commongroup + gid: 99999 + ugid: "87654321" +users_to_groups: + - uid: 1111 + gid: 11111 + - uid: 1111 + gid: 99999 + - uid: 2222 + gid: 22222 + - uid: 2222 + gid: 99999 + - uid: 3333 + gid: 33333 + - uid: 3333 + gid: 99999 + - uid: 4444 + gid: 44444 + - uid: 4444 + gid: 99999 +schema_version: 1 From 577a8f0f783343933c70562c694273661c643113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Sat, 5 Jul 2025 07:11:52 +0200 Subject: [PATCH 0594/1670] users/manager: Also expose the pre-auth private group on GetGroupByID If a NSS request for the given GID happens we should preemptively return the user GID, or some other NSS source may reuse it --- internal/users/manager.go | 4 +++ internal/users/manager_test.go | 36 +++++++++++++++---- internal/users/tempentries/preauth.go | 15 ++++++++ ...cessfully_get_group_by_ID_for_preauth_user | 5 +++ 4 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 internal/users/testdata/golden/TestGroupByIDAndName/Successfully_get_group_by_ID_for_preauth_user diff --git a/internal/users/manager.go b/internal/users/manager.go index 8d521254f8..ca93a8e64b 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -563,6 +563,10 @@ func (m *Manager) GroupByName(groupname string) (types.GroupEntry, error) { // GroupByID returns the group information for the given group ID. func (m *Manager) GroupByID(gid uint32) (types.GroupEntry, error) { grp, err := m.db.GroupWithMembersByID(gid) + if errors.Is(err, db.NoDataFoundError{}) { + // Check if the ID will be the private-group of a temporary user. + return m.preAuthRecords.GroupByID(gid) + } if err != nil { return types.GroupEntry{}, err } diff --git a/internal/users/manager_test.go b/internal/users/manager_test.go index 08b985ba75..43b4a1b213 100644 --- a/internal/users/manager_test.go +++ b/internal/users/manager_test.go @@ -869,15 +869,17 @@ func TestGroupByIDAndName(t *testing.T) { t.Parallel() tests := map[string]struct { - gid uint32 - groupname string - dbFile string + gid uint32 + groupname string + dbFile string + preAuthUser string wantErr bool wantErrType error }{ - "Successfully_get_group_by_ID": {gid: 11111, dbFile: "multiple_users_and_groups"}, - "Successfully_get_group_by_name": {groupname: "group1", dbFile: "multiple_users_and_groups"}, + "Successfully_get_group_by_ID": {gid: 11111, dbFile: "multiple_users_and_groups"}, + "Successfully_get_group_by_ID_for_preauth_user": {preAuthUser: "hello-authd", dbFile: "multiple_users_and_groups"}, + "Successfully_get_group_by_name": {groupname: "group1", dbFile: "multiple_users_and_groups"}, "Error_if_group_does_not_exist_-_by_ID": {gid: 0, dbFile: "multiple_users_and_groups", wantErrType: db.NoDataFoundError{}}, "Error_if_group_does_not_exist_-_by_name": {groupname: "doesnotexist", dbFile: "multiple_users_and_groups", wantErrType: db.NoDataFoundError{}}, @@ -889,7 +891,16 @@ func TestGroupByIDAndName(t *testing.T) { dbDir := t.TempDir() err := db.Z_ForTests_CreateDBFromYAML(filepath.Join("testdata", "db", tc.dbFile+".db.yaml"), dbDir) require.NoError(t, err, "Setup: could not create database from testdata") - m := newManagerForTests(t, dbDir) + m := newManagerForTests(t, dbDir, users.WithIDGenerator(&users.IDGeneratorMock{ + UIDsToGenerate: []uint32{12345}, + GIDsToGenerate: []uint32{12345}, + })) + + if tc.preAuthUser != "" { + tc.gid, err = m.RegisterUserPreAuth(tc.preAuthUser) + require.NoError(t, err, "RegisterUserPreAuth should not fail for %q, but it did", + tc.preAuthUser) + } var group types.GroupEntry if tc.groupname != "" { @@ -903,6 +914,19 @@ func TestGroupByIDAndName(t *testing.T) { return } + if tc.preAuthUser != "" { + require.True(t, strings.HasPrefix(group.Name, tempentries.UserPrefix), + "Pre-auth user group should have %q as prefix: %q", tempentries.UserPrefix, + group.Name) + group.Name = tempentries.UserPrefix + "-{{RANDOM_ID}}" + + require.Len(t, group.Users, 1, "Users length mismatch") + require.True(t, strings.HasPrefix(group.Users[0], tempentries.UserPrefix), + "Pre-auth user should have %q as prefix: %q", tempentries.UserPrefix, + group.Users[0]) + group.Users[0] = tempentries.UserPrefix + "-{{RANDOM_ID}}" + } + golden.CheckOrUpdateYAML(t, group) }) } diff --git a/internal/users/tempentries/preauth.go b/internal/users/tempentries/preauth.go index 26aa323677..7a418e2f4a 100644 --- a/internal/users/tempentries/preauth.go +++ b/internal/users/tempentries/preauth.go @@ -91,6 +91,21 @@ func (r *PreAuthUserRecords) UserByLogin(name string) (types.UserEntry, error) { return r.userByLogin(name) } +// GroupByID returns the private-group information for the given user ID. +func (r *PreAuthUserRecords) GroupByID(gid uint32) (types.GroupEntry, error) { + user, err := r.userByID(gid) + if err != nil { + return types.GroupEntry{}, err + } + + return types.GroupEntry{ + Name: user.Name, + GID: user.GID, + Users: []string{user.Name}, + Passwd: "x", + }, nil +} + // AllUsers returns all pre-auth users as a slice of UserEntry. func (r *PreAuthUserRecords) AllUsers() ([]types.UserEntry, error) { r.rwMu.RLock() diff --git a/internal/users/testdata/golden/TestGroupByIDAndName/Successfully_get_group_by_ID_for_preauth_user b/internal/users/testdata/golden/TestGroupByIDAndName/Successfully_get_group_by_ID_for_preauth_user new file mode 100644 index 0000000000..de8789371b --- /dev/null +++ b/internal/users/testdata/golden/TestGroupByIDAndName/Successfully_get_group_by_ID_for_preauth_user @@ -0,0 +1,5 @@ +name: authd-pre-auth-user-{{RANDOM_ID}} +gid: 12345 +users: + - authd-pre-auth-user-{{RANDOM_ID}} +passwd: x From 3ad6d91f896aab71a1d64894e78d2d5deb124c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Trevisan=20=28Trevi=C3=B1o=29?= Date: Mon, 7 Jul 2025 17:36:52 +0200 Subject: [PATCH 0595/1670] users/idlimits: Get systemd limits from pkg-config Add a generator to get the systemd limits from systemd, in fact one value in ubuntu was different than the upstream one (container base max) so doing this is definitely safer. --- internal/users/generate.go | 3 + internal/users/idgenerator.go | 12 +-- internal/users/idlimits.go | 20 +++++ .../idlimitsgenerator/idlimitsgenerator.go | 86 +++++++++++++++++++ internal/users/manager.go | 4 +- 5 files changed, 117 insertions(+), 8 deletions(-) create mode 100644 internal/users/generate.go create mode 100644 internal/users/idlimits.go create mode 100644 internal/users/idlimitsgenerator/idlimitsgenerator.go diff --git a/internal/users/generate.go b/internal/users/generate.go new file mode 100644 index 0000000000..3fa911b7d5 --- /dev/null +++ b/internal/users/generate.go @@ -0,0 +1,3 @@ +package users + +//go:generate go run -tags generate ./idlimitsgenerator/ diff --git a/internal/users/idgenerator.go b/internal/users/idgenerator.go index 0b625693fb..84577ded65 100644 --- a/internal/users/idgenerator.go +++ b/internal/users/idgenerator.go @@ -58,8 +58,8 @@ const ( // Systemd used ranges. const ( - // FIXME: Do not hardcode them, use go-generate script to define these - // values as constants using pkg-config instead. + // FIXME: Use idgenerator to define them all. + // Some are not yet available in noble though. // Human users (homed) (nss-systemd). nssSystemdHomedMin uint32 = 60001 @@ -70,8 +70,8 @@ const ( systemdContainersUsersMax uint32 = 60577 // Dynamic service users (nss-systemd). - nssSystemdDynamicServiceUsersMin uint32 = 61184 - nssSystemdDynamicServiceUsersMax uint32 = 65519 + nssSystemdDynamicServiceUsersMin = SystemdDynamicUidMin + nssSystemdDynamicServiceUsersMax = SystemdDynamicUidMax // Container UID ranges (nss-systemd). // According to https://systemd.io/UIDS-GIDS/, systemd-nspawn will check NSS @@ -79,8 +79,8 @@ const ( // safe for us to use. However, it also says that, for performance reasons, // it will only check for the first UID of the range it allocates, so we do // need to avoid using the whole range. - nssSystemdContainerMin uint32 = 524288 - nssSystemdContainerMax uint32 = 1879048191 + nssSystemdContainerMin = SystemdContainerUidBaseMin + nssSystemdContainerMax = SystemdContainerUidBaseMax ) // GenerateUID generates a random UID in the configured range. diff --git a/internal/users/idlimits.go b/internal/users/idlimits.go new file mode 100644 index 0000000000..7a615cb4f0 --- /dev/null +++ b/internal/users/idlimits.go @@ -0,0 +1,20 @@ +// Code generated by "idlimitsgenerator"; DO NOT EDIT. +package users + +// SystemdSystemUidMax is the Systemd configured Value for system_uid_max. +const SystemdSystemUidMax uint32 = 999 + +// SystemdSystemGidMax is the Systemd configured Value for system_gid_max. +const SystemdSystemGidMax uint32 = 999 + +// SystemdDynamicUidMin is the Systemd configured Value for dynamic_uid_min. +const SystemdDynamicUidMin uint32 = 61184 + +// SystemdDynamicUidMax is the Systemd configured Value for dynamic_uid_max. +const SystemdDynamicUidMax uint32 = 65519 + +// SystemdContainerUidBaseMin is the Systemd configured Value for container_uid_base_min. +const SystemdContainerUidBaseMin uint32 = 524288 + +// SystemdContainerUidBaseMax is the Systemd configured Value for container_uid_base_max. +const SystemdContainerUidBaseMax uint32 = 1878982656 diff --git a/internal/users/idlimitsgenerator/idlimitsgenerator.go b/internal/users/idlimitsgenerator/idlimitsgenerator.go new file mode 100644 index 0000000000..34d93ab7b4 --- /dev/null +++ b/internal/users/idlimitsgenerator/idlimitsgenerator.go @@ -0,0 +1,86 @@ +//go:build generate + +// TiCS: disabled // This is a helper file to generate the IDs from pkg-config. + +// Package main is the package for the ID limits generator. +package main + +import ( + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" +) + +const outputFile = "idlimits.go" + +func toPascalCase(snakeCaseStr string) string { + s := strings.Split(snakeCaseStr, "_") + for index := range s { + s[index] = strings.Title(strings.ToLower(s[index])) + } + return strings.Join(s, "") +} + +func getPkgConfigVariable(library, variable string) (string, error) { + cmd := exec.Command("pkg-config", library, fmt.Sprintf("--variable=%s", variable)) + out, err := cmd.CombinedOutput() + return strings.TrimSpace(string(out)), err +} + +func getSystemdLimitValue(variable string) (uint32, error) { + val, err := getPkgConfigVariable("systemd", variable) + if err != nil { + return 0, err + } + + uintVal, err := strconv.ParseUint(val, 10, 32) + if err != nil { + return 0, err + } + return uint32(uintVal), nil +} + +func main() { + idVariables := []string{ + "system_uid_max", + "system_gid_max", + "dynamic_uid_min", + "dynamic_uid_max", + "container_uid_base_min", + "container_uid_base_max", + } + + currentProgram, err := os.Executable() + if err != nil { + log.Fatal(err) + } + + generatedCode := []string{ + fmt.Sprintf("// Code generated by %q; DO NOT EDIT.", filepath.Base(currentProgram)), + "package users", + "", + } + + for _, v := range idVariables { + id, err := getSystemdLimitValue(v) + if err != nil { + log.Fatalf("Failed getting value for %q systemd variable: %v", v, err) + } + + varName := "Systemd" + toPascalCase(v) + generatedCode = append(generatedCode, + fmt.Sprintf("// %s is the Systemd configured Value for %s.", varName, v), + fmt.Sprintf("const %s uint32 = %d", varName, id), + "", + ) + } + + err = os.WriteFile(outputFile, []byte(strings.Join(generatedCode, "\n")), 0600) + if err != nil { + log.Fatalf("Failed to write to %s: %v", outputFile, err) + } +} diff --git a/internal/users/manager.go b/internal/users/manager.go index ca93a8e64b..a9d0e65bcd 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -31,8 +31,8 @@ type Config struct { const ( // minAllowedUID is the minimum value used to generate the user and group IDs. // See https://systemd.io/UIDS-GIDS/ for reference. - minAllowedUID uint32 = 1000 - minAllowedGID uint32 = 1000 + minAllowedUID = SystemdSystemUidMax + 1 + minAllowedGID = SystemdSystemGidMax + 1 maxSuggestedID uint32 = math.MaxInt32 ) From efb7f8768b0ab7b887e5891210fb00f818e0e7a8 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 8 Jul 2025 12:57:35 +0200 Subject: [PATCH 0596/1670] Check reserved ID ranges when authd is started Instead of checking during runtime if any generated IDs are reserved, we now check if the configured UID/GID ranges overlap with any reserved ID ranges when authd is started. This has the benefit that admins can be sure that, if authd starts successfully, all IDs of the configured ID ranges can actually be used by authd. It also avoids the case that an ID range can be configured which doesn't contain any valid IDs, which would previously only be noticed when a new user successfully authenticates. --- internal/users/export_test.go | 11 +- internal/users/idgenerator.go | 168 ++++-------------- internal/users/idgenerator_test.go | 140 ++------------- internal/users/idlimits.go | 22 +-- .../idlimitsgenerator/idlimitsgenerator.go | 76 ++++---- internal/users/manager.go | 47 ++--- internal/users/manager_test.go | 19 +- ...h_GID_range_next_to_systemd_dynamic_groups | 64 +++++++ ...th_UID_range_next_to_systemd_dynamic_users | 64 +++++++ 9 files changed, 246 insertions(+), 365 deletions(-) create mode 100644 internal/users/testdata/golden/TestNewManager/Successfully_create_manager_with_GID_range_next_to_systemd_dynamic_groups create mode 100644 internal/users/testdata/golden/TestNewManager/Successfully_create_manager_with_UID_range_next_to_systemd_dynamic_users diff --git a/internal/users/export_test.go b/internal/users/export_test.go index f68b301339..fcfab87042 100644 --- a/internal/users/export_test.go +++ b/internal/users/export_test.go @@ -5,12 +5,6 @@ import ( "github.com/ubuntu/authd/internal/users/types" ) -const ( - MinAllowedUID = minAllowedUID - MinAllowedGID = minAllowedGID - MaxSuggestedID = maxSuggestedID -) - func (m *Manager) DB() *db.Manager { return m.db } @@ -27,3 +21,8 @@ func (m *Manager) GetOldUserInfoFromDB(name string) (oldUserInfo *types.UserInfo func CompareNewUserInfoWithUserInfoFromDB(newUserInfo, dbUserInfo types.UserInfo) bool { return compareNewUserInfoWithUserInfoFromDB(newUserInfo, dbUserInfo) } + +const ( + SystemdDynamicUIDMin = systemdDynamicUIDMin + SystemdDynamicUIDMax = systemdDynamicUIDMax +) diff --git a/internal/users/idgenerator.go b/internal/users/idgenerator.go index 84577ded65..1585459b5b 100644 --- a/internal/users/idgenerator.go +++ b/internal/users/idgenerator.go @@ -48,7 +48,7 @@ type IDGenerator struct { // from other NSS sources when determining which candidates to exclude. const maxIDGenerateIterations = 1000 -// Reserved IDs. +// Special Linux UIDs, see https://systemd.io/UIDS-GIDS/ const ( rootID uint32 = 0 nobodyID uint32 = 65534 @@ -56,33 +56,6 @@ const ( uidT16MinusOne uint32 = math.MaxUint16 ) -// Systemd used ranges. -const ( - // FIXME: Use idgenerator to define them all. - // Some are not yet available in noble though. - - // Human users (homed) (nss-systemd). - nssSystemdHomedMin uint32 = 60001 - nssSystemdHomedMax uint32 = 60513 - - // Host users mapped into containers (systemd-nspawn). - systemdContainersUsersMin uint32 = 60514 - systemdContainersUsersMax uint32 = 60577 - - // Dynamic service users (nss-systemd). - nssSystemdDynamicServiceUsersMin = SystemdDynamicUidMin - nssSystemdDynamicServiceUsersMax = SystemdDynamicUidMax - - // Container UID ranges (nss-systemd). - // According to https://systemd.io/UIDS-GIDS/, systemd-nspawn will check NSS - // for collisions before allocating a UID in this range, which would make it - // safe for us to use. However, it also says that, for performance reasons, - // it will only check for the first UID of the range it allocates, so we do - // need to avoid using the whole range. - nssSystemdContainerMin = SystemdContainerUidBaseMin - nssSystemdContainerMax = SystemdContainerUidBaseMax -) - // GenerateUID generates a random UID in the configured range. func (g *IDGenerator) GenerateUID(lockedEntries *localentries.UserDBLocked, owner IDOwner) (uint32, func(), error) { return g.generateID(lockedEntries, owner, generateID{ @@ -118,11 +91,6 @@ func (g *IDGenerator) generateID(lockedEntries *localentries.UserDBLocked, owner return 0, nil, errors.New("minID must be less than or equal to maxID") } - args.minID = adjustIDForSafeRanges(args.minID) - if args.minID > args.maxID { - return 0, nil, errors.New("no usable ID in range") - } - usedIDs, err := args.getUsedIDs(lockedEntries, owner) if err != nil { return 0, nil, err @@ -141,13 +109,6 @@ func (g *IDGenerator) generateID(lockedEntries *localentries.UserDBLocked, owner return 0, nil, err } - if !isReservedID(id) { - // Keep track of the id, but preserving usedIDs sorted, since we - // use the binary search to add elements to it. - usedIDs = slices.Insert(usedIDs, pos, id) - continue - } - available, err := args.isAvailableID(lockedEntries, id) if err != nil { return 0, nil, err @@ -163,12 +124,6 @@ func (g *IDGenerator) generateID(lockedEntries *localentries.UserDBLocked, owner continue } - if id > maxSuggestedID { - log.Warningf(context.Background(), - "Generated ID %d is outside the signed 32-bit range, "+ - "it may not work as expected", id) - } - g.pendingIDs = append(g.pendingIDs, id) cleanup = func() { idx := slices.Index(g.pendingIDs, id) @@ -181,80 +136,6 @@ func (g *IDGenerator) generateID(lockedEntries *localentries.UserDBLocked, owner args.idType, maxAttempts) } -// isReservedID checks if the ID is a value is not a linux system reserved value. -// Note that we are not listing here the system IDs (1…999), as this the job -// for the [Manager], being a wrong configuration. -// See: https://systemd.io/UIDS-GIDS/ -func isReservedID(id uint32) bool { - switch id { - case rootID: - // The root super-user. - log.Warningf(context.Background(), - "ID %d cannot be used: it is the root super-user.", id) - return false - - case nobodyID: - // Nobody user. - log.Warningf(context.Background(), - "ID %d cannot be used: it is nobody user.", id) - return false - - case uidT32MinusOne: - // uid_t-1 (32): Special non-valid ID for `setresuid` and `chown`. - log.Warningf(context.Background(), - "ID %d cannot be used: it is uid_t-1 (32bit).") - return false - - case uidT16MinusOne: - // uid_t-1 (16): As before, but comes from legacy 16bit programs. - log.Warningf(context.Background(), - "ID %d cannot be used: it is uid_t-1 (16bit)", id) - return false - - default: - return true - } -} - -// adjustIDForSafeRanges verifies if the ID value can be safely used, by -// checking if it's part of any of the well known the ID ranges that can't be -// used, as per being part of the linux (systemd) reserved ranges. -// See (again) https://systemd.io/UIDS-GIDS/ -func adjustIDForSafeRanges(id uint32) (adjustedID uint32) { - initialID := id - defer func() { - if adjustedID == initialID { - return - } - - log.Noticef(context.Background(), - "ID %d is within a range used by systemd and cannot be used; "+ - "skipping to the next available ID (%d)", initialID, adjustedID) - }() - - // Human users (homed) (nss-systemd) - adjacent to containers users! - if id >= nssSystemdHomedMin && id <= nssSystemdHomedMax { - id = nssSystemdHomedMax + 1 - } - - // Host users mapped into containers (systemd-nspawn) - adjacent to homed! - if id >= systemdContainersUsersMin && id <= systemdContainersUsersMax { - id = systemdContainersUsersMax + 1 - } - - // Dynamic service users (nss-systemd) - if id >= nssSystemdDynamicServiceUsersMin && id <= nssSystemdDynamicServiceUsersMax { - id = nssSystemdDynamicServiceUsersMax + 1 - } - - // Container UID ranges (nss-systemd) - if id >= nssSystemdContainerMin && id <= nssSystemdContainerMax { - id = nssSystemdContainerMax + 1 - } - - return id -} - func getIDCandidate(minID, maxID uint32, usedIDs []uint32) (id uint32, uniqueIDsPos int, err error) { // Pick the preferred ID candidate, starting with minID. preferredID := minID @@ -270,37 +151,52 @@ func getIDCandidate(minID, maxID uint32, usedIDs []uint32) (id uint32, uniqueIDs } // Try IDs starting from the preferred ID up to the maximum ID. - for id := preferredID; ; id++ { - // Sanitize the ID to ensure that it's not in a restricted range. - id = adjustIDForSafeRanges(id) - - if id > maxID { - break + // Overflows are avoided by the "id < math.MaxUint32" condition. + for id := preferredID; id <= maxID && id < math.MaxUint32; id++ { + if isReservedID(id) { + continue } - if pos, found := slices.BinarySearch(usedIDs, id); !found { - return id, pos, nil + pos, found := slices.BinarySearch(usedIDs, id) + if found { + continue } - if id == math.MaxUint32 { - break // Avoid overflow - } + return id, pos, nil } // Fallback: try IDs from the minimum ID up to the preferred ID. + // Overflows are avoided by "id < preferredID" condition, because + // preferredID is a uint32, so the condition must be false when + // id == math.MaxUint32. for id := minID; id < preferredID && id <= maxID; id++ { - if pos, found := slices.BinarySearch(usedIDs, id); !found { - return id, pos, nil + if isReservedID(id) { + continue } - // Overflows are avoided by the loop condition (id < preferredID, where - // preferredID is a uint32, so the condition must be false when - // id == math.MaxUint32). + pos, found := slices.BinarySearch(usedIDs, id) + if found { + continue + } + + return id, pos, nil } return 0, -1, errors.New("no available ID in range") } +func isReservedID(id uint32) bool { + switch id { + case rootID, + nobodyID, + uidT32MinusOne, + uidT16MinusOne: + return true + default: + return false + } +} + func (g *IDGenerator) isUIDAvailable(lockedEntries *localentries.UserDBLocked, uid uint32) (bool, error) { if unique, err := lockedEntries.IsUniqueUID(uid); !unique || err != nil { return false, err diff --git a/internal/users/idgenerator_test.go b/internal/users/idgenerator_test.go index cc1bd26544..a376b0db86 100644 --- a/internal/users/idgenerator_test.go +++ b/internal/users/idgenerator_test.go @@ -99,6 +99,11 @@ func TestGetIDCandidate(t *testing.T) { used: []uint32{math.MaxUint32 - 2, math.MaxUint32 - 1, math.MaxUint32}, wantErr: true, }, + "Error_if_only_MaxUint32_is_available": { + idMin: math.MaxUint32, + idMax: math.MaxUint32, + wantErr: true, + }, } for name, tc := range tests { @@ -257,93 +262,15 @@ func TestGenerateIDMocked(t *testing.T) { owner: IDOwnerMock{usedUIDs: []uint32{uidT32MinusOne}}, wantID: uidT32MinusOne - 2, }, - "IDs_in_systemd_homed_and_systemd_containers_range_are_skipped": { - genID: generateID{ - idType: "UID", - minID: nssSystemdHomedMin, maxID: systemdContainersUsersMax + 86, - isAvailableID: allAvailableIDsFunc, - getUsedIDs: noUsedIDFunc, - }, - wantID: systemdContainersUsersMax + 1, - }, - "IDs_in_systemd_homed_and_systemd_containers_range_are_skipped_when_next_to_preferred_id": { - genID: generateID{ - idType: "UID", - minID: nssSystemdHomedMin - 10, maxID: systemdContainersUsersMax + 10, - isAvailableID: allAvailableIDsFunc, - getUsedIDs: func(ud *localentries.UserDBLocked, i IDOwner) ([]uint32, error) { - return []uint32{nssSystemdHomedMin - 1}, nil - }, - }, - wantID: systemdContainersUsersMax + 1, - }, - "IDs_in_systemd_homed_and_systemd_containers_range_are_skipped_when_next_to_preferred_id_minus_one": { - genID: generateID{ - idType: "UID", - minID: nssSystemdHomedMin - 10, maxID: systemdContainersUsersMax + 10, - isAvailableID: allAvailableIDsFunc, - getUsedIDs: func(ud *localentries.UserDBLocked, i IDOwner) ([]uint32, error) { - return []uint32{nssSystemdHomedMin - 1}, nil - }, - }, - wantID: systemdContainersUsersMax + 1, - }, - "IDs_in_systemd_homed_and_systemd_containers_range_are_not_skipped_when_next_to_min_id": { + "MaxUint32_is_always_skipped": { genID: generateID{ idType: "UID", - minID: nssSystemdHomedMin - 1, maxID: systemdContainersUsersMax + 10, + minID: math.MaxUint32 - 2, maxID: math.MaxUint32, isAvailableID: allAvailableIDsFunc, - getUsedIDs: noUsedIDFunc, - }, - wantID: nssSystemdHomedMin - 1, - }, - "IDs_in_systemd_homed_and_systemd_containers_range_are_not_skipped_when_next_to_min_id_minus_one": { - genID: generateID{ - idType: "UID", - minID: nssSystemdHomedMin - 2, maxID: systemdContainersUsersMax + 10, - isAvailableID: allAvailableIDsFunc, - getUsedIDs: noUsedIDFunc, - }, - wantID: nssSystemdHomedMin - 2, - }, - "IDs_in_systemd_dynamic_service_users_range_are_skipped": { - genID: generateID{ - idType: "UID", - minID: nssSystemdDynamicServiceUsersMin - 55, maxID: nssSystemdDynamicServiceUsersMax + 1, - isAvailableID: func(ud *localentries.UserDBLocked, u uint32) (bool, error) { - return u != nssSystemdDynamicServiceUsersMax+1, nil - }, - getUsedIDs: getOwnerUsedUIDsFunc, - }, - owner: IDOwnerMock{usedUIDs: []uint32{nssSystemdDynamicServiceUsersMin + 1}}, - wantID: nssSystemdDynamicServiceUsersMin - 55, - }, - "IDs_in_systemd_containers_and_dynamic_service_user_range_are_skipped": { - genID: generateID{ - idType: "UID", - minID: systemdContainersUsersMin, maxID: nssSystemdDynamicServiceUsersMax, - isAvailableID: func(ud *localentries.UserDBLocked, u uint32) (bool, error) { - return u != systemdContainersUsersMax+2, nil - }, - getUsedIDs: getOwnerUsedUIDsFunc, - }, - owner: IDOwnerMock{usedUIDs: []uint32{ - nssSystemdDynamicServiceUsersMax - 10, - systemdContainersUsersMax + 1, - }}, - wantID: systemdContainersUsersMax + 3, - }, - "IDs_in_systemd_container_range_are_skipped": { - genID: generateID{ - idType: "UID", - minID: nssSystemdContainerMin - 8, maxID: nssSystemdContainerMax, - isAvailableID: func(ud *localentries.UserDBLocked, u uint32) (bool, error) { - return u != nssSystemdContainerMin-8, nil - }, - getUsedIDs: getOwnerUsedUIDsFunc, + getUsedIDs: getOwnerUsedUIDsFunc, }, - owner: IDOwnerMock{usedUIDs: []uint32{(nssSystemdContainerMin + nssSystemdContainerMax) / 2}}, - wantID: nssSystemdContainerMin - 7, + owner: IDOwnerMock{usedUIDs: []uint32{math.MaxUint32 - 1}}, + wantID: math.MaxUint32 - 2, }, // Error cases @@ -426,53 +353,6 @@ func TestGenerateIDMocked(t *testing.T) { owner: IDOwnerMock{usedUIDs: []uint32{10, 11, 12}}, wantErr: true, }, - "Errors_if_IDs_only_in_systemd_homed_range": { - genID: generateID{ - idType: "UID", - minID: nssSystemdHomedMin, maxID: nssSystemdHomedMax, - isAvailableID: allAvailableIDsFunc, - getUsedIDs: noUsedIDFunc, - }, - wantErr: true, - }, - "Errors_if_IDs_only_in_systemd_containers_range": { - genID: generateID{ - idType: "UID", - minID: systemdContainersUsersMin, maxID: systemdContainersUsersMax, - isAvailableID: allAvailableIDsFunc, - getUsedIDs: noUsedIDFunc, - }, - wantErr: true, - }, - "Errors_if_IDs_only_in_systemd_dynamic_service_user_range": { - genID: generateID{ - idType: "UID", - minID: nssSystemdDynamicServiceUsersMin, maxID: nssSystemdDynamicServiceUsersMax, - isAvailableID: allAvailableIDsFunc, - getUsedIDs: noUsedIDFunc, - }, - wantErr: true, - }, - "Errors_if_IDs_only_in_systemd_containers_and_dynamic_service_user_range_with_other_used_IDs": { - genID: generateID{ - idType: "UID", - minID: systemdContainersUsersMin, maxID: nssSystemdDynamicServiceUsersMax, - isAvailableID: func(ud *localentries.UserDBLocked, u uint32) (bool, error) { - return u <= systemdContainersUsersMax && u >= nssSystemdDynamicServiceUsersMin, nil - }, - getUsedIDs: noUsedIDFunc, - }, - wantErr: true, - }, - "Errors_if_IDs_only_in_container_nss-systemd_range": { - genID: generateID{ - idType: "UID", - minID: nssSystemdContainerMin, maxID: nssSystemdContainerMax, - isAvailableID: allAvailableIDsFunc, - getUsedIDs: noUsedIDFunc, - }, - wantErr: true, - }, } for name, tc := range tests { diff --git a/internal/users/idlimits.go b/internal/users/idlimits.go index 7a615cb4f0..386539fb01 100644 --- a/internal/users/idlimits.go +++ b/internal/users/idlimits.go @@ -1,20 +1,8 @@ // Code generated by "idlimitsgenerator"; DO NOT EDIT. package users -// SystemdSystemUidMax is the Systemd configured Value for system_uid_max. -const SystemdSystemUidMax uint32 = 999 - -// SystemdSystemGidMax is the Systemd configured Value for system_gid_max. -const SystemdSystemGidMax uint32 = 999 - -// SystemdDynamicUidMin is the Systemd configured Value for dynamic_uid_min. -const SystemdDynamicUidMin uint32 = 61184 - -// SystemdDynamicUidMax is the Systemd configured Value for dynamic_uid_max. -const SystemdDynamicUidMax uint32 = 65519 - -// SystemdContainerUidBaseMin is the Systemd configured Value for container_uid_base_min. -const SystemdContainerUidBaseMin uint32 = 524288 - -// SystemdContainerUidBaseMax is the Systemd configured Value for container_uid_base_max. -const SystemdContainerUidBaseMax uint32 = 1878982656 +const ( + // dynamic service users (nss-systemd). + systemdDynamicUIDMin uint32 = 61184 + systemdDynamicUIDMax uint32 = 65519 +) diff --git a/internal/users/idlimitsgenerator/idlimitsgenerator.go b/internal/users/idlimitsgenerator/idlimitsgenerator.go index 34d93ab7b4..64cd23d9d3 100644 --- a/internal/users/idlimitsgenerator/idlimitsgenerator.go +++ b/internal/users/idlimitsgenerator/idlimitsgenerator.go @@ -6,6 +6,7 @@ package main import ( + "bytes" "fmt" "log" "os" @@ -13,16 +14,27 @@ import ( "path/filepath" "strconv" "strings" + "text/template" ) -const outputFile = "idlimits.go" +const ( + outputFile = "idlimits.go" +) -func toPascalCase(snakeCaseStr string) string { - s := strings.Split(snakeCaseStr, "_") - for index := range s { - s[index] = strings.Title(strings.ToLower(s[index])) - } - return strings.Join(s, "") +const tmpl = `// Code generated by "{{ .GeneratorName }}"; DO NOT EDIT. +package users + +const ( + // dynamic service users (nss-systemd). + systemdDynamicUIDMin uint32 = {{ .DynamicUidMin }} + systemdDynamicUIDMax uint32 = {{ .DynamicUidMax }} +) +` + +type variables struct { + GeneratorName string + DynamicUidMin uint32 + DynamicUidMax uint32 } func getPkgConfigVariable(library, variable string) (string, error) { @@ -31,56 +43,42 @@ func getPkgConfigVariable(library, variable string) (string, error) { return strings.TrimSpace(string(out)), err } -func getSystemdLimitValue(variable string) (uint32, error) { +func systemdLimitValue(variable string) uint32 { val, err := getPkgConfigVariable("systemd", variable) if err != nil { - return 0, err + log.Fatalf("Error getting pkg-config variable %q: %v", variable, err) } uintVal, err := strconv.ParseUint(val, 10, 32) if err != nil { - return 0, err + log.Fatalf("Error parsing %q value %q to uint32: %v", variable, val, err) } - return uint32(uintVal), nil + return uint32(uintVal) } func main() { - idVariables := []string{ - "system_uid_max", - "system_gid_max", - "dynamic_uid_min", - "dynamic_uid_max", - "container_uid_base_min", - "container_uid_base_max", - } - - currentProgram, err := os.Executable() + generatorName, err := os.Executable() if err != nil { log.Fatal(err) } - generatedCode := []string{ - fmt.Sprintf("// Code generated by %q; DO NOT EDIT.", filepath.Base(currentProgram)), - "package users", - "", + vars := variables{ + GeneratorName: filepath.Base(generatorName), + DynamicUidMin: systemdLimitValue("dynamic_uid_min"), + DynamicUidMax: systemdLimitValue("dynamic_uid_max"), } - for _, v := range idVariables { - id, err := getSystemdLimitValue(v) - if err != nil { - log.Fatalf("Failed getting value for %q systemd variable: %v", v, err) - } + tmpl, err := template.New("idlimits").Parse(tmpl) + if err != nil { + log.Fatalf("Failed to parse template: %v", err) + } - varName := "Systemd" + toPascalCase(v) - generatedCode = append(generatedCode, - fmt.Sprintf("// %s is the Systemd configured Value for %s.", varName, v), - fmt.Sprintf("const %s uint32 = %d", varName, id), - "", - ) + var buf bytes.Buffer + if err := tmpl.Execute(&buf, vars); err != nil { + log.Fatalf("Failed to execute template: %v", err) } - err = os.WriteFile(outputFile, []byte(strings.Join(generatedCode, "\n")), 0600) - if err != nil { - log.Fatalf("Failed to write to %s: %v", outputFile, err) + if err := os.WriteFile(outputFile, buf.Bytes(), 0600); err != nil { + log.Fatalf("Failed to write output file: %v", err) } } diff --git a/internal/users/manager.go b/internal/users/manager.go index a9d0e65bcd..bb134bf85d 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -28,15 +28,6 @@ type Config struct { GIDMax uint32 `mapstructure:"gid_max" yaml:"gid_max"` } -const ( - // minAllowedUID is the minimum value used to generate the user and group IDs. - // See https://systemd.io/UIDS-GIDS/ for reference. - minAllowedUID = SystemdSystemUidMax + 1 - minAllowedGID = SystemdSystemGidMax + 1 - - maxSuggestedID uint32 = math.MaxInt32 -) - // DefaultConfig is the default configuration for the user manager. var DefaultConfig = Config{ UIDMin: 1000000000, @@ -85,32 +76,30 @@ func NewManager(config Config, dbDir string, args ...Option) (m *Manager, err er if opts.idGenerator == nil { // Check that the ID ranges are valid. if config.UIDMin >= config.UIDMax { - return nil, errors.New("UID_MIN must be less than UID_MAX") + return nil, fmt.Errorf("UID_MIN (%d) must be less than UID_MAX (%d)", config.UIDMin, config.UIDMax) } if config.GIDMin >= config.GIDMax { - return nil, errors.New("GID_MIN must be less than GID_MAX") + return nil, fmt.Errorf("GID_MIN (%d) must be less than GID_MAX (%d)", config.GIDMin, config.GIDMax) } - - // See https://systemd.io/UIDS-GIDS/ for reference. - if config.UIDMin < minAllowedUID { - log.Warning(context.Background(), "authd is configured to potentially use "+ - "system users IDs (as per the default SYS_UID_MIN and SYS_UID_MAX values). "+ - "This may not be safe.") + // UIDs/GIDs larger than a signed int32 are known to cause issues in various programs, + // so they should be avoided (see https://systemd.io/UIDS-GIDS/) + if config.UIDMax > math.MaxInt32 { + return nil, fmt.Errorf("UID_MAX (%d) must be less than or equal to %d", config.UIDMax, math.MaxInt32) } - if config.GIDMin < minAllowedGID { - log.Warning(context.Background(), "authd is configured to potentially use "+ - "system group IDs (as per the default SYS_GID_MIN and SYS_GID_MAX values). "+ - "This may not be safe.") + if config.GIDMax > math.MaxInt32 { + return nil, fmt.Errorf("GID_MAX (%d) must be less than or equal to %d", config.GIDMax, math.MaxInt32) } - if config.UIDMax > maxSuggestedID { - log.Warningf(context.Background(), "authd is configured to use maximum user ID values "+ - "outside the signed 32-bit range, this is not safe and may lead some programs no to work "+ - "as expected. Adjust UID_MAX to a value minor or equal than %d", maxSuggestedID) + + // Check that the ID ranges are not overlapping with systemd dynamic service users. + rangesOverlap := func(min1, max1, min2, max2 uint32) bool { + return (min1 <= max2 && max1 >= min2) || (min2 <= max1 && max2 >= min1) + } + + if rangesOverlap(config.UIDMin, config.UIDMax, systemdDynamicUIDMin, systemdDynamicUIDMax) { + return nil, fmt.Errorf("UID range (%d-%d) overlaps with systemd dynamic service users range (%d-%d)", config.UIDMin, config.UIDMax, systemdDynamicUIDMin, systemdDynamicUIDMax) } - if config.GIDMax > maxSuggestedID { - log.Warningf(context.Background(), "authd is configured to use maximum groups ID values "+ - "outside the signed 32-bit range, this is not safe and may lead some programs no to work "+ - "as expected. Adjust GID_MAX to a value minor or equal than %d", maxSuggestedID) + if rangesOverlap(config.GIDMin, config.GIDMax, systemdDynamicUIDMin, systemdDynamicUIDMax) { + return nil, fmt.Errorf("GID range (%d-%d) overlaps with systemd dynamic service users range (%d-%d)", config.GIDMin, config.GIDMax, systemdDynamicUIDMin, systemdDynamicUIDMax) } // Check that the number of possible UIDs is at least twice the number of possible pre-auth users. diff --git a/internal/users/manager_test.go b/internal/users/manager_test.go index 43b4a1b213..61935e9559 100644 --- a/internal/users/manager_test.go +++ b/internal/users/manager_test.go @@ -40,23 +40,26 @@ func TestNewManager(t *testing.T) { wantErr bool }{ - "Successfully_create_manager_with_default_config": {}, - "Successfully_create_manager_with_custom_config": {uidMin: 10000, uidMax: 20000, gidMin: 10000, gidMax: 20000}, + "Successfully_create_manager_with_default_config": {}, + "Successfully_create_manager_with_custom_config": {uidMin: 10000, uidMax: 20000, gidMin: 10000, gidMax: 20000}, + "Successfully_create_manager_with_UID_range_next_to_systemd_dynamic_users": {uidMin: users.SystemdDynamicUIDMax + 1, uidMax: users.SystemdDynamicUIDMax + 10000}, + "Successfully_create_manager_with_GID_range_next_to_systemd_dynamic_groups": {gidMin: users.SystemdDynamicUIDMin - 1000, gidMax: users.SystemdDynamicUIDMin - 1}, "Warns_creating_manager_with_partially_invalid_UID_ranges": {uidMin: 1, uidMax: 20000}, "Warns_creating_manager_with_partially_invalid_GID_ranges": {gidMin: 1, gidMax: 20000}, - "Warns_creating_manager_with_potentially_invalid_UID_ranges": {uidMin: 20000, uidMax: users.MaxSuggestedID + 1}, - "Warns_creating_manager_with_potentially_invalid_GID_ranges": {gidMin: 20000, gidMax: users.MaxSuggestedID + 1}, - // Corrupted databases "Error_when_database_is_corrupted": {corruptedDbFile: true, wantErr: true}, "Error_if_dbDir_does_not_exist": {dbFile: "-", wantErr: true}, // Invalid UIDs/GIDs ranges - "Error_if_UID_MIN_is_equal_to_UID_MAX": {uidMin: 1000, uidMax: 1000, wantErr: true}, - "Error_if_GID_MIN_is_equal_to_GID_MAX": {gidMin: 1000, gidMax: 1000, wantErr: true}, - "Error_if_UID_range_is_too_small": {uidMin: 1000, uidMax: 2000, wantErr: true}, + "Error_if_UID_MIN_is_equal_to_UID_MAX": {uidMin: 1000, uidMax: 1000, wantErr: true}, + "Error_if_GID_MIN_is_equal_to_GID_MAX": {gidMin: 1000, gidMax: 1000, wantErr: true}, + "Error_if_UID_range_is_too_small": {uidMin: 1000, uidMax: 2000, wantErr: true}, + "Error_if_UID_range_overlaps_with_systemd_dynamic_users": {uidMin: users.SystemdDynamicUIDMin, uidMax: users.SystemdDynamicUIDMax, wantErr: true}, + "Error_if_GID_range_overlaps_with_systemd_dynamic_groups": {gidMin: users.SystemdDynamicUIDMin, gidMax: users.SystemdDynamicUIDMax, wantErr: true}, + "Error_if_UID_range_is_larger_than_max_signed_int32": {uidMin: 0, uidMax: math.MaxInt32 + 1, wantErr: true}, + "Error_if_GID_range_is_larger_than_max_signed_int32": {gidMin: 0, gidMax: math.MaxInt32 + 1, wantErr: true}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { diff --git a/internal/users/testdata/golden/TestNewManager/Successfully_create_manager_with_GID_range_next_to_systemd_dynamic_groups b/internal/users/testdata/golden/TestNewManager/Successfully_create_manager_with_GID_range_next_to_systemd_dynamic_groups new file mode 100644 index 0000000000..5d465349a9 --- /dev/null +++ b/internal/users/testdata/golden/TestNewManager/Successfully_create_manager_with_GID_range_next_to_systemd_dynamic_groups @@ -0,0 +1,64 @@ +users: + - name: user1 + uid: 1111 + gid: 11111 + gecos: |- + User1 gecos + On multiple lines + dir: /home/user1 + shell: /bin/bash + broker_id: broker-id + - name: user2 + uid: 2222 + gid: 22222 + gecos: User2 + dir: /home/user2 + shell: /bin/dash + broker_id: broker-id + - name: user3 + uid: 3333 + gid: 33333 + gecos: User3 + dir: /home/user3 + shell: /bin/zsh + broker_id: broker-id + - name: userwithoutbroker + uid: 4444 + gid: 44444 + gecos: userwithoutbroker + dir: /home/userwithoutbroker + shell: /bin/sh +groups: + - name: group1 + gid: 11111 + ugid: "12345678" + - name: group2withoutugid + gid: 22222 + ugid: "" + - name: group3 + gid: 33333 + ugid: "34567812" + - name: group4 + gid: 44444 + ugid: "45678123" + - name: commongroup + gid: 99999 + ugid: "87654321" +users_to_groups: + - uid: 1111 + gid: 11111 + - uid: 1111 + gid: 99999 + - uid: 2222 + gid: 22222 + - uid: 2222 + gid: 99999 + - uid: 3333 + gid: 33333 + - uid: 3333 + gid: 99999 + - uid: 4444 + gid: 44444 + - uid: 4444 + gid: 99999 +schema_version: 1 diff --git a/internal/users/testdata/golden/TestNewManager/Successfully_create_manager_with_UID_range_next_to_systemd_dynamic_users b/internal/users/testdata/golden/TestNewManager/Successfully_create_manager_with_UID_range_next_to_systemd_dynamic_users new file mode 100644 index 0000000000..5d465349a9 --- /dev/null +++ b/internal/users/testdata/golden/TestNewManager/Successfully_create_manager_with_UID_range_next_to_systemd_dynamic_users @@ -0,0 +1,64 @@ +users: + - name: user1 + uid: 1111 + gid: 11111 + gecos: |- + User1 gecos + On multiple lines + dir: /home/user1 + shell: /bin/bash + broker_id: broker-id + - name: user2 + uid: 2222 + gid: 22222 + gecos: User2 + dir: /home/user2 + shell: /bin/dash + broker_id: broker-id + - name: user3 + uid: 3333 + gid: 33333 + gecos: User3 + dir: /home/user3 + shell: /bin/zsh + broker_id: broker-id + - name: userwithoutbroker + uid: 4444 + gid: 44444 + gecos: userwithoutbroker + dir: /home/userwithoutbroker + shell: /bin/sh +groups: + - name: group1 + gid: 11111 + ugid: "12345678" + - name: group2withoutugid + gid: 22222 + ugid: "" + - name: group3 + gid: 33333 + ugid: "34567812" + - name: group4 + gid: 44444 + ugid: "45678123" + - name: commongroup + gid: 99999 + ugid: "87654321" +users_to_groups: + - uid: 1111 + gid: 11111 + - uid: 1111 + gid: 99999 + - uid: 2222 + gid: 22222 + - uid: 2222 + gid: 99999 + - uid: 3333 + gid: 33333 + - uid: 3333 + gid: 99999 + - uid: 4444 + gid: 44444 + - uid: 4444 + gid: 99999 +schema_version: 1 From 314fa1026af203646fabf7e00a53ae0d351a2bec Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 9 Jul 2025 19:50:08 +0200 Subject: [PATCH 0597/1670] ci: Fix Debian package build Install missing build dependency systemd-dev --- .github/workflows/build-deb.yaml | 3 +++ debian/control | 1 + 2 files changed, 4 insertions(+) diff --git a/.github/workflows/build-deb.yaml b/.github/workflows/build-deb.yaml index 6bc07eb0c2..ad25858fa3 100644 --- a/.github/workflows/build-deb.yaml +++ b/.github/workflows/build-deb.yaml @@ -56,10 +56,13 @@ jobs: uses: canonical/desktop-engineering/gh-actions/common/build-debian@main with: docker-image: ubuntu:${{ matrix.ubuntu-version }} + # Extra build dependencies: + # - systemd-dev: Required to read compile time variables from systemd via pkg-config. extra-source-build-deps: | ca-certificates git libssl-dev + systemd-dev extra-source-build-script: | if [ "${{ matrix.ubuntu-version }}" == noble ]; then cargo install --locked --root=/usr \ diff --git a/debian/control b/debian/control index 737f869d19..dfaf8e3d0a 100644 --- a/debian/control +++ b/debian/control @@ -22,6 +22,7 @@ Build-Depends: debhelper-compat (= 13), libpwquality-dev, pkgconf, protobuf-compiler, + systemd-dev, Standards-Version: 4.6.2 XS-Go-Import-Path: github.com/ubuntu/authd XS-Vendored-Sources-Rust: adler2@2.0.0, aho-corasick@1.1.3, anyhow@1.0.98, async-trait@0.1.88, atomic-waker@1.1.2, autocfg@1.4.0, axum-core@0.5.2, axum@0.8.4, base64@0.22.1, bitflags@2.9.0, bytes@1.10.1, cc@1.2.21, cfg-if@1.0.0, chrono@0.4.41, colored@2.2.0, crc32fast@1.4.2, ctor-proc-macro@0.0.5, ctor@0.4.2, deranged@0.4.0, dtor-proc-macro@0.0.5, dtor@0.0.6, either@1.15.0, equivalent@1.0.2, errno@0.3.11, fastrand@2.3.0, fixedbitset@0.5.7, flate2@1.1.1, fnv@1.0.7, futures-channel@0.3.31, futures-core@0.3.31, futures-sink@0.3.31, futures-task@0.3.31, futures-util@0.3.31, getrandom@0.3.2, h2@0.4.10, hashbrown@0.15.3, heck@0.5.0, hex@0.4.3, hostname@0.4.1, http-body-util@0.1.3, http-body@1.0.1, http@1.3.1, httparse@1.10.1, httpdate@1.0.3, hyper-timeout@0.5.2, hyper-util@0.1.11, hyper@1.6.0, iana-time-zone@0.1.63, indexmap@2.9.0, itertools@0.14.0, itoa@1.0.15, lazy_static@1.5.0, libc@0.2.172, libnss@0.9.0, linux-raw-sys@0.4.15, linux-raw-sys@0.9.4, log@0.4.27, matchit@0.8.4, memchr@2.7.4, mime@0.3.17, miniz_oxide@0.8.8, mio@1.0.3, multimap@0.10.0, num-conv@0.1.0, num-traits@0.2.19, num_threads@0.1.7, once_cell@1.21.3, paste@1.0.15, percent-encoding@2.3.1, petgraph@0.7.1, pin-project-internal@1.1.10, pin-project-lite@0.2.16, pin-project@1.1.10, pin-utils@0.1.0, powerfmt@0.2.0, prettyplease@0.2.32, proc-macro2@1.0.95, procfs-core@0.17.0, procfs@0.17.0, prost-build@0.13.5, prost-derive@0.13.5, prost-types@0.13.5, prost@0.13.5, quote@1.0.40, regex-automata@0.4.9, regex-syntax@0.8.5, regex@1.11.1, rustix@0.38.44, rustix@1.0.7, rustversion@1.0.20, serde@1.0.219, shlex@1.3.0, simple_logger@5.0.0, slab@0.4.9, smallvec@1.15.0, socket2@0.5.9, syn@2.0.101, sync_wrapper@1.0.2, syslog@7.0.0, tempfile@3.19.1, time-core@0.1.4, time-macros@0.2.22, time@0.3.41, tokio-macros@2.5.0, tokio-stream@0.1.17, tokio-util@0.7.15, tokio@1.45.0, tonic-build@0.13.1, tonic@0.13.1, tower-layer@0.3.3, tower-service@0.3.3, tower@0.4.13, tower@0.5.2, tracing-attributes@0.1.28, tracing-core@0.1.33, tracing@0.1.41, try-lock@0.2.5, unicode-ident@1.0.18, want@0.3.1 From 9b4bb2343a11e877b056424ff82957428a93a63c Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 9 Jul 2025 20:58:46 +0200 Subject: [PATCH 0598/1670] Remove failing test MaxUint32 is a reserved ID, it's expected that we're failing if the ID range only includes that ID. --- internal/users/idgenerator_test.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/internal/users/idgenerator_test.go b/internal/users/idgenerator_test.go index a376b0db86..7f369aaa6e 100644 --- a/internal/users/idgenerator_test.go +++ b/internal/users/idgenerator_test.go @@ -61,12 +61,6 @@ func TestGetIDCandidate(t *testing.T) { wantID: 1000, wantPos: 2, }, - "Only_MaxUint32_is_available": { - idMin: math.MaxUint32, - idMax: math.MaxUint32, - wantID: math.MaxUint32, - wantPos: 0, - }, "Intermediate_value_after_MaxUint32_is_reached": { idMin: math.MaxUint32 - 2, idMax: math.MaxUint32, used: []uint32{math.MaxUint32 - 2, math.MaxUint32}, From e9a9eda64d1c650bd64dadddd77f6af8839a0941 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 10 Jul 2025 14:01:07 +0200 Subject: [PATCH 0599/1670] Fix race when getting old user and groups from db --- internal/users/db/userlocalgroups.go | 6 ++++- internal/users/db/users.go | 37 +++++++++++++++++++++++++++- internal/users/manager.go | 22 +++++------------ 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/internal/users/db/userlocalgroups.go b/internal/users/db/userlocalgroups.go index 5c65d22982..b2e09cf81c 100644 --- a/internal/users/db/userlocalgroups.go +++ b/internal/users/db/userlocalgroups.go @@ -6,7 +6,11 @@ import ( // UserLocalGroups returns all local groups for a given user or an error if the database is corrupted or no entry was found. func (m *Manager) UserLocalGroups(uid uint32) ([]string, error) { - rows, err := m.db.Query(`SELECT group_name FROM users_to_local_groups WHERE uid = ?`, uid) + return userLocalGroups(m.db, uid) +} + +func userLocalGroups(db queryable, uid uint32) ([]string, error) { + rows, err := db.Query(`SELECT group_name FROM users_to_local_groups WHERE uid = ?`, uid) if err != nil { return nil, fmt.Errorf("query error: %w", err) } diff --git a/internal/users/db/users.go b/internal/users/db/users.go index 001ac80a42..53218350c6 100644 --- a/internal/users/db/users.go +++ b/internal/users/db/users.go @@ -62,11 +62,15 @@ func userByID(db queryable, uid uint32) (UserRow, error) { // UserByName returns a user matching this name or an error if the database is corrupted or no entry was found. func (m *Manager) UserByName(name string) (UserRow, error) { + return userByName(m.db, name) +} + +func userByName(db queryable, name string) (UserRow, error) { // authd uses lowercase usernames name = strings.ToLower(name) query := fmt.Sprintf(`SELECT %s FROM users WHERE name = ?`, publicUserColumns) - row := m.db.QueryRow(query, name) + row := db.QueryRow(query, name) var u UserRow err := row.Scan(&u.Name, &u.UID, &u.GID, &u.Gecos, &u.Dir, &u.Shell, &u.BrokerID) @@ -185,3 +189,34 @@ func (m *Manager) DeleteUser(uid uint32) error { return nil } + +// UserWithGroups returns a user and their groups, including local groups, in a single transaction. +func (m *Manager) UserWithGroups(name string) (u UserRow, groups []GroupRow, localGroups []string, err error) { + // Start a transaction + tx, err := m.db.Begin() + if err != nil { + return UserRow{}, nil, nil, fmt.Errorf("failed to start transaction: %w", err) + } + + // Ensure the transaction is committed or rolled back + defer func() { + err = commitOrRollBackTransaction(err, tx) + }() + + u, err = userByName(tx, name) + if err != nil { + return UserRow{}, nil, nil, err + } + + groups, err = userGroups(tx, u.UID) + if err != nil { + return UserRow{}, nil, nil, fmt.Errorf("failed to get groups: %w", err) + } + + localGroups, err = userLocalGroups(tx, u.UID) + if err != nil { + return UserRow{}, nil, nil, fmt.Errorf("failed to get local groups: %w", err) + } + + return u, groups, localGroups, nil +} diff --git a/internal/users/manager.go b/internal/users/manager.go index bb134bf85d..496f0b1dce 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -329,26 +329,16 @@ func (m *Manager) UpdateUser(u types.UserInfo) (err error) { } func (m *Manager) getOldUserInfoFromDB(name string) (oldUserInfo *types.UserInfo, err error) { - // Check if the user already exists in the database. - u, err := m.db.UserByName(name) - if errors.Is(err, db.NoDataFoundError{}) { - return nil, nil - } - if err != nil { - return nil, fmt.Errorf("could not get user %q: %w", name, err) - } - - userGroups, err := m.db.UserGroups(u.UID) - if err != nil && !errors.Is(err, db.NoDataFoundError{}) { - return nil, fmt.Errorf("could not get users groups for %q: %w", name, err) - } - - oldLocalGroups, err := m.db.UserLocalGroups(u.UID) + oldUser, oldGroups, oldLocalGroups, err := m.db.UserWithGroups(name) if err != nil && !errors.Is(err, db.NoDataFoundError{}) { + // Unexpected error return nil, err } + if errors.Is(err, db.NoDataFoundError{}) { + return nil, nil + } - return userInfoFromUserAndGroupRows(u, userGroups, oldLocalGroups), nil + return userInfoFromUserAndGroupRows(oldUser, oldGroups, oldLocalGroups), nil } func compareNewUserInfoWithUserInfoFromDB(newUserInfo, dbUserInfo types.UserInfo) bool { From 0818a910639d56845cb42705a63986c76b12732c Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 27 Jun 2025 18:57:26 +0200 Subject: [PATCH 0600/1670] Log errors in gRPC method implementations We don't log the errors in the RedactErrorInterceptor and instead log them in the gRPC method implementations with the adequate log level: debug or info for informational messages which result from normal behavior, warning for abnormal but non-critical events, error for error conditions requiring attention. --- internal/services/pam/pam.go | 37 ++++++++++++++++++---- internal/services/user/user.go | 58 ++++++++++++++++++++++++++++++---- 2 files changed, 82 insertions(+), 13 deletions(-) diff --git a/internal/services/pam/pam.go b/internal/services/pam/pam.go index fa295e9f4d..e22bf2c1aa 100644 --- a/internal/services/pam/pam.go +++ b/internal/services/pam/pam.go @@ -76,13 +76,13 @@ func (s Service) GetPreviousBroker(ctx context.Context, req *authd.GPBRequest) ( // autoselection silently in authd. // User not in database, if there is only the local broker available, return this one without saving it. if len(s.brokerManager.AvailableBrokers()) == 1 { - log.Debugf(ctx, "User %q is not handled by authd and only local broker: select it.", req.GetUsername()) + log.Debugf(ctx, "GetPreviousBroker: User %q not found in database and only local broker available, selecting local broker", req.GetUsername()) return &authd.GPBResponse{PreviousBroker: brokers.LocalBrokerName}, nil } // User not accessible through NSS, first time login or no valid user. Anyway, no broker selected. if _, err := user.Lookup(req.GetUsername()); err != nil { - log.Debugf(ctx, "User %q is unknown", req.GetUsername()) + log.Debugf(ctx, "GetPreviousBroker: User %q not found", req.GetUsername()) return &authd.GPBResponse{}, nil } @@ -90,18 +90,18 @@ func (s Service) GetPreviousBroker(ctx context.Context, req *authd.GPBRequest) ( // service (passwd, winbind, sss…) is handling that user. brokerID = brokers.LocalBrokerName } else if err != nil { - log.Infof(ctx, "Could not get previous broker for user %q from database: %v", req.GetUsername(), err) + log.Infof(ctx, "GetPreviousBroker: Could not get broker for user %q from database: %v", req.GetUsername(), err) return &authd.GPBResponse{}, nil } // No error but the brokerID is empty (broker in database but default broker not stored yet due no successful login) if brokerID == "" { - log.Infof(ctx, "No assigned broker for user %q from database", req.GetUsername()) + log.Infof(ctx, "GetPreviousBroker: No broker set for user %q, letting the user select a new one", req.GetUsername()) return &authd.GPBResponse{}, nil } if !s.brokerManager.BrokerExists(brokerID) { - log.Warningf(ctx, "Last used broker %q is not available for user %q, letting the user select a new one", brokerID, req.GetUsername()) + log.Warningf(ctx, "GetPreviousBroker: Broker %q set for user %q does not exist, letting the user select a new one", brokerID, req.GetUsername()) return &authd.GPBResponse{}, nil } @@ -112,7 +112,7 @@ func (s Service) GetPreviousBroker(ctx context.Context, req *authd.GPBRequest) ( return &authd.GPBResponse{PreviousBroker: brokerID}, nil } if err = s.brokerManager.SetDefaultBrokerForUser(brokerID, req.GetUsername()); err != nil { - log.Warningf(ctx, "Could not set default broker %q for user %q: %v", brokerID, req.GetUsername(), err) + log.Warningf(ctx, "GetPreviousBroker: Could not cache broker %q for user %q: %v", brokerID, req.GetUsername(), err) return &authd.GPBResponse{}, nil } @@ -133,9 +133,11 @@ func (s Service) SelectBroker(ctx context.Context, req *authd.SBRequest) (resp * username = strings.ToLower(username) if username == "" { + log.Errorf(ctx, "SelectBroker: No user name provided") return nil, status.Error(codes.InvalidArgument, "no user name provided") } if brokerID == "" { + log.Errorf(ctx, "SelectBroker: No broker selected") return nil, status.Error(codes.InvalidArgument, "no broker selected") } if lang == "" { @@ -149,19 +151,21 @@ func (s Service) SelectBroker(ctx context.Context, req *authd.SBRequest) (resp * case authd.SessionMode_CHANGE_PASSWORD: mode = auth.SessionModeChangePassword default: + log.Errorf(ctx, "SelectBroker: Invalid session mode %q", req.GetMode()) return nil, status.Error(codes.InvalidArgument, "invalid session mode") } // Create a session and Memorize selected broker for it. sessionID, encryptionKey, err := s.brokerManager.NewSession(brokerID, username, lang, mode) if err != nil { + log.Errorf(ctx, "SelectBroker: Could not create session for user %q with broker %q: %v", username, brokerID, err) return nil, err } return &authd.SBResponse{ SessionId: sessionID, EncryptionKey: encryptionKey, - }, err + }, nil } // GetAuthenticationModes fetches a list of authentication modes supported by the broker depending on the session information. @@ -170,11 +174,13 @@ func (s Service) GetAuthenticationModes(ctx context.Context, req *authd.GAMReque sessionID := req.GetSessionId() if sessionID == "" { + log.Errorf(ctx, "GetAuthenticationModes: No session ID provided") return nil, status.Error(codes.InvalidArgument, "no session ID provided") } broker, err := s.brokerManager.BrokerFromSessionID(sessionID) if err != nil { + log.Errorf(ctx, "GetAuthenticationModes: Could not get broker for session %q: %v", sessionID, err) return nil, err } @@ -182,6 +188,7 @@ func (s Service) GetAuthenticationModes(ctx context.Context, req *authd.GAMReque for _, l := range req.GetSupportedUiLayouts() { layout, err := uiLayoutToMap(l) if err != nil { + log.Errorf(ctx, "GetAuthenticationModes: Invalid UI layout %v: %v", l, err) return nil, err } supportedLayouts = append(supportedLayouts, layout) @@ -189,6 +196,7 @@ func (s Service) GetAuthenticationModes(ctx context.Context, req *authd.GAMReque authenticationModes, err := broker.GetAuthenticationModes(ctx, sessionID, supportedLayouts) if err != nil { + log.Errorf(ctx, "GetAuthenticationModes: Could not get authentication modes for session %q: %v", sessionID, err) return nil, err } @@ -213,19 +221,23 @@ func (s Service) SelectAuthenticationMode(ctx context.Context, req *authd.SAMReq authenticationModeID := req.GetAuthenticationModeId() if sessionID == "" { + log.Errorf(ctx, "SelectAuthenticationMode: No session ID provided") return nil, status.Error(codes.InvalidArgument, "no session ID provided") } if authenticationModeID == "" { + log.Errorf(ctx, "SelectAuthenticationMode: No authentication mode provided") return nil, status.Error(codes.InvalidArgument, "no authentication mode provided") } broker, err := s.brokerManager.BrokerFromSessionID(sessionID) if err != nil { + log.Errorf(ctx, "SelectAuthenticationMode: Could not get broker for session %q: %v", sessionID, err) return nil, err } uiLayoutInfo, err := broker.SelectAuthenticationMode(ctx, sessionID, authenticationModeID) if err != nil { + log.Errorf(ctx, "SelectAuthenticationMode: Could not select authentication mode %q for session %q: %v", authenticationModeID, sessionID, err) return nil, err } @@ -240,21 +252,25 @@ func (s Service) IsAuthenticated(ctx context.Context, req *authd.IARequest) (res sessionID := req.GetSessionId() if sessionID == "" { + log.Errorf(ctx, "IsAuthenticated: No session ID provided") return nil, status.Error(codes.InvalidArgument, "no session ID provided") } broker, err := s.brokerManager.BrokerFromSessionID(sessionID) if err != nil { + log.Errorf(ctx, "IsAuthenticated: Could not get broker for session %q: %v", sessionID, err) return nil, err } authenticationDataJSON, err := protojson.Marshal(req.GetAuthenticationData()) if err != nil { + log.Errorf(ctx, "IsAuthenticated: Could not marshal authentication data for session %q: %v", sessionID, err) return nil, err } access, data, err := broker.IsAuthenticated(ctx, sessionID, string(authenticationDataJSON)) if err != nil { + log.Errorf(ctx, "IsAuthenticated: Could not check authentication for session %q: %v", sessionID, err) return nil, err } @@ -269,11 +285,13 @@ func (s Service) IsAuthenticated(ctx context.Context, req *authd.IARequest) (res var uInfo types.UserInfo if err := json.Unmarshal([]byte(data), &uInfo); err != nil { + log.Errorf(ctx, "IsAuthenticated: Could not unmarshal user data for session %q: %v", sessionID, err) return nil, fmt.Errorf("user data from broker invalid: %v", err) } // Update database and local groups on granted auth. if err := s.userManager.UpdateUser(uInfo); err != nil { + log.Errorf(ctx, "IsAuthenticated: Could not update user %q in database: %v", uInfo.Name, err) return nil, err } @@ -288,20 +306,24 @@ func (s Service) SetDefaultBrokerForUser(ctx context.Context, req *authd.SDBFURe defer decorate.OnError(&err, "can't set default broker %q for user %q", req.GetBrokerId(), req.GetUsername()) if req.GetUsername() == "" { + log.Errorf(ctx, "SetDefaultBrokerForUser: No user name given") return nil, status.Error(codes.InvalidArgument, "no user name given") } // Don't allow setting the default broker to the local broker, because the decision to use the local broker should // be made each time the user tries to log in, based on whether the user is provided by any other NSS service. if req.GetBrokerId() == brokers.LocalBrokerName { + log.Errorf(ctx, "SetDefaultBrokerForUser: Can't set local broker as default for user %q", req.GetUsername()) return nil, status.Error(codes.InvalidArgument, "can't set local broker as default") } if err = s.brokerManager.SetDefaultBrokerForUser(req.GetBrokerId(), req.GetUsername()); err != nil { + log.Errorf(ctx, "SetDefaultBrokerForUser: Could not set default broker %q for user %q: %v", req.GetBrokerId(), req.GetUsername(), err) return &authd.Empty{}, err } if err = s.userManager.UpdateBrokerForUser(req.GetUsername(), req.GetBrokerId()); err != nil { + log.Errorf(ctx, "SetDefaultBrokerForUser: Could not update broker for user %q in database: %v", req.GetUsername(), err) return &authd.Empty{}, err } @@ -314,6 +336,7 @@ func (s Service) EndSession(ctx context.Context, req *authd.ESRequest) (empty *a sessionID := req.GetSessionId() if sessionID == "" { + log.Errorf(ctx, "EndSession: No session ID given") return nil, status.Error(codes.InvalidArgument, "no session id given") } diff --git a/internal/services/user/user.go b/internal/services/user/user.go index b421b6cd7f..92ea66c047 100644 --- a/internal/services/user/user.go +++ b/internal/services/user/user.go @@ -42,6 +42,7 @@ func NewService(ctx context.Context, userManager *users.Manager, brokerManager * func (s Service) GetUserByName(ctx context.Context, req *authd.GetUserByNameRequest) (*authd.User, error) { name := req.GetName() if name == "" { + log.Warningf(ctx, "GetUserByName: no user name provided") return nil, status.Error(codes.InvalidArgument, "no user name provided") } @@ -50,15 +51,28 @@ func (s Service) GetUserByName(ctx context.Context, req *authd.GetUserByNameRequ return userToProtobuf(user), nil } - if !errors.Is(err, users.NoDataFoundError{}) || !req.GetShouldPreCheck() { + if !errors.Is(err, users.NoDataFoundError{}) { + log.Errorf(context.Background(), "GetUserByName: %v", err) + return nil, grpcError(err) + } + + if !req.GetShouldPreCheck() { + // The user was not found in the database and pre-check is not requested. + // It often happens that NSS requests are sent for users that are not in the authd database, so to avoid + // spamming the logs, we only log this at debug level. log.Debugf(context.Background(), "GetUserByName: %v", err) return nil, grpcError(err) } // If the user is not found in the database, we check if it exists in at least one broker. user, err = s.userPreCheck(ctx, name) + if errors.Is(err, errUserNotPermitted) { + err := fmt.Errorf("user %q is unknown and not authorized to log in via SSH for the first time by any configured broker", name) + log.Warningf(context.Background(), "GetUserByName: %v", err) + return nil, status.Error(codes.NotFound, err.Error()) + } if err != nil { - log.Debugf(context.Background(), "GetUserByName: %v", err) + log.Errorf(context.Background(), "GetUserByName: %v", err) return nil, status.Error(codes.NotFound, err.Error()) } @@ -68,11 +82,18 @@ func (s Service) GetUserByName(ctx context.Context, req *authd.GetUserByNameRequ // GetUserByID returns the user entry for the given user ID. func (s Service) GetUserByID(ctx context.Context, req *authd.GetUserByIDRequest) (*authd.User, error) { if req.GetId() == 0 { + log.Warningf(ctx, "GetUserByID: no user ID provided") return nil, status.Error(codes.InvalidArgument, "no user ID provided") } u, err := s.userManager.UserByID(req.Id) + if errors.Is(err, users.NoDataFoundError{}) { + // Only log this at debug level, see GetUserByName for details. + log.Debugf(context.Background(), "GetUserByID: %v", err) + return nil, grpcError(err) + } if err != nil { + log.Errorf(context.Background(), "GetUserByID: %v", err) return nil, grpcError(err) } @@ -83,6 +104,7 @@ func (s Service) GetUserByID(ctx context.Context, req *authd.GetUserByIDRequest) func (s Service) ListUsers(ctx context.Context, req *authd.Empty) (*authd.Users, error) { allUsers, err := s.userManager.AllUsers() if err != nil { + log.Errorf(context.Background(), "ListUsers: %v", err) return nil, grpcError(err) } @@ -97,11 +119,18 @@ func (s Service) ListUsers(ctx context.Context, req *authd.Empty) (*authd.Users, // GetGroupByName returns the group entry for the given group name. func (s Service) GetGroupByName(ctx context.Context, req *authd.GetGroupByNameRequest) (*authd.Group, error) { if req.GetName() == "" { + log.Warningf(ctx, "GetGroupByName: no group name provided") return nil, status.Error(codes.InvalidArgument, "no group name provided") } g, err := s.userManager.GroupByName(req.GetName()) + if errors.Is(err, users.NoDataFoundError{}) { + // Only log this at debug level, see GetUserByName for details + log.Debugf(context.Background(), "GetGroupByName: %v", err) + return nil, grpcError(err) + } if err != nil { + log.Errorf(context.Background(), "GetGroupByName: %v", err) return nil, grpcError(err) } @@ -111,11 +140,18 @@ func (s Service) GetGroupByName(ctx context.Context, req *authd.GetGroupByNameRe // GetGroupByID returns the group entry for the given group ID. func (s Service) GetGroupByID(ctx context.Context, req *authd.GetGroupByIDRequest) (*authd.Group, error) { if req.GetId() == 0 { + log.Warningf(ctx, "GetGroupByID: no group ID provided") return nil, status.Error(codes.InvalidArgument, "no group ID provided") } g, err := s.userManager.GroupByID(req.GetId()) + if errors.Is(err, users.NoDataFoundError{}) { + // Only log this at debug level, see GetUserByName for details + log.Debugf(context.Background(), "GetGroupByID: %v", err) + return nil, grpcError(err) + } if err != nil { + log.Errorf(context.Background(), "GetGroupByID: %v", err) return nil, grpcError(err) } @@ -126,6 +162,7 @@ func (s Service) GetGroupByID(ctx context.Context, req *authd.GetGroupByIDReques func (s Service) ListGroups(ctx context.Context, req *authd.Empty) (*authd.Groups, error) { allGroups, err := s.userManager.AllGroups() if err != nil { + log.Errorf(context.Background(), "ListGroups: %v", err) return nil, grpcError(err) } @@ -159,12 +196,16 @@ func groupToProtobuf(g types.GroupEntry) *authd.Group { } } -// userPreCheck checks if the user exists in at least one broker. +var errUserNotPermitted = errors.New("user not permitted to log in via SSH for the first time") + +// userPreCheck checks if the user is permitted to log in via SSH for the first time. +// It returns a types.UserEntry with a unique UID if the user is permitted to log in. +// If the user is not permitted to log in by any broker, errUserNotPermitted is returned. func (s Service) userPreCheck(ctx context.Context, username string) (types.UserEntry, error) { // authd uses lowercase usernames. username = strings.ToLower(username) - // Check if the user exists in at least one broker. + // Check if any broker permits the user to log in via SSH for the first time. var userinfo string var err error for _, b := range s.brokerManager.AvailableBrokers() { @@ -174,13 +215,18 @@ func (s Service) userPreCheck(ctx context.Context, username string) (types.UserE } userinfo, err = b.UserPreCheck(ctx, username) - if err == nil && userinfo != "" { + if err != nil { + log.Debugf(ctx, "UserPreCheck: %v", err) + continue + } + if userinfo != "" { break } } if err != nil || userinfo == "" { - return types.UserEntry{}, fmt.Errorf("user %q is not known by any broker", username) + // No broker permits the user to log in via SSH for the first time. + return types.UserEntry{}, errUserNotPermitted } var u types.UserEntry From 162b2e5c9abcf4918fa7899a2331d8b74f75d920 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 10 Jul 2025 17:15:36 +0200 Subject: [PATCH 0601/1670] Simplify TestAutoDetectConfig We can avoid stopping the daemon in a separate goroutine, because we have the --check-config option now. --- cmd/authd/daemon/daemon_test.go | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/cmd/authd/daemon/daemon_test.go b/cmd/authd/daemon/daemon_test.go index 4774757fe5..85045d78bf 100644 --- a/cmd/authd/daemon/daemon_test.go +++ b/cmd/authd/daemon/daemon_test.go @@ -276,10 +276,8 @@ func TestConfigLoad(t *testing.T) { } func TestAutoDetectConfig(t *testing.T) { - customizedSocketPath := filepath.Join(t.TempDir(), "mysocket") var config daemon.DaemonConfig config.Verbosity = 1 - config.Paths.Socket = customizedSocketPath configPath := daemon.GenerateTestConfig(t, &config) configNextToBinaryPath := filepath.Join(filepath.Dir(os.Args[0]), "authd.yaml") @@ -289,22 +287,11 @@ func TestAutoDetectConfig(t *testing.T) { defer os.Remove(configNextToBinaryPath) a := daemon.New() + a.SetArgs("--check-config") - wg := sync.WaitGroup{} - wg.Add(1) - go func() { - defer wg.Done() - err := a.Run() - require.NoError(t, err, "Run should exits without any error") - }() - a.WaitReady() - time.Sleep(50 * time.Millisecond) - - defer wg.Wait() - defer a.Quit() + err = a.Run() + require.NoError(t, err, "Run should not return an error") - _, err = os.Stat(customizedSocketPath) - require.NoError(t, err, "Socket should exist") require.Equal(t, 1, a.Config().Verbosity, "Verbosity is set from config") require.Equal(t, &users.DefaultConfig, a.Config().UsersConfig, "Default Users Config") } From 43995b5453e51d49a4468e8ea29adada0af438cd Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 10 Jul 2025 17:17:52 +0200 Subject: [PATCH 0602/1670] Fix TestAutoDetectConfig The test was failing on my system where I have authd installed, because it was using the /etc/authd/authd.yaml config file. --- cmd/authd/daemon/config.go | 4 ++-- cmd/authd/daemon/daemon.go | 23 +++++++++++++++++++++-- cmd/authd/daemon/daemon_test.go | 5 ++++- internal/consts/consts.go | 3 +++ 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/cmd/authd/daemon/config.go b/cmd/authd/daemon/config.go index 883bdf7001..a1cf95735c 100644 --- a/cmd/authd/daemon/config.go +++ b/cmd/authd/daemon/config.go @@ -17,7 +17,7 @@ import ( ) // initViperConfig sets verbosity level and add config env variables and file support based on name prefix. -func initViperConfig(name string, cmd *cobra.Command, vip *viper.Viper) (err error) { +func initViperConfig(name string, cmd *cobra.Command, vip *viper.Viper, configDir string) (err error) { defer decorate.OnError(&err, "can't load configuration") // Force a visit of the local flags so persistent flags for all parents are merged. @@ -37,7 +37,7 @@ func initViperConfig(name string, cmd *cobra.Command, vip *viper.Viper) (err err vip.SetConfigName(name) vip.AddConfigPath("./") vip.AddConfigPath("$HOME/") - vip.AddConfigPath("/etc/authd/") + vip.AddConfigPath(configDir) // Add the executable path to the config search path. if binPath, err := os.Executable(); err != nil { log.Warningf(context.Background(), "Failed to get current executable path, not adding it as a config dir: %v", err) diff --git a/cmd/authd/daemon/daemon.go b/cmd/authd/daemon/daemon.go index 852904f087..eb4f6b5a82 100644 --- a/cmd/authd/daemon/daemon.go +++ b/cmd/authd/daemon/daemon.go @@ -48,8 +48,27 @@ type daemonConfig struct { UsersConfig *users.Config `mapstructure:",squash" yaml:",inline"` } +type options struct { + configDir string +} + +// Option is a function that modifies configuration options for the daemon. +type Option func(*options) + +// WithConfigDir sets the configuration directory for the daemon. +func WithConfigDir(configDir string) Option { + return func(o *options) { + o.configDir = configDir + } +} + // New registers commands and return a new App. -func New() *App { +func New(args ...Option) *App { + opts := &options{configDir: consts.DefaultConfigDir} + for _, arg := range args { + arg(opts) + } + a := App{ready: make(chan struct{})} a.rootCmd = cobra.Command{ Use: fmt.Sprintf("%s COMMAND", cmdName), @@ -75,7 +94,7 @@ func New() *App { } // Install and unmarshall configuration - if err := initViperConfig(cmdName, &a.rootCmd, a.viper); err != nil { + if err := initViperConfig(cmdName, &a.rootCmd, a.viper, opts.configDir); err != nil { return err } if err := a.viper.Unmarshal(&a.config); err != nil { diff --git a/cmd/authd/daemon/daemon_test.go b/cmd/authd/daemon/daemon_test.go index 85045d78bf..7a61d1e8f0 100644 --- a/cmd/authd/daemon/daemon_test.go +++ b/cmd/authd/daemon/daemon_test.go @@ -286,7 +286,10 @@ func TestAutoDetectConfig(t *testing.T) { // Remove configuration next binary for other tests to not pick it up. defer os.Remove(configNextToBinaryPath) - a := daemon.New() + // Avoid that an existing config file in the config directory is picked up. + opt := daemon.WithConfigDir(t.TempDir()) + + a := daemon.New(opt) a.SetArgs("--check-config") err = a.Run() diff --git a/internal/consts/consts.go b/internal/consts/consts.go index 2058306f3a..1b90d9dcce 100644 --- a/internal/consts/consts.go +++ b/internal/consts/consts.go @@ -24,6 +24,9 @@ const ( // DefaultDatabaseDir is the default directory for the database. DefaultDatabaseDir = "/var/lib/authd/" + // DefaultConfigDir is the default configuration directory. + DefaultConfigDir = "/etc/authd/" + // DefaultDatabaseFileName is the default file name for the database. DefaultDatabaseFileName = "authd.sqlite3" From 59f8bfa638f6dec53348c1c43698f3f863fcc35f Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 10 Jul 2025 18:01:45 +0200 Subject: [PATCH 0603/1670] Fix NSS integration tests The test was using the authd instance running on my system, causing the tests which expect authd to not be running to fail. --- nss/integration-tests/integration_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nss/integration-tests/integration_test.go b/nss/integration-tests/integration_test.go index 57deb16feb..22a2ac62a3 100644 --- a/nss/integration-tests/integration_test.go +++ b/nss/integration-tests/integration_test.go @@ -107,7 +107,7 @@ func TestIntegration(t *testing.T) { // We don't check compatibility of arguments, have noDaemon taking precedences to the others. if tc.noDaemon { - socketPath = "" + socketPath = "/non-existent" useAlternativeDaemon = false } From d82087536b6ce9858ad0413a0ded4913acc5d04a Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Sun, 29 Jun 2025 14:03:01 +0200 Subject: [PATCH 0604/1670] UserPreCheck: Don't return error if user is not permitted Lets make it easier for the caller to differentiate between the expected error that the user is not permitted to log in and an unexpected error, by returning an empty string in the first case and an error in the latter. --- internal/broker/broker.go | 4 +++- internal/broker/broker_test.go | 18 ++++-------------- ...o_if_allowed_suffixes_has_only_empty_string | 0 ...serinfo_if_no_allowed_suffixes_are_provided | 0 ...o_if_username_does_not_match_allowed_suffix | 0 ..._does_not_match_any_of_the_allowed_suffixes | 0 6 files changed, 7 insertions(+), 15 deletions(-) create mode 100644 internal/broker/testdata/golden/TestUserPreCheck/Empty_userinfo_if_allowed_suffixes_has_only_empty_string create mode 100644 internal/broker/testdata/golden/TestUserPreCheck/Empty_userinfo_if_no_allowed_suffixes_are_provided create mode 100644 internal/broker/testdata/golden/TestUserPreCheck/Empty_userinfo_if_username_does_not_match_allowed_suffix create mode 100644 internal/broker/testdata/golden/TestUserPreCheck/Empty_userinfo_if_username_does_not_match_any_of_the_allowed_suffixes diff --git a/internal/broker/broker.go b/internal/broker/broker.go index a5a57b905a..7c1229d608 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -799,6 +799,7 @@ func (b *Broker) CancelIsAuthenticated(sessionID string) { } // UserPreCheck checks if the user is valid and can be allowed to authenticate. +// It returns the user info in JSON format if the user is valid, or an empty string if the user is not allowed. func (b *Broker) UserPreCheck(username string) (string, error) { found := false for _, suffix := range b.cfg.allowedSSHSuffixes { @@ -815,7 +816,8 @@ func (b *Broker) UserPreCheck(username string) (string, error) { } if !found { - return "", fmt.Errorf("username %q does not match any allowed suffix", username) + // The username does not match any of the allowed suffixes. + return "", nil } u := info.NewUser(username, filepath.Join(b.cfg.homeBaseDir, username), "", "", "", nil) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index f8e63d09ce..ef4b56387b 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -1192,8 +1192,6 @@ func TestUserPreCheck(t *testing.T) { username string allowedSuffixes []string homePrefix string - - wantErr bool }{ "Successfully_allow_username_with_matching_allowed_suffix": { username: "user@allowed", @@ -1220,24 +1218,20 @@ func TestUserPreCheck(t *testing.T) { homePrefix: "/home/allowed/", }, - "Error_when_username_does_not_match_allowed_suffix": { + "Empty_userinfo_if_username_does_not_match_allowed_suffix": { username: "user@notallowed", allowedSuffixes: []string{"@allowed"}, - wantErr: true, }, - "Error_when_username_does_not_match_any_of_the_allowed_suffixes": { + "Empty_userinfo_if_username_does_not_match_any_of_the_allowed_suffixes": { username: "user@notallowed", allowedSuffixes: []string{"@other", "@something", "@allowed", ""}, - wantErr: true, }, - "Error_when_no_allowed_suffixes_are_provided": { + "Empty_userinfo_if_no_allowed_suffixes_are_provided": { username: "user@allowed", - wantErr: true, }, - "Error_when_allowed_suffixes_has_only_empty_string": { + "Empty_userinfo_if_allowed_suffixes_has_only_empty_string": { username: "user@allowed", allowedSuffixes: []string{""}, - wantErr: true, }, } for name, tc := range tests { @@ -1251,10 +1245,6 @@ func TestUserPreCheck(t *testing.T) { }) got, err := b.UserPreCheck(tc.username) - if tc.wantErr { - require.Error(t, err, "UserPreCheck should have returned an error") - return - } require.NoError(t, err, "UserPreCheck should not have returned an error") golden.CheckOrUpdate(t, got) diff --git a/internal/broker/testdata/golden/TestUserPreCheck/Empty_userinfo_if_allowed_suffixes_has_only_empty_string b/internal/broker/testdata/golden/TestUserPreCheck/Empty_userinfo_if_allowed_suffixes_has_only_empty_string new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/golden/TestUserPreCheck/Empty_userinfo_if_no_allowed_suffixes_are_provided b/internal/broker/testdata/golden/TestUserPreCheck/Empty_userinfo_if_no_allowed_suffixes_are_provided new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/golden/TestUserPreCheck/Empty_userinfo_if_username_does_not_match_allowed_suffix b/internal/broker/testdata/golden/TestUserPreCheck/Empty_userinfo_if_username_does_not_match_allowed_suffix new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/broker/testdata/golden/TestUserPreCheck/Empty_userinfo_if_username_does_not_match_any_of_the_allowed_suffixes b/internal/broker/testdata/golden/TestUserPreCheck/Empty_userinfo_if_username_does_not_match_any_of_the_allowed_suffixes new file mode 100644 index 0000000000..e69de29bb2 From 214e49a7ac018326f8357bd852b8eb5d36a83be5 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Sun, 29 Jun 2025 13:58:06 +0200 Subject: [PATCH 0605/1670] UserPreCheck: Log unexpected errors at error level We now differentiate between "user not permitted to log in via SSH for the first time" and unexpected errors. The expected error is logged at debug level and the unexpected error at error level. --- internal/services/user/user.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/internal/services/user/user.go b/internal/services/user/user.go index 92ea66c047..0eb3ae9690 100644 --- a/internal/services/user/user.go +++ b/internal/services/user/user.go @@ -216,12 +216,17 @@ func (s Service) userPreCheck(ctx context.Context, username string) (types.UserE userinfo, err = b.UserPreCheck(ctx, username) if err != nil { - log.Debugf(ctx, "UserPreCheck: %v", err) + // An unexpected error occurred while checking the user. + log.Errorf(ctx, "UserPreCheck: %v", err) continue } - if userinfo != "" { - break + if userinfo == "" { + // The broker does not permit the user to log in via SSH for the first time. + // This is an expected error, so we only log it at debug level. + log.Debugf(ctx, "UserPreCheck: %v", err) + continue } + break } if err != nil || userinfo == "" { From 035c3668efd28c319452c7af7e7eca552b75cb85 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 30 Jun 2025 14:57:18 +0200 Subject: [PATCH 0606/1670] examplebroker: Make UserPreCheck not return error if user is not permitted See https://github.com/ubuntu/authd-oidc-brokers/pull/568 --- examplebroker/broker.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examplebroker/broker.go b/examplebroker/broker.go index b78a1157de..9c84b0ce8e 100644 --- a/examplebroker/broker.go +++ b/examplebroker/broker.go @@ -853,6 +853,7 @@ func (b *Broker) cancelIsAuthenticatedUnlocked(_ context.Context, sessionID stri } // UserPreCheck checks if the user is known to the broker. +// It returns the user info in JSON format if the user is valid, or an empty string if the user is not allowed. func (b *Broker) UserPreCheck(ctx context.Context, username string) (string, error) { if strings.HasPrefix(username, "user-") && strings.Contains(username, "integration") && strings.Contains(username, fmt.Sprintf("-%s-", UserIntegrationPreCheckValue)) { @@ -862,7 +863,8 @@ func (b *Broker) UserPreCheck(ctx context.Context, username string) (string, err exampleUsersMu.Lock() defer exampleUsersMu.Unlock() if _, exists := exampleUsers[username]; !exists { - return "", fmt.Errorf("user %q does not exist", username) + // The username does not match any of the allowed suffixes. + return "", nil } return userInfoFromName(username), nil } From bb28ada38ffc6e6a52905308ec0bb10f5035efda Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 4 Jul 2025 13:18:30 +0200 Subject: [PATCH 0607/1670] localentries: Remove unused functions These functions were never used. On https://github.com/ubuntu/authd/pull/993 we had a use case for them, but then decided to use `user.Lookup`/`user.LookupGroup` instead, so let's remove them. --- internal/users/localentries/getgrent_c.go | 49 ------------------ internal/users/localentries/getgrent_test.go | 31 ------------ internal/users/localentries/getpwent_c.go | 52 -------------------- internal/users/localentries/getpwent_test.go | 33 ------------- 4 files changed, 165 deletions(-) diff --git a/internal/users/localentries/getgrent_c.go b/internal/users/localentries/getgrent_c.go index ce124bb5fa..cec36f9148 100644 --- a/internal/users/localentries/getgrent_c.go +++ b/internal/users/localentries/getgrent_c.go @@ -73,55 +73,6 @@ func getGroupEntries() (entries []types.GroupEntry, err error) { } } -// ErrGroupNotFound is returned when a group is not found. -var ErrGroupNotFound = errors.New("group not found") - -// GetGroupByName returns the group with the given name. -func GetGroupByName(name string) (g types.GroupEntry, err error) { - decorate.OnError(&err, "getgrnam_r") - - var group C.struct_group - var groupPtr *C.struct_group - buf := make([]C.char, 256) - - pinner := runtime.Pinner{} - defer pinner.Unpin() - - pinner.Pin(&group) - pinner.Pin(&buf[0]) - - cName := C.CString(name) - defer C.free(unsafe.Pointer(cName)) - - for { - ret := C.getgrnam_r(cName, &group, &buf[0], C.size_t(len(buf)), &groupPtr) - errno := syscall.Errno(ret) - - if errors.Is(errno, syscall.ERANGE) { - buf = make([]C.char, len(buf)*2) - pinner.Pin(&buf[0]) - continue - } - if (errors.Is(errno, syscall.Errno(0)) && groupPtr == nil) || - errors.Is(errno, syscall.ENOENT) || - errors.Is(errno, syscall.ESRCH) || - errors.Is(errno, syscall.EBADF) || - errors.Is(errno, syscall.EPERM) { - return types.GroupEntry{}, ErrGroupNotFound - } - if !errors.Is(errno, syscall.Errno(0)) { - return types.GroupEntry{}, errno - } - - return types.GroupEntry{ - Name: C.GoString(groupPtr.gr_name), - GID: uint32(groupPtr.gr_gid), - Passwd: C.GoString(groupPtr.gr_passwd), - Users: strvToSlice(groupPtr.gr_mem), - }, nil - } -} - func strvToSlice(strv **C.char) []string { var users []string for i := C.uint(0); ; i++ { diff --git a/internal/users/localentries/getgrent_test.go b/internal/users/localentries/getgrent_test.go index f157a593e3..09f03a0f3a 100644 --- a/internal/users/localentries/getgrent_test.go +++ b/internal/users/localentries/getgrent_test.go @@ -30,34 +30,3 @@ func TestGetGroupEntries(t *testing.T) { }) } } - -func TestGetGroupByName(t *testing.T) { - t.Parallel() - - for idx := range 10 { - t.Run(fmt.Sprintf("iteration_%d", idx), func(t *testing.T) { - t.Parallel() - - got, err := GetGroupByName("root") - require.NoError(t, err, "GetGroupByName should not return an error") - require.Equal(t, got.Name, "root", "Name does not match") - require.Equal(t, got.GID, uint32(0), "GID does not match") - require.Equal(t, got.Passwd, "x", "Passwd does not match") - require.Empty(t, got.Users) - }) - } -} - -func TestGetGroupByName_NotFound(t *testing.T) { - t.Parallel() - - for idx := range 10 { - t.Run(fmt.Sprintf("iteration_%d", idx), func(t *testing.T) { - t.Parallel() - - got, err := GetGroupByName(fmt.Sprintf("nonexistent-really-%d", idx)) - require.ErrorIs(t, err, ErrGroupNotFound) - require.Empty(t, got, "Entry should be empty, but is not") - }) - } -} diff --git a/internal/users/localentries/getpwent_c.go b/internal/users/localentries/getpwent_c.go index d41665e997..b991aa0e01 100644 --- a/internal/users/localentries/getpwent_c.go +++ b/internal/users/localentries/getpwent_c.go @@ -13,7 +13,6 @@ import ( "runtime" "sync" "syscall" - "unsafe" "github.com/ubuntu/authd/internal/users/types" "github.com/ubuntu/decorate" @@ -71,54 +70,3 @@ func getUserEntries() (entries []types.UserEntry, err error) { }) } } - -// ErrUserNotFound is returned when a user is not found. -var ErrUserNotFound = errors.New("user not found") - -// GetPasswdByName returns the user with the given name. -func GetPasswdByName(name string) (p types.UserEntry, err error) { - decorate.OnError(&err, "getgrnam_r") - - var passwd C.struct_passwd - var passwdPtr *C.struct_passwd - buf := make([]C.char, 256) - - pinner := runtime.Pinner{} - defer pinner.Unpin() - - pinner.Pin(&passwd) - pinner.Pin(&buf[0]) - - cName := C.CString(name) - defer C.free(unsafe.Pointer(cName)) - - for { - ret := C.getpwnam_r(cName, &passwd, &buf[0], C.size_t(len(buf)), &passwdPtr) - errno := syscall.Errno(ret) - - if errors.Is(errno, syscall.ERANGE) { - buf = make([]C.char, len(buf)*2) - pinner.Pin(&buf[0]) - continue - } - if (errors.Is(errno, syscall.Errno(0)) && passwdPtr == nil) || - errors.Is(errno, syscall.ENOENT) || - errors.Is(errno, syscall.ESRCH) || - errors.Is(errno, syscall.EBADF) || - errors.Is(errno, syscall.EPERM) { - return types.UserEntry{}, ErrUserNotFound - } - if !errors.Is(errno, syscall.Errno(0)) { - return types.UserEntry{}, errno - } - - return types.UserEntry{ - Name: C.GoString(passwdPtr.pw_name), - UID: uint32(passwdPtr.pw_uid), - GID: uint32(passwdPtr.pw_gid), - Gecos: C.GoString(passwdPtr.pw_gecos), - Dir: C.GoString(passwdPtr.pw_dir), - Shell: C.GoString(passwdPtr.pw_shell), - }, nil - } -} diff --git a/internal/users/localentries/getpwent_test.go b/internal/users/localentries/getpwent_test.go index a147cc9069..7434c172ee 100644 --- a/internal/users/localentries/getpwent_test.go +++ b/internal/users/localentries/getpwent_test.go @@ -28,36 +28,3 @@ func TestGetPasswdEntries(t *testing.T) { }) } } - -func TestGetPasswdByName(t *testing.T) { - t.Parallel() - - for idx := range 10 { - t.Run(fmt.Sprintf("iteration_%d", idx), func(t *testing.T) { - t.Parallel() - - got, err := GetPasswdByName("root") - require.NoError(t, err, "GetPasswdByName should not return an error") - require.Equal(t, "root", got.Name, "Name does not match") - require.Equal(t, uint32(0), got.UID, "UID does not match") - require.Equal(t, uint32(0), got.GID, "GID does not match") - require.Equal(t, "root", got.Gecos, "Gecos does not match") - require.NotEmpty(t, got.Shell, "Shell is not empty") - require.Equal(t, "/root", got.Dir, "Dir does not match") - }) - } -} - -func TestGetPasswdByName_NotFound(t *testing.T) { - t.Parallel() - - for idx := range 10 { - t.Run(fmt.Sprintf("iteration_%d", idx), func(t *testing.T) { - t.Parallel() - - got, err := GetPasswdByName(fmt.Sprintf("nonexistent-really-%d", idx)) - require.ErrorIs(t, err, ErrUserNotFound) - require.Empty(t, got, "Entry should be empty, but is not") - }) - } -} From 76cfe3b3005440cce628043169acc8908e21bde0 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 11 Jul 2025 14:27:05 +0200 Subject: [PATCH 0608/1670] Change default UID/GID range to 10000:60000 Regular users (from /etc/passwd and other NSS sources) usually use the UID/GID range 1000 to 60000 (see https://systemd.io/UIDS-GIDS/#summary). To make it easier to distinguish authd users from local users, we use UIDs and GIDs above 10000. This also fixes that our old ID range (starting at 1000000000) was overlapping with the ID range used by LXD for ID mapping (also starting at 1000000000), which caused issues with LXD containers (#819). Closes #819 --- debian/authd-config/authd.yaml | 13 +++++++------ internal/users/manager.go | 8 ++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/debian/authd-config/authd.yaml b/debian/authd-config/authd.yaml index 5dc865272d..42e127ef55 100644 --- a/debian/authd-config/authd.yaml +++ b/debian/authd-config/authd.yaml @@ -11,13 +11,14 @@ ## These define the minimum and maximum UID and GID values assigned ## to users and groups by authd. ## -## Ensure that these ranges do not overlap with other ID ranges in use, -## such as those specified in /etc/subuid or /etc/subgid. +## Ensure these ranges do not overlap with other UID/GID ranges in use. +## For common ranges typically used on Linux, see: +## https://systemd.io/UIDS-GIDS/#summary ## ## Note: The user private group (the group named after the user) is always ## created with the same GID as the user’s UID, regardless of the GID_MIN ## and GID_MAX values. -#UID_MIN: 1000000000 -#UID_MAX: 1999999999 -#GID_MIN: 1000000000 -#GID_MAX: 1999999999 +#UID_MIN: 10000 +#UID_MAX: 60000 +#GID_MIN: 10000 +#GID_MAX: 60000 diff --git a/internal/users/manager.go b/internal/users/manager.go index 496f0b1dce..232691f796 100644 --- a/internal/users/manager.go +++ b/internal/users/manager.go @@ -30,10 +30,10 @@ type Config struct { // DefaultConfig is the default configuration for the user manager. var DefaultConfig = Config{ - UIDMin: 1000000000, - UIDMax: 1999999999, - GIDMin: 1000000000, - GIDMax: 1999999999, + UIDMin: 10000, + UIDMax: 60000, + GIDMin: 10000, + GIDMax: 60000, } // Manager is the manager for any user related operation. From 7ad1937fcec47c74cedd1e28e6cbb377ec591dec Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 11 Jul 2025 15:19:22 +0200 Subject: [PATCH 0609/1670] docs: Remove sections and pages on configuring ID ranges for LXD The new default ID ranges should not cause issues with LXD anymore. --- docs/howto/configure-authd.md | 7 ---- docs/howto/index.md | 1 - docs/howto/use-with-lxd.md | 60 ------------------------------- docs/reference/troubleshooting.md | 8 ----- 4 files changed, 76 deletions(-) delete mode 100644 docs/howto/use-with-lxd.md diff --git a/docs/howto/configure-authd.md b/docs/howto/configure-authd.md index 835ad81407..34020bd9be 100644 --- a/docs/howto/configure-authd.md +++ b/docs/howto/configure-authd.md @@ -314,13 +314,6 @@ The authd service is configured in `/etc/authd/authd.yaml`. This provides configuration options for logging verbosity and UID/GID ranges. -```{admonition} Additional configuration for LXD -:class: tip -If you are using authd inside LXD containers, read our short guide on [how to -use authd with LXD](howto::use-with-lxd), which outlines additional steps for -configuring appropriate UID/GID ranges. -``` - ## Configure password quality You can change authd's local password policy to ensure that users always set diff --git a/docs/howto/index.md b/docs/howto/index.md index bcd2487d90..9021e152de 100644 --- a/docs/howto/index.md +++ b/docs/howto/index.md @@ -12,7 +12,6 @@ provider. Install authd Configure authd -Use authd with LXD ``` ## Login and authentication diff --git a/docs/howto/use-with-lxd.md b/docs/howto/use-with-lxd.md deleted file mode 100644 index 97ff2cb097..0000000000 --- a/docs/howto/use-with-lxd.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -myst: - html_meta: - "description lang=en": "To use authd with LXD, you need to configure the UID and GID ranges." ---- - -(howto::use-with-lxd)= -# Use authd in LXD containers - -Running authd inside a LXD container requires the configuration of UID and GID -ranges. - -```{important} -These steps are in addition to the general installation and configuration steps -outlined in the [configuring authd guide](configure-authd.md). -``` -## Default ID map ranges - -The ID map range for LXD is: `1000000:1000000000`. - -The default range for authd exceeds those values: `1000000000:1999999999`. - -This causes errors when authenticating from LXD containers. - -## Configuration options for using authd with LXD - -Two options for configuring the ID ranges are outlined below. - -### 1. Configure ID ranges for the authd service - -Change the default ranges so that they don't exceed those from the user namespace mappings, for example: - -```{code-block} diff -:caption: /etc/authd/authd.yaml - --#UID_MIN: 1000000000 -+#UID_MIN: 100000 --#UID_MAX: 1999999999 -+#UID_MAX: 1000000000 --#GID_MIN: 1000000000 -+#GID_MIN: 100000 --#GID_MAX: 1999999999 -+#GID_MAX: 1000000000 - -``` - -### 2. Configure subordinate ID ranges on the host - -The mappings that apply to LXD containers can be found in the following files on the host: - -* `/etc/subuid` -* `/etc/subgid` - -Configure the subordinate ID range in each file to include the default authd ID range (`1000000000:1999999999`), for example: - -```{code-block} diff -:caption: /etc/subuid --:100000:65536 -+:1000000000:1999999999 -``` diff --git a/docs/reference/troubleshooting.md b/docs/reference/troubleshooting.md index cac9ff3260..7455e2a64f 100644 --- a/docs/reference/troubleshooting.md +++ b/docs/reference/troubleshooting.md @@ -342,11 +342,3 @@ mode: 5. Select `drop to root shell prompt` The user then has access to the root filesystem and can proceed with debugging. - -## Using authd in LXD containers - -If you are using authd in a LXD container, you may encounter errors during authentication. - -To fix these errors, you need to configure UID and GID ranges. - -The configuration steps are outlined in this guide on [using authd with LXD](howto::use-with-lxd). From cd10e0de3833c2babb9eb485eb0b9d14bc419af2 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 3 Jul 2025 00:45:54 +0200 Subject: [PATCH 0610/1670] snap: Run `go mod vendor` as part of the broker build I had a snap build fail because I didn't run `go mod vendor` before running `snapcraft build`. That should really be done as part of the snap build. --- snap/snapcraft.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index bb16214994..cae30e8b1c 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -46,6 +46,7 @@ parts: plugin: go override-build: | go mod download all + go mod vendor go build -o ${GOBIN}/semver semver/semver.go # Build the snap version from the git repository and current tree state. version: From f808c02e58f35b5facedfafdb87b354ee4478722 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 10:50:41 +0000 Subject: [PATCH 0611/1670] deps(go-tools): bump golang.org/x/mod from 0.25.0 to 0.26.0 in /tools Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.25.0 to 0.26.0. - [Commits](https://github.com/golang/mod/compare/v0.25.0...v0.26.0) --- updated-dependencies: - dependency-name: golang.org/x/mod dependency-version: 0.26.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- tools/go.mod | 8 ++++---- tools/go.sum | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index b2fb7adfa1..604c2ed69c 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -6,7 +6,7 @@ toolchain go1.24.1 require ( github.com/golangci/golangci-lint v1.64.8 - golang.org/x/mod v0.25.0 + golang.org/x/mod v0.26.0 ) require ( @@ -182,10 +182,10 @@ require ( go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect + golang.org/x/sync v0.15.0 // indirect + golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.22.0 // indirect - golang.org/x/tools v0.31.0 // indirect + golang.org/x/tools v0.34.0 // indirect google.golang.org/protobuf v1.36.5 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/tools/go.sum b/tools/go.sum index c5cc590ec3..5a8fca205a 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -653,8 +653,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= -golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -695,8 +695,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= 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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -718,8 +718,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -772,8 +772,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -855,8 +855,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= -golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= -golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 7bd32cd58b283db3c585e5f3086a3c9af8a5813c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 10:59:25 +0000 Subject: [PATCH 0612/1670] deps(go): bump golang.org/x/crypto from 0.39.0 to 0.40.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.39.0 to 0.40.0. - [Commits](https://github.com/golang/crypto/compare/v0.39.0...v0.40.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.40.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 0940d1c924..ffeae37564 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/ubuntu/authd v0.5.6 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 - golang.org/x/crypto v0.39.0 + golang.org/x/crypto v0.40.0 golang.org/x/oauth2 v0.30.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 @@ -64,8 +64,8 @@ require ( go.opentelemetry.io/otel/metric v1.35.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.38.0 // indirect - golang.org/x/sync v0.15.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.26.0 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/text v0.27.0 // indirect ) diff --git a/go.sum b/go.sum index 7f50896704..03b851838d 100644 --- a/go.sum +++ b/go.sum @@ -124,20 +124,20 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -145,16 +145,16 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc 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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From ceaaf1fcee04f472d093c451ee6fb07bdd06982a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 11:02:07 +0000 Subject: [PATCH 0613/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.77.0 to 1.78.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.77.0...v1.78.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-version: 1.78.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0940d1c924..85694cdc3c 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.2 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.77.0 + github.com/microsoftgraph/msgraph-sdk-go v1.78.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 diff --git a/go.sum b/go.sum index 7f50896704..f3ae61dab5 100644 --- a/go.sum +++ b/go.sum @@ -65,8 +65,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.1.2 h1:1pUyA1QgIeKslQwb github.com/microsoft/kiota-serialization-multipart-go v1.1.2/go.mod h1:j2K7ZyYErloDu7Kuuk993DsvfoP7LPWvAo7rfDpdPio= github.com/microsoft/kiota-serialization-text-go v1.1.2 h1:7OfKFlzdjpPygca/+OtqafkEqCWR7+94efUFGC28cLw= github.com/microsoft/kiota-serialization-text-go v1.1.2/go.mod h1:QNTcswkBPFY3QVBFmzfk00UMNViKQtV0AQKCrRw5ibM= -github.com/microsoftgraph/msgraph-sdk-go v1.77.0 h1:Dy6RQCsEgWlARfynx0Rai+PJyK8THgdNOwkpYaPCvJc= -github.com/microsoftgraph/msgraph-sdk-go v1.77.0/go.mod h1:5ncg4aauxM5XKHo/xvAq7Cjl6+Dqu6lOtoihSGKtDt4= +github.com/microsoftgraph/msgraph-sdk-go v1.78.0 h1:oBvzsU9ImDpSl7Lt2ZQKESemH8IVieAAf5ykVEDfFts= +github.com/microsoftgraph/msgraph-sdk-go v1.78.0/go.mod h1:5ncg4aauxM5XKHo/xvAq7Cjl6+Dqu6lOtoihSGKtDt4= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 h1:5jCUSosTKaINzPPQXsz7wsHWwknyBmJSu8+ZWxx3kdQ= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2/go.mod h1:iD75MK3LX8EuwjDYCmh0hkojKXK6VKME33u4daCo3cE= github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= From 93013562b4e9818aa94fe590e454179c6ebd39c2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 11:04:39 +0000 Subject: [PATCH 0614/1670] deps(go): bump github.com/Azure/azure-sdk-for-go/sdk/azcore Bumps [github.com/Azure/azure-sdk-for-go/sdk/azcore](https://github.com/Azure/azure-sdk-for-go) from 1.18.0 to 1.18.1. - [Release notes](https://github.com/Azure/azure-sdk-for-go/releases) - [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/go-mgmt-sdk-release-guideline.md) - [Commits](https://github.com/Azure/azure-sdk-for-go/compare/sdk/azcore/v1.18.0...sdk/azcore/v1.18.1) --- updated-dependencies: - dependency-name: github.com/Azure/azure-sdk-for-go/sdk/azcore dependency-version: 1.18.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0940d1c924..6352bff0de 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.24.0 toolchain go1.24.4 require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 github.com/coreos/go-oidc/v3 v3.14.1 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/go-jose/go-jose/v4 v4.1.1 diff --git a/go.sum b/go.sum index 7f50896704..9cace7d5a4 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 h1:Wc1ml6QlJs2BHQ/9Bqu1jiyggbsSjramq2oUmp5WeIo= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.0 h1:Bg8m3nq/X1DeePkAbCfb6ml6F3F0IunEhE8TMh+lY48= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.0/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk= From 6c430eba54ece4edcc2da54775d8b2cb6e026ba4 Mon Sep 17 00:00:00 2001 From: shanecrowley Date: Wed, 9 Jul 2025 16:21:18 +0100 Subject: [PATCH 0615/1670] add docs section to contributing guide * added section on CODA to contributing guide * fixed old link to wiki-based docs * added ignore for linkcheck on matrix URLs * updated spelling ignore wordlist * fixed typo --- CONTRIBUTING.md | 89 ++++++++++++++++++++++++++++++++++++++- docs/.custom_wordlist.txt | 2 + docs/conf.py | 5 ++- 3 files changed, 93 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 65921022ef..7f1e5d5624 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,6 +26,10 @@ These are mostly guidelines, not rules. Use your best judgment and feel free to - [About the test suite](#about-the-testsuite) - [Tests with dependencies](#tests-with-dependencies) - [Code style](#code-style) + - [Contributing to the documentation](#contributing-to-the-documentation) + - [Building the documentation](#building-the-documentation) + - [Testing the documentation](#testing-the-documentation) + - [Open Documentation Academy](#open-documentation-academy) - [Contributor License Agreement](#contributor-license-agreement) - [Getting help](#getting-help) @@ -48,7 +52,7 @@ Contributions are made to this project via Issues and Pull Requests (PRs). These Issues can be used to report problems with the software, request a new feature or discuss potential changes before a PR is created. When you [create a new Issue](https://github.com/ubuntu/authd/issues), a template will be loaded that will guide you through collecting and providing the information that we need to investigate. -If you find an Issue that addresses the problem you're having, please add your own reproduction information to the existing issue rather than creating a new one. Adding a [reaction](https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) can also help be indicating to our maintainers that a particular problem is affecting more than just the reporter. +If you find an Issue that addresses the problem you're having, please add your own reproduction information to the existing issue rather than creating a new one. Adding a [reaction](https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) can also help by indicating to our maintainers that a particular problem is affecting more than just the reporter. ### Pull Requests @@ -57,7 +61,7 @@ PRs to our project are always welcome and can be a quick way to get your fix or * Only fix/add the functionality in question **OR** address wide-spread whitespace/style issues, not both. * Add unit or integration tests for fixed or changed functionality. * Address a single concern in the least possible number of changed lines. -* Include documentation in the repo or on our [docs site](https://github.com/ubuntu/authd/wiki). +* Include documentation in the repo or on our [docs site](https://documentation.ubuntu.com/authd/stable/). * Be accompanied by a complete Pull Request template (loaded automatically when a PR is created). For changes that address core functionality or that would require breaking changes (e.g. a major release), it's best to open an Issue to discuss your proposal first. This is not required but can save time when creating and reviewing changes. @@ -190,6 +194,87 @@ Information about these tools and their usage will be linked below: This project follow the Go code-style. For more detailed information about the code style in use, please check . +## Contributing to the documentation + +You can contribute to the documentation in various ways. + +At the top of each page in the documentation, there is a **Give feedback** +button. If you find an issue in the documentation, clicking this button will +open an Issue submission on GitHub for the specific page. + +For minor changes, such as fixing a single typo, you can click the **pencil** +icon at the top right of any page. This will open up the source file in GitHub so +that you can make edits directly. + +For more significant changes to the content or organization of the +documentation, you should create your own fork and follow the steps +outlined in the section on [pull requests](#pull-requests). + +### Building the documentation + +After cloning your fork, change into the `/docs/` directory. +The documentation is written in markdown files grouped under +[Diátaxis](https://diataxis.fr/) categories. + +A makefile is used to preview and test the documentation locally. +To view all the possible commands, run `make` without arguments. + +The command `make run` will serve the documentation at port `8000` on +`localhost`. You can then preview the documentation in your browser and the +preview will automatically update with each change that you make. + +To clean the build environment at any point, run `make clean`. + +When you submit a PR, there are automated checks for typos and broken links. +Please run the tests locally before submitting the PR to save yourself and your +reviewers time. + +### Testing the documentation + +Automatic checks will be run on any PR relating to documentation to verify +spelling and the validity of links. Before submitting a PR, you can check for +any issues locally: + +- Check the spelling: `make spelling` +- Check the validity of links: `make linkcheck` + +Doing these checks locally is good practice. You are less likely to run into +failed CI checks after your PR is submitted and the reviewer of your PR can +more quickly focus on the substance of your contribution. + +If the documentation builds, your PR will generate a preview of the +documentation on Read the Docs. This preview appears as a check in the CI. +Click on the check to open the preview and confirm that your changes have been +applied successfully. + +### Open Documentation Academy + +authd is a proud member of the [Canonical Open Documentation +Academy](https://github.com/canonical/open-documentation-academy) (CODA). + +CODA is an initiative to encourage open source contributions from the +community, and to provide help, advice and mentorship to people making their +first contributions. + +A key aim of the initiative is to lower the barrier to successful open-source +software contributions by making documentation into the gateway, and it’s a +great way to make your first open source contributions to projects like authd. + +The best way to get started is to take a look at our [project-related +documentation +tasks](https://github.com/canonical/open-documentation-academy/issues) and read +our [Getting started +guide](https://discourse.ubuntu.com/t/getting-started/42769). Tasks typically +include testing and fixing documentation pages, updating outdated content, and +restructuring large documents. We'll help you see those tasks through to +completion. + +You can get involved the with the CODA community through: + +* The [discussion forum](https://discourse.ubuntu.com/c/community/open-documentation-academy/166) on the Ubuntu Community Hub +* The [Matrix channel](https://matrix.to/#/#documentation:ubuntu.com) for interactive chat +* [Fosstodon](https://fosstodon.org/@CanonicalDocumentation) for the latest updates and events + ## Contributor License Agreement It is a requirement that you sign the [Contributor License Agreement](https://ubuntu.com/legal/contributors) in order to contribute to this project. diff --git a/docs/.custom_wordlist.txt b/docs/.custom_wordlist.txt index d28d1832de..d223d8fcc1 100644 --- a/docs/.custom_wordlist.txt +++ b/docs/.custom_wordlist.txt @@ -8,6 +8,7 @@ Center DBus entra filesystem +Fosstodon fstab ESC GDM @@ -27,6 +28,7 @@ Keytab Keytabs libpwquality linux +makefile MDM mountpoint msentraid diff --git a/docs/conf.py b/docs/conf.py index 8464a8b895..b9ac0227dc 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -217,7 +217,10 @@ # # TODO: Remove or adjust the ACME entry after you update the contributing guide -linkcheck_ignore = ["http://127.0.0.1:8000"] +linkcheck_ignore = [ + "http://127.0.0.1:8000", + "https://matrix.to/#/#documentation:ubuntu.com", # the linkchecker struggles with hashes +] # A regex list of URLs where anchors are ignored by 'make linkcheck' From 77e24f00b62a347614125c73ce107c0c9eac494a Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 16 Jul 2025 11:45:02 +0200 Subject: [PATCH 0616/1670] Improve warning message Remove redundancy from the warning message. This changes the message from: Could not fetch user info: could not get user info: [...]. Using cached user info. to: Could not fetch user info: [...]. Using cached user info. --- internal/broker/broker.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 7c1229d608..b107c42f46 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -650,7 +650,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au userInfo, err := b.fetchUserInfo(ctx, session, &authInfo) if err != nil && authInfo.UserInfo.Name == "" { // We don't have a valid user info, so we can't proceed. - log.Error(context.Background(), err.Error()) + log.Errorf(context.Background(), "could not fetch user info: %s", err) return AuthDenied, errorMessageForDisplay(err, "could not fetch user info") } if err != nil { @@ -888,7 +888,7 @@ func (b *Broker) fetchUserInfo(ctx context.Context, session *session, t *token.A userInfo, err = b.provider.GetUserInfo(ctx, t.Token, idToken, t.ProviderMetadata) if err != nil { - return info.User{}, fmt.Errorf("could not get user info: %w", err) + return info.User{}, err } if err = b.provider.VerifyUsername(session.username, userInfo.Name); err != nil { From 795d1cfdd4bf313a4d7f910b7a6dbddcc0bebc95 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Jul 2025 09:12:53 +0000 Subject: [PATCH 0617/1670] deps(go): bump the minor-updates group across 1 directory with 3 updates Bumps the minor-updates group with 3 updates in the / directory: [golang.org/x/sys](https://github.com/golang/sys), [golang.org/x/term](https://github.com/golang/term) and [google.golang.org/grpc](https://github.com/grpc/grpc-go). Updates `golang.org/x/sys` from 0.33.0 to 0.34.0 - [Commits](https://github.com/golang/sys/compare/v0.33.0...v0.34.0) Updates `golang.org/x/term` from 0.32.0 to 0.33.0 - [Commits](https://github.com/golang/term/compare/v0.32.0...v0.33.0) Updates `google.golang.org/grpc` from 1.73.0 to 1.74.0 - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.73.0...v1.74.0) --- updated-dependencies: - dependency-name: golang.org/x/sys dependency-version: 0.34.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-updates - dependency-name: golang.org/x/term dependency-version: 0.33.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-updates - dependency-name: google.golang.org/grpc dependency-version: 1.74.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-updates ... Signed-off-by: dependabot[bot] --- go.mod | 14 +++++++------- go.sum | 52 ++++++++++++++++++++++++++-------------------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/go.mod b/go.mod index 84b1f2b088..ca109e22b1 100644 --- a/go.mod +++ b/go.mod @@ -24,9 +24,9 @@ require ( github.com/ubuntu/decorate v0.0.0-20230606064312-bc4ac83958d6 go.etcd.io/bbolt v1.4.2 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 - golang.org/x/sys v0.33.0 - golang.org/x/term v0.32.0 - google.golang.org/grpc v1.73.0 + golang.org/x/sys v0.34.0 + golang.org/x/term v0.33.0 + google.golang.org/grpc v1.74.0 google.golang.org/protobuf v1.36.6 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 @@ -64,10 +64,10 @@ require ( github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/net v0.38.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/text v0.23.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sync v0.14.0 // indirect + golang.org/x/text v0.25.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect ) // FIXME: Use released version once we have one! diff --git a/go.sum b/go.sum index 8d3a44d577..01fc4aca4a 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= @@ -120,39 +120,39 @@ go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= -go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= -golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= -google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= +golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.74.0 h1:sxRSkyLxlceWQiqDofxDot3d4u7DyoHPc7SBXMj8gGY= +google.golang.org/grpc v1.74.0/go.mod h1:NZUaK8dAMUfzhK6uxZ+9511LtOrk73UGWOFoNvz7z+s= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 4a158dbd85c0ce8c40ead71f2b84aa3102747240 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 9 Jul 2025 15:54:56 +0200 Subject: [PATCH 0618/1670] Add special case handling for when IsAuthenticated is canceled When the user cancels an ongoing authentication request (i.e. device authentication) via Ctrl+C, the PAM module calls EndSession, which cancels the ongoing IsAuthenticated call. With https://github.com/ubuntu/authd-oidc-brokers/pull/574, that returns the custom error "com.ubuntu.authd.Canceled", which we now only log at the debug level, because it's not worth logging an error message when the user cancels an authentication request. --- internal/brokers/broker.go | 4 ++++ internal/brokers/dbusbroker.go | 3 +++ 2 files changed, 7 insertions(+) diff --git a/internal/brokers/broker.go b/internal/brokers/broker.go index dcde2a3f0b..d93de9c011 100644 --- a/internal/brokers/broker.go +++ b/internal/brokers/broker.go @@ -151,6 +151,10 @@ func (b Broker) IsAuthenticated(ctx context.Context, sessionID, authenticationDa select { case <-done: + if errors.Is(err, context.Canceled) { + log.Debugf(ctx, "Authentication for session %s was canceled", sessionID) + return auth.Cancelled, "{}", nil + } if err != nil { return "", "", err } diff --git a/internal/brokers/dbusbroker.go b/internal/brokers/dbusbroker.go index 853a1199f7..906e083bce 100644 --- a/internal/brokers/dbusbroker.go +++ b/internal/brokers/dbusbroker.go @@ -152,6 +152,9 @@ func (b dbusBroker) call(ctx context.Context, method string, args ...interface{} if errors.As(err, &dbusError) && dbusError.Name == "org.freedesktop.DBus.Error.ServiceUnknown" { err = fmt.Errorf("couldn't connect to broker %q. Is it running?", b.name) } + if errors.As(err, &dbusError) && dbusError.Name == "com.ubuntu.authd.Canceled" { + return nil, context.Canceled + } return nil, errmessages.NewToDisplayError(err) } From 2db0acfb16068bf3676570522c11509082e0bbef Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 11 Jul 2025 01:01:21 +0200 Subject: [PATCH 0619/1670] Add special case handling for when PAM client disconnects unexpectedly When the PAM module is terminated abruptly (e.g. via a signal) the gRPC connection is closed and the context passed to the service method is canceled (note that this is different from when the user presses Ctrl+C, that case is handled by the PAM module by calling EndSession). We already handled the case that the context is canceled by calling the CancelIsAuthenticated broker method to cancel the ongoing IsAuthenticated request. However, that caused the `access` variable to be empty, which led to this error being logged: IsAuthenticated: Could not check authentication for session "": invalid access authentication key: We now handle that by logging a warning instead and returning the auth.Cancelled response to the PAM module. --- internal/brokers/broker.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/brokers/broker.go b/internal/brokers/broker.go index d93de9c011..079c62c5fa 100644 --- a/internal/brokers/broker.go +++ b/internal/brokers/broker.go @@ -159,8 +159,14 @@ func (b Broker) IsAuthenticated(ctx context.Context, sessionID, authenticationDa return "", "", err } case <-ctx.Done(): + log.Warningf(ctx, "Authentication aborted: PAM client disconnected unexpectedly (session %s)", sessionID) + log.Debugf(ctx, "Cancelling broker authentication (session %s)", sessionID) b.cancelIsAuthenticated(ctx, sessionID) <-done + if err != nil && !errors.Is(err, context.Canceled) { + log.Errorf(ctx, "Authentication failed: %v", err) + } + return auth.Cancelled, "{}", nil } // Validate access authentication. From 2b37cbc21a6ccbad39c645ceca04b8d6e3c002a1 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 11 Jul 2025 13:37:18 +0200 Subject: [PATCH 0620/1670] examplebroker: Return context.Canceled when IsAuthenticated was canceled Same as the dbusbroker does now --- examplebroker/broker.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/examplebroker/broker.go b/examplebroker/broker.go index 9c84b0ce8e..4d8314d68a 100644 --- a/examplebroker/broker.go +++ b/examplebroker/broker.go @@ -633,8 +633,15 @@ func (b *Broker) IsAuthenticated(ctx context.Context, sessionID, authenticationD }() access, data = b.handleIsAuthenticated(ctx, sessionInfo, authData) + + if ctx.Err() != nil { + log.Debugf(ctx, "IsAuthenticated for session %s was cancelled: %v", sessionID, ctx.Err()) + return auth.Cancelled, `{"message": "authentication request cancelled"}`, ctx.Err() + } + log.Debugf(context.TODO(), "Authentication result on session %s (%s) for user %q: %q - %#v", sessionInfo.sessionMode, sessionID, sessionInfo.username, access, data) + if access == auth.Granted && sessionInfo.currentAuthStep < sessionInfo.neededAuthSteps { data = "" if sessionInfo.pwdChange != noReset && sessionInfo.sessionMode == auth.SessionModeLogin { From 84b7bb54c13c117cade3f3c99e1db7f7a00ba36d Mon Sep 17 00:00:00 2001 From: shanecrowley Date: Mon, 14 Jul 2025 15:41:01 +0100 Subject: [PATCH 0621/1670] reorganise troubleshooting content The Troubleshooting reference contained useful information but it was getting complex and disorganised. Some of the information, such as switching versions, was also not necessarily restricted to troubleshooting. Lastly, the content involved step-by-step guides, so is more appropriate under the how-to section. * Specific troubleshooting content that was sufficiently developed and useful was moved to standalone pages. * Guidance on configuring users was moved to the dedicated configuration guide where it is most relevant and discoverable. * The Troubleshooting page remains as a reference, but was rewritten to primarily link out to relevant material. It can be developed further in this way. * Minor fixups after reorganisation of content: update header levels; update how-to index; typographical fixes. --- docs/howto/changing-versions.md | 96 +++++++++ docs/howto/configure-authd.md | 37 ++++ docs/howto/enter-recovery-mode.md | 32 +++ docs/howto/index.md | 35 +++- docs/howto/logging.md | 168 +++++++++++++++ docs/reference/troubleshooting.md | 334 +++--------------------------- 6 files changed, 385 insertions(+), 317 deletions(-) create mode 100644 docs/howto/changing-versions.md create mode 100644 docs/howto/enter-recovery-mode.md create mode 100644 docs/howto/logging.md diff --git a/docs/howto/changing-versions.md b/docs/howto/changing-versions.md new file mode 100644 index 0000000000..18d42f842c --- /dev/null +++ b/docs/howto/changing-versions.md @@ -0,0 +1,96 @@ +--- +myst: + html_meta: + "description lang=en": "Change to the edge release of authd to test new features and bug fixes." +--- + +(ref::changing-versions)= +# Changing authd versions + +To test new features or check if a bug has been fixed in a new version, +you can switch to edge releases of authd and its brokers. + +```{warning} +Do not use the edge PPA in a production system, because it may apply changes to +the authd database in a non-reversible way, which can make it difficult to roll +back to the stable version of authd. +``` + +## Switch authd to the edge PPA + +The [edge +PPA](https://launchpad.net/~ubuntu-enterprise-desktop/+archive/ubuntu/authd-edge) contains +the latest fixes and features for authd, in addition to its GNOME Shell (GDM) +counterpart. + +```shell +sudo add-apt-repository ppa:ubuntu-enterprise-desktop/authd-edge +sudo apt update +sudo apt install authd gnome-shell +``` + +Keep in mind that this version is not tested and may be incompatible with the current released version of the brokers. + +To switch back to the stable version of authd: + +```shell +sudo apt install ppa-purge +sudo ppa-purge ppa:ubuntu-enterprise-desktop/authd-edge +``` + +## Switch broker snap to the edge channel + +You can also switch to the edge channel of the broker snap: + +::::{tab-set} +:sync-group: broker + +:::{tab-item} Google IAM +:sync: google + +```shell +sudo snap switch authd-google --edge +sudo snap refresh authd-google +``` +::: + +:::{tab-item} MS Entra ID +:sync: msentraid + +```shell +sudo snap switch authd-msentraid --edge +sudo snap refresh authd-msentraid +``` +::: +:::: + +Keep in mind that this version is not tested and may be incompatible with the current released version of authd. + +To switch back to stable after trying the edge channel: + +::::{tab-set} +:sync-group: broker + +:::{tab-item} Google IAM +:sync: google + +```shell +sudo snap switch authd-google --stable +sudo snap refresh authd-google +``` +::: + +:::{tab-item} MS Entra ID +:sync: msentraid + +```shell +sudo snap switch authd-msentraid --stable +sudo snap refresh authd-msentraid +``` +::: +:::: + +```{note} +If using an edge release, you can read the +[latest development version of the documentation](https://canonical-authd.readthedocs-hosted.com/en/latest/) +``` diff --git a/docs/howto/configure-authd.md b/docs/howto/configure-authd.md index 34020bd9be..4f5352b6a5 100644 --- a/docs/howto/configure-authd.md +++ b/docs/howto/configure-authd.md @@ -4,6 +4,7 @@ myst: "description lang=en": "Configure authd and its identity brokers to enable authentication of Ubuntu devices with multiple cloud identity providers, including Google IAM and Microsoft Entra ID." --- +(ref::config)= # Configure authd for cloud identity providers This guide shows how to configure identity brokers to support authentication of @@ -238,6 +239,42 @@ in `allowed_users`: owner = "" ``` +:::::{admonition} Only my first logged-in user has access to the machine? +:class: important +By default, the first logged-in user is defined as the "owner" and only the +owner can log in. +To allow more users to log in, update the list of allowed users. + +If an administrator is the first to log in to a machine and becomes the owner, +they can ensure that the next user to log in becomes the owner by removing the +`20-owner-autoregistration.conf` file: + +::::{tab-set} +:sync-group: broker + +:::{tab-item} Google IAM +:sync: google + +```shell +sudo rm /var/snap/authd-google/current/broker.conf.d/20-owner-autoregistration.conf +``` +::: + +:::{tab-item} MS Entra ID +:sync: msentraid + +```shell +sudo rm /var/snap/authd-msentraid/current/broker.conf.d/20-owner-autoregistration.conf +``` +::: +:::: + + +This file is generated when a user logs in and becomes the owner. If it is +removed, it will be regenerated on the next successful login. + +::::: + (ref::config-user-groups)= ## Configure user groups diff --git a/docs/howto/enter-recovery-mode.md b/docs/howto/enter-recovery-mode.md new file mode 100644 index 0000000000..2e349fd210 --- /dev/null +++ b/docs/howto/enter-recovery-mode.md @@ -0,0 +1,32 @@ +--- +myst: + html_meta: + "description lang=en": "Guide on entering recovery mode after a failed login attempt with authd." +--- + +# How to enter recovery mode after failed login + +If authd and/or the broker are missing, corrupted, or broken in any way, a user may +be prevented from logging in. + +To get access to the system for modifying configurations and installations in +such cases, there are two main options: + +1. Log in as root user or another local user with administrator privileges +2. Boot into recovery mode to get root access + +The steps required for entering recovery mode are included below. + +## Boot into recovery mode + +If it is not possible to log in with the root user account or another local +user account with administrator privileges, the user can boot into recovery +mode: + +1. Reboot the device +2. During the reboot, press and hold the right SHIFT key +3. When the Grub menu appears, select `advanced options for Ubuntu` +4. Choose `recovery mode` for the correct kernel version +5. Select `drop to root shell prompt` + +The user then has access to the root filesystem and can proceed with debugging. diff --git a/docs/howto/index.md b/docs/howto/index.md index 9021e152de..2b83ede480 100644 --- a/docs/howto/index.md +++ b/docs/howto/index.md @@ -10,8 +10,8 @@ provider. ```{toctree} :titlesonly: -Install authd -Configure authd +Installing authd +Configuring authd ``` ## Login and authentication @@ -19,12 +19,11 @@ Configure authd Read about how users can authenticate when logging into Ubuntu Desktop or Server. - ```{toctree} :titlesonly: -Log in with GDM -Log in with SSH +Logging in with GDM +Logging in with SSH ``` ## Network file systems @@ -34,8 +33,30 @@ Learn how to use authd with different network file systems. ```{toctree} :titlesonly: -Use authd with NFS -Use authd with Samba +Using authd with NFS +Using authd with Samba +``` + +## Debugging and troubleshooting + +Actions you can take if something goes wrong. + +```{toctree} +:titlesonly: + +Accessing and configuring logs +Entering recovery mode on failed login +``` + +## Updating and upgrading + +authd and its brokers can currently be switched between +stable and edge releases. + +```{toctree} +:titlesonly: + +Changing authd versions ``` ## Contributing to authd diff --git a/docs/howto/logging.md b/docs/howto/logging.md new file mode 100644 index 0000000000..9cb2ea33ec --- /dev/null +++ b/docs/howto/logging.md @@ -0,0 +1,168 @@ +--- +myst: + html_meta: + "description lang=en": "Get logs from authd and configure logging behavior." +--- + +(ref::logging)= +# Accessing and configuring logs + +Logs are generated for authd and its brokers, which can help when +troubleshooting and reporting bugs. + +## authd + +`authd` logs to the system journal. + +For `authd` entries, run: + +```shell +sudo journalctl -u authd.service +``` + +If you want logs for authd and all brokers on the system, run: + +```shell +sudo journalctl -u authd.service -u "snap.authd-*.service" +``` + +For specific broker entries run the command for your chosen broker: + +::::{tab-set} +:sync-group: broker + +:::{tab-item} Google IAM +:sync: google + +```shell +sudo journalctl -u snap.authd-google.authd-google.service +``` +::: + +:::{tab-item} MS Entra ID +:sync: msentraid + +```shell +sudo journalctl -u snap.authd-msentraid.authd-msentraid.service +``` +::: +:::: + +For the GDM integration: + +```shell +sudo journalctl /usr/bin/gnome-shell +``` + +For anything else or more broader investigation, use `sudo journalctl`. + +## Configure logging verbosity + +You can increase the verbosity of the logs in different ways. + +### PAM module + +Append `debug=true` to all the lines with `pam_authd_exec.so` or `pam_authd.so` in the PAM configuration files (`common-auth`, `gdm-authd`...) in `/etc/pam.d/` to increase the verbosity of the PAM messages. + +### NSS module + +Export `AUTHD_NSS_INFO=stderr` environment variable on any program using the authd NSS module to get more info on NSS requests to authd. + +### authd service + +To increase the verbosity of the service itself, edit the service file: + +```shell +sudo systemctl edit authd.service +``` + +Add the following lines to the override file and make sure to add `-vv` at the end of the `authd` command: + +```ini +[Service] +ExecStart= +ExecStart=/usr/libexec/authd -vv +``` + +Then you need to restart the service with `sudo systemctl restart authd`. + +### GDM + +Ensure the lines in `/etc/gdm3/custom.conf` are not commented: + +```ini +[debug] +# Uncomment the line below to turn on debugging +# More verbose logs +# Additionally lets the X server dump core if it crashes +Enable=true +``` + +Then you need to restart the service with `sudo systemctl restart gdm`. + +### authd broker service + +To increase the verbosity of the broker service, edit the service file: + +::::{tab-set} +:sync-group: broker + +:::{tab-item} Google IAM +:sync: google + +```shell +sudo systemctl edit snap.authd-google.authd-google.service +``` +::: + +:::{tab-item} MS Entra ID +:sync: msentraid + +```shell +sudo systemctl edit snap.authd-msentraid.authd-msentraid.service +``` +::: +:::: + +Add the following lines to the override file and make sure to add `-vv` to the exec command: + +::::{tab-set} +:sync-group: broker + +:::{tab-item} Google IAM +:sync: google + +```ini +[Service] +ExecStart= +ExecStart=/usr/bin/snap run authd-google -vv +``` +::: + +:::{tab-item} MS Entra ID +:sync: msentraid + +```ini +[Service] +ExecStart= +ExecStart=/usr/bin/snap run authd-msentraid -vv +``` +:::: + +You will then need to restart the service with: + +::::{tab-set} +:sync-group: broker + +:::{tab-item} Google IAM +:sync: google + +`sudo snap restart authd-google`. +::: + +:::{tab-item} MS Entra ID +:sync: msentraid + +`sudo snap restart authd-msentraid`. +::: +:::: diff --git a/docs/reference/troubleshooting.md b/docs/reference/troubleshooting.md index 7455e2a64f..a018086c9d 100644 --- a/docs/reference/troubleshooting.md +++ b/docs/reference/troubleshooting.md @@ -6,313 +6,46 @@ myst: # Troubleshooting -This page includes tips for troubleshooting authd and the identity -brokers for different cloud providers. +This page includes links to authd documentation that may be helpful when +troubleshooting. ## Logging -### authd +Logs are generated for authd and its brokers, which can be useful when +troubleshooting and reporting bugs. -`authd` logs to the system journal. +To learn how to get logs and configure logging behavior, read the dedicated +[guide on logging](ref::logging). -For `authd` entries, run: +## Changing versions -```shell -sudo journalctl -u authd.service -``` +To test new features or check if a bug has been fixed in a new version, +you can switch to edge releases for authd and its brokers. -If you want logs for authd and all brokers on the system, run: +This is described in the [guide on changing +versions](ref::changing-versions). -```shell -sudo journalctl -u authd.service -u "snap.authd-*.service" -``` +## Only the first logged-in user can get access -For specific broker entries run the command for your chosen broker: +This is the expected behavior, as the first logged-in user becomes the owner +and only the owner has access by default. -::::{tab-set} -:sync-group: broker +To change access you can make the next logged-in user the owner or add more +allowed users. -:::{tab-item} Google IAM -:sync: google +Guidelines on [configuring allowed users](ref::config-allowed-users) are +outlined in the [configuring authd guide](ref::config). -```shell -sudo journalctl -u snap.authd-google.authd-google.service -``` -::: - -:::{tab-item} MS Entra ID -:sync: msentraid - -```shell -sudo journalctl -u snap.authd-msentraid.authd-msentraid.service -``` -::: -:::: - - -For the GDM integration: - -```shell -sudo journalctl /usr/bin/gnome-shell -``` - -For anything else or more broader investigation, use `sudo journalctl`. - -### Logging verbosity - -You can increase the verbosity of the logs in different ways. - -#### PAM module - -Append `debug=true` to all the lines with `pam_authd_exec.so` or `pam_authd.so` in the PAM configuration files (`common-auth`, `gdm-authd`...) in `/etc/pam.d/` to increase the verbosity of the PAM messages. - -#### NSS module - -Export `AUTHD_NSS_INFO=stderr` environment variable on any program using the authd NSS module to get more info on NSS requests to authd. - -#### authd service - -To increase the verbosity of the service itself, edit the service file: - -```shell -sudo systemctl edit authd.service -``` - -Add the following lines to the override file and make sure to add `-vv` at the end of the `authd` command: - -```ini -[Service] -ExecStart= -ExecStart=/usr/libexec/authd -vv -``` - -Then you need to restart the service with `sudo systemctl restart authd`. - -#### GDM - -Ensure the lines in `/etc/gdm3/custom.conf` are not commented: - -```ini -[debug] -# Uncomment the line below to turn on debugging -# More verbose logs -# Additionally lets the X server dump core if it crashes -Enable=true -``` - -Then you need to restart the service with `sudo systemctl restart gdm`. - -#### authd broker service - -To increase the verbosity of the broker service, edit the service file: - -::::{tab-set} -:sync-group: broker - -:::{tab-item} Google IAM -:sync: google - -```shell -sudo systemctl edit snap.authd-google.authd-google.service -``` -::: - -:::{tab-item} MS Entra ID -:sync: msentraid - -```shell -sudo systemctl edit snap.authd-msentraid.authd-msentraid.service -``` -::: -:::: - -Add the following lines to the override file and make sure to add `-vv` to the exec command: - -::::{tab-set} -:sync-group: broker - -:::{tab-item} Google IAM -:sync: google - -```ini -[Service] -ExecStart= -ExecStart=/usr/bin/snap run authd-google -vv -``` -::: - -:::{tab-item} MS Entra ID -:sync: msentraid - -```ini -[Service] -ExecStart= -ExecStart=/usr/bin/snap run authd-msentraid -vv -``` -:::: - -You will then need to restart the service with: - -::::{tab-set} -:sync-group: broker - -:::{tab-item} Google IAM -:sync: google - -`sudo snap restart authd-google`. -::: - -:::{tab-item} MS Entra ID -:sync: msentraid - -`sudo snap restart authd-msentraid`. -::: -:::: - -## Switch authd to the edge PPA - -Maybe your issue is already fixed! You can try switching to the [edge PPA](https://launchpad.net/~ubuntu-enterprise-desktop/+archive/ubuntu/authd-edge), which contains the -latest fixes and features for authd, in addition to its GNOME Shell (GDM) -counterpart. - -```{warning} -Do not use the edge PPA in a production system, because it may apply changes to -the authd database in a non-reversible way, which can make it difficult to roll -back to the stable version of authd. -``` - -```shell -sudo add-apt-repository ppa:ubuntu-enterprise-desktop/authd-edge -sudo apt update -sudo apt install authd gnome-shell -``` - -Keep in mind that this version is not tested and may be incompatible with the current released version of the brokers. - -To switch back to the stable version of authd: - -```shell -sudo apt install ppa-purge -sudo ppa-purge ppa:ubuntu-enterprise-desktop/authd-edge -``` - -```{note} -If using an edge release, you can read the -[latest development version of the documentation](https://canonical-authd.readthedocs-hosted.com/en/latest/) -``` - -## Switch broker snap to the edge channel - -Maybe your issue is already fixed! You should try switching to the edge channel of the broker snap. You can easily do that with: - -::::{tab-set} -:sync-group: broker - -:::{tab-item} Google IAM -:sync: google - -```shell -sudo snap switch authd-google --edge -sudo snap refresh authd-google -``` -::: - -:::{tab-item} MS Entra ID -:sync: msentraid - -```shell -sudo snap switch authd-msentraid --edge -sudo snap refresh authd-msentraid -``` -::: -:::: - -Keep in mind that this version is not tested and may be incompatible with the current released version of authd. You should switch back to stable after trying the edge channel: - -::::{tab-set} -:sync-group: broker - -:::{tab-item} Google IAM -:sync: google - -```shell -sudo snap switch authd-google --stable -sudo snap refresh authd-google -``` -::: - -:::{tab-item} MS Entra ID -:sync: msentraid - -```shell -sudo snap switch authd-msentraid --stable -sudo snap refresh authd-msentraid -``` -::: -:::: - -```{note} -If using an edge release, you can read the -[latest development version of the documentation](https://canonical-authd.readthedocs-hosted.com/en/latest/) -``` - -## Common issues - -### Only the first logged-in user can get access to a machine - -This is the expected behavior. - -By default, the first logged-in user is defined as the "owner" and only the -owner can log in. - -For other users to gain access after authentication, they must be added to -`allowed_users` in the `broker.conf` file. -This is outlined in the [guide for configuring authd](ref::config-allowed-users). - -See below the relevant line in the configuration, showing both the owner and -an additional user: - -```ini -[users] -allowed_users = OWNER,additionaluser1@example.com -``` - -If an administrator is the first to log in to a machine and becomes the owner, -they can ensure that the next user to log in becomes the owner by removing the -`20-owner-autoregistration.conf` file: - -::::{tab-set} -:sync-group: broker - -:::{tab-item} Google IAM -:sync: google - -```shell -sudo rm /var/snap/authd-google/current/broker.conf.d/20-owner-autoregistration.conf -``` -::: - -:::{tab-item} MS Entra ID -:sync: msentraid - -```shell -sudo rm /var/snap/authd-msentraid/current/broker.conf.d/20-owner-autoregistration.conf -``` -::: -:::: - - -This file is generated when a user logs in and becomes the owner. If it is -removed, it will be regenerated on the next successful login. - -### File ownership on shared network resources (e.g. NFS, Samba) +## File ownership on shared network resources (NFS, Samba) The user identifiers (UIDs) and group identifiers (GIDs) assigned by authd are unique to each machine. This means that when using authd with NFS or Samba, the UIDs and GIDs of users and groups on the server will not match those on the client machines, which leads to permission issues. -To avoid these issues, you can use ID mapping. For more information, see +To avoid these issues, you can use ID mapping. For more information, see the +dedicated guides on NFS and Samba: + * [Using authd with NFS](../howto/use-with-nfs) * [Using authd with Samba](../howto/use-with-samba) @@ -321,24 +54,5 @@ To avoid these issues, you can use ID mapping. For more information, see If authd and/or the broker are missing, corrupted, or broken in any way, a user may be prevented from logging in. -To get access to the system for modifying configurations and installations in -such cases, there are two main options: - -1. Log in as root user or another local user with administrator privileges -2. Boot into recovery mode to get root access - -The steps required for entering recovery mode are included below. - -### Boot into recovery mode - -If it is not possible to log in with the root user account or another local -user account with administrator privileges, the user can boot into recovery -mode: - -1. Reboot the device -2. During the reboot, press and hold the right SHIFT key -3. When the Grub menu appears, select `advanced options for Ubuntu` -4. Choose `recovery mode` for the correct kernel version -5. Select `drop to root shell prompt` - -The user then has access to the root filesystem and can proceed with debugging. +When this occurs, you can [boot into recovery mode](../howto/enter-recovery-mode.md) to +access to the system for modifying configurations and installations. From b10897b08262066d3d26cf797e65a84091ab31c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 09:07:29 +0000 Subject: [PATCH 0622/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-version: 1.79.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ce4bdc38d4..5f708faf38 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.2 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.78.0 + github.com/microsoftgraph/msgraph-sdk-go v1.79.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 diff --git a/go.sum b/go.sum index 70e7f8832c..e671e0b8bf 100644 --- a/go.sum +++ b/go.sum @@ -65,8 +65,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.1.2 h1:1pUyA1QgIeKslQwb github.com/microsoft/kiota-serialization-multipart-go v1.1.2/go.mod h1:j2K7ZyYErloDu7Kuuk993DsvfoP7LPWvAo7rfDpdPio= github.com/microsoft/kiota-serialization-text-go v1.1.2 h1:7OfKFlzdjpPygca/+OtqafkEqCWR7+94efUFGC28cLw= github.com/microsoft/kiota-serialization-text-go v1.1.2/go.mod h1:QNTcswkBPFY3QVBFmzfk00UMNViKQtV0AQKCrRw5ibM= -github.com/microsoftgraph/msgraph-sdk-go v1.78.0 h1:oBvzsU9ImDpSl7Lt2ZQKESemH8IVieAAf5ykVEDfFts= -github.com/microsoftgraph/msgraph-sdk-go v1.78.0/go.mod h1:5ncg4aauxM5XKHo/xvAq7Cjl6+Dqu6lOtoihSGKtDt4= +github.com/microsoftgraph/msgraph-sdk-go v1.79.0 h1:Pz6xWAQ097DP2/SKm4xtnSAHZSHbQMkSVLzNfFrTUEs= +github.com/microsoftgraph/msgraph-sdk-go v1.79.0/go.mod h1:w+NAJ1j3fyxb2An9NWB1CsY+KQMcgBbcrXXA/cNtZ3c= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 h1:5jCUSosTKaINzPPQXsz7wsHWwknyBmJSu8+ZWxx3kdQ= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2/go.mod h1:iD75MK3LX8EuwjDYCmh0hkojKXK6VKME33u4daCo3cE= github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= From 8b2d3563120750fd1552bd74fcf431f29ac16c52 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 09:24:26 +0000 Subject: [PATCH 0623/1670] deps(go): bump github.com/golang-jwt/jwt/v5 from 5.2.2 to 5.2.3 --- updated-dependencies: - dependency-name: github.com/golang-jwt/jwt/v5 dependency-version: 5.2.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ce4bdc38d4..b5816d3129 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/go-jose/go-jose/v4 v4.1.1 github.com/godbus/dbus/v5 v5.1.0 - github.com/golang-jwt/jwt/v5 v5.2.2 + github.com/golang-jwt/jwt/v5 v5.2.3 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible github.com/microsoftgraph/msgraph-sdk-go v1.78.0 diff --git a/go.sum b/go.sum index 70e7f8832c..df85d554cf 100644 --- a/go.sum +++ b/go.sum @@ -29,8 +29,8 @@ github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlnd github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= -github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0= +github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= From fe83ab026311808a499dc81b8425ec55c0449ce0 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 21 Jul 2025 19:01:18 +0200 Subject: [PATCH 0624/1670] Remove support for old encrypted token MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We don’t store the token encrypted anymore since commit 524fb8216f6aef0781d3b0efea60f70cafa98d7c which was merged on Nov 7, 2024 and released in v0.2.0 of the broker on Jan 30, 2025. That should be long enough ago that we don’t need to support it anymore, all active users should have had re-authenticate via device auth by now, which will have created a new, unencrypted token. This simplifies things for the device registration PR, where we need to parse the token in GetAuthenticationModes to decide if local password authentication should be offered or not (it should not in case that device registration is enabled in the broker config and the stored token is an old token which can’t be used for device registration). --- cmd/authd-oidc/daemon/daemon.go | 21 ++-- internal/broker/broker.go | 85 ++++++---------- internal/broker/broker_test.go | 4 + internal/token/export_test.go | 5 - internal/token/migration.go | 149 ---------------------------- internal/token/migration_test.go | 161 ------------------------------- 6 files changed, 38 insertions(+), 387 deletions(-) delete mode 100644 internal/token/export_test.go delete mode 100644 internal/token/migration.go delete mode 100644 internal/token/migration_test.go diff --git a/cmd/authd-oidc/daemon/daemon.go b/cmd/authd-oidc/daemon/daemon.go index c4e46c46d0..e721a7a461 100644 --- a/cmd/authd-oidc/daemon/daemon.go +++ b/cmd/authd-oidc/daemon/daemon.go @@ -30,9 +30,8 @@ type App struct { // only overriable for tests. type systemPaths struct { - BrokerConf string - DataDir string - OldEncryptedTokensDir string + BrokerConf string + DataDir string } // daemonConfig defines configuration parameters of the daemon. @@ -56,24 +55,17 @@ func New(name string) *App { // Command parsing has been successful. Returns to not print usage anymore. a.rootCmd.SilenceUsage = true - // Before version 0.2, we used to store the tokens encrypted - // in a different directory. For backward compatibility, we - // try to use the encrypted tokens from the old directory - // if they are not found in the new one. - oldEncryptedTokensDir := filepath.Join("/var", "lib", name) dataDir := filepath.Join("/var", "lib", name) configDir := "." if snapData := os.Getenv("SNAP_DATA"); snapData != "" { - oldEncryptedTokensDir = filepath.Join(snapData, "cache") dataDir = snapData configDir = snapData } // Set config defaults a.config = daemonConfig{ Paths: systemPaths{ - BrokerConf: filepath.Join(configDir, "broker.conf"), - DataDir: dataDir, - OldEncryptedTokensDir: oldEncryptedTokensDir, + BrokerConf: filepath.Join(configDir, "broker.conf"), + DataDir: dataDir, }, } @@ -146,9 +138,8 @@ func (a *App) serve(config daemonConfig) error { } b, err := broker.New(broker.Config{ - ConfigFile: config.Paths.BrokerConf, - DataDir: config.Paths.DataDir, - OldEncryptedTokensDir: config.Paths.OldEncryptedTokensDir, + ConfigFile: config.Paths.BrokerConf, + DataDir: config.Paths.DataDir, }) if err != nil { return err diff --git a/internal/broker/broker.go b/internal/broker/broker.go index b107c42f46..d7866c668c 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -39,9 +39,8 @@ const ( // Config is the configuration for the broker. type Config struct { - ConfigFile string - DataDir string - OldEncryptedTokensDir string + ConfigFile string + DataDir string userConfig } @@ -69,14 +68,13 @@ type session struct { attemptsPerMode map[string]int nextAuthModes []string - oidcServer *oidc.Provider - oauth2Config oauth2.Config - authInfo map[string]any - isOffline bool - userDataDir string - passwordPath string - tokenPath string - oldEncryptedTokenPath string + oidcServer *oidc.Provider + oauth2Config oauth2.Config + authInfo map[string]any + isOffline bool + userDataDir string + passwordPath string + tokenPath string isAuthenticating *isAuthenticatedCtx } @@ -174,7 +172,6 @@ func (b *Broker) NewSession(username, lang, mode string) (sessionID, encryptionK s.tokenPath = filepath.Join(s.userDataDir, "token.json") // The password is stored in $DATA_DIR/$ISSUER/$USERNAME/password. s.passwordPath = filepath.Join(s.userDataDir, "password") - s.oldEncryptedTokenPath = filepath.Join(b.cfg.OldEncryptedTokensDir, issuer, username+".cache") // Construct an OIDC provider via OIDC discovery. s.oidcServer, err = b.connectToOIDCServer(context.Background()) @@ -260,8 +257,8 @@ func (b *Broker) availableAuthModes(session session) (availableModes []string, e switch session.mode { case sessionmode.ChangePassword, sessionmode.ChangePasswordOld: // Session is for changing the password. - if !tokenExists(session) { - return nil, errors.New("user has no cached token") + if !passwordFileExists(session) { + return nil, errors.New("password file does not exist, cannot change password") } return []string{authmodes.Password}, nil @@ -283,7 +280,7 @@ func (b *Broker) availableAuthModes(session session) (availableModes []string, e func authModeIsAvailable(session session, authMode string) bool { switch authMode { case authmodes.Password: - return tokenExists(session) + return tokenExists(session) && passwordFileExists(session) case authmodes.NewPassword: return true case authmodes.Device, authmodes.DeviceQr: @@ -293,20 +290,19 @@ func authModeIsAvailable(session session, authMode string) bool { } func tokenExists(session session) bool { - tokenExists, err := fileutils.FileExists(session.tokenPath) + exists, err := fileutils.FileExists(session.tokenPath) if err != nil { log.Warningf(context.Background(), "Could not check if token exists: %v", err) } - if tokenExists { - return true - } + return exists +} - // Check the old encrypted token path. - tokenExists, err = fileutils.FileExists(session.oldEncryptedTokenPath) +func passwordFileExists(session session) bool { + exists, err := fileutils.FileExists(session.passwordPath) if err != nil { - log.Warningf(context.Background(), "Could not check if old encrypted token exists: %v", err) + log.Warningf(context.Background(), "Could not check if local password file exists: %v", err) } - return tokenExists + return exists } func (b *Broker) authModesSupportedByUI(supportedUILayouts []map[string]string) (supportedModes []string) { @@ -583,41 +579,20 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au return AuthNext, nil case authmodes.Password: - useOldEncryptedToken, err := token.UseOldEncryptedToken(session.tokenPath, session.passwordPath, session.oldEncryptedTokenPath) + ok, err := password.CheckPassword(secret, session.passwordPath) if err != nil { log.Error(context.Background(), err.Error()) return AuthDenied, errorMessage{Message: "could not check password file"} } + if !ok { + log.Noticef(context.Background(), "Authentication failure: incorrect local password for user %q", session.username) + return AuthRetry, errorMessage{Message: "incorrect password"} + } - if useOldEncryptedToken { - authInfo, err = token.LoadOldEncryptedAuthInfo(session.oldEncryptedTokenPath, secret) - if err != nil { - log.Error(context.Background(), err.Error()) - return AuthDenied, errorMessage{Message: "could not load encrypted token"} - } - - // We were able to decrypt the old token with the password, so we can now hash and store the password in the - // new format. - if err = password.HashAndStorePassword(secret, session.passwordPath); err != nil { - log.Error(context.Background(), err.Error()) - return AuthDenied, errorMessage{Message: "could not store password"} - } - } else { - ok, err := password.CheckPassword(secret, session.passwordPath) - if err != nil { - log.Error(context.Background(), err.Error()) - return AuthDenied, errorMessage{Message: "could not check password"} - } - if !ok { - log.Noticef(context.Background(), "Authentication failure: incorrect local password for user %q", session.username) - return AuthRetry, errorMessage{Message: "incorrect password"} - } - - authInfo, err = token.LoadAuthInfo(session.tokenPath) - if err != nil { - log.Error(context.Background(), err.Error()) - return AuthDenied, errorMessage{Message: "could not load stored token"} - } + authInfo, err = token.LoadAuthInfo(session.tokenPath) + if err != nil { + log.Error(context.Background(), err.Error()) + return AuthDenied, errorMessage{Message: "could not load stored token"} } // If the session is for changing the password, we don't need to refresh the token and user info (and we don't @@ -716,10 +691,6 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au return AuthDenied, errorMessage{Message: "could not cache user info"} } - // At this point we successfully stored the hashed password and a new token, so we can now safely remove any old - // encrypted token. - token.CleanupOldEncryptedToken(session.oldEncryptedTokenPath) - return AuthGranted, userInfoMessage{UserInfo: authInfo.UserInfo} } diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index ef4b56387b..c23445436f 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -237,6 +237,8 @@ func TestGetAuthenticationModes(t *testing.T) { require.NoError(t, err, "Setup: MkdirAll should not have returned an error") err = os.WriteFile(b.TokenPathForSession(sessionID), []byte("some token"), 0600) require.NoError(t, err, "Setup: WriteFile should not have returned an error") + err = os.WriteFile(b.PasswordFilepathForSession(sessionID), []byte("some password"), 0600) + require.NoError(t, err, "Setup: WriteFile should not have returned an error") } if tc.nextAuthMode != "" { b.SetNextAuthModes(sessionID, []string{tc.nextAuthMode}) @@ -351,6 +353,8 @@ func TestSelectAuthenticationMode(t *testing.T) { require.NoError(t, err, "Setup: MkdirAll should not have returned an error") err = os.WriteFile(b.TokenPathForSession(sessionID), []byte("some token"), 0600) require.NoError(t, err, "Setup: WriteFile should not have returned an error") + err = os.WriteFile(b.PasswordFilepathForSession(sessionID), []byte("some password"), 0600) + require.NoError(t, err, "Setup: WriteFile should not have returned an error") } if tc.nextAuthMode != "" { b.SetNextAuthModes(sessionID, []string{tc.nextAuthMode}) diff --git a/internal/token/export_test.go b/internal/token/export_test.go deleted file mode 100644 index 6f26c85418..0000000000 --- a/internal/token/export_test.go +++ /dev/null @@ -1,5 +0,0 @@ -package token - -func Saltlen() int { - return saltLen -} diff --git a/internal/token/migration.go b/internal/token/migration.go deleted file mode 100644 index 6e3e088334..0000000000 --- a/internal/token/migration.go +++ /dev/null @@ -1,149 +0,0 @@ -package token - -import ( - "context" - "crypto/aes" - "crypto/cipher" - "encoding/json" - "fmt" - "os" - "path/filepath" - - "github.com/ubuntu/authd-oidc-brokers/internal/fileutils" - "github.com/ubuntu/authd/log" - "golang.org/x/crypto/scrypt" -) - -const saltLen = 32 - -// UseOldEncryptedToken checks if the password file or the old encrypted token file exists. It returns false if the -// password file exists, true if only the old encrypted token file exists, and an error if neither exists. -func UseOldEncryptedToken(passwordPath, tokenPath, oldEncryptedTokenPath string) (bool, error) { - passwordExists, err := fileutils.FileExists(passwordPath) - if err != nil { - return false, fmt.Errorf("could not check password file: %w", err) - } - tokenExists, err := fileutils.FileExists(tokenPath) - if err != nil { - return false, fmt.Errorf("could not check token file: %w", err) - } - if passwordExists && tokenExists { - return false, nil - } - - oldEncryptedTokenExists, err := fileutils.FileExists(oldEncryptedTokenPath) - if err != nil { - return false, fmt.Errorf("could not check old encrypted token file: %w", err) - } - if !oldEncryptedTokenExists { - if !passwordExists { - // We mention the password file in the error message instead of the old encrypted token file, because the latter - // is only used for backward compatibility, so if it doesn't exist, the missing password file is the real issue. - return false, fmt.Errorf("password file %q does not exist", passwordPath) - } - // We only get here if the password file exists and the token file does not exist. - return false, fmt.Errorf("token file %q does not exist", tokenPath) - } - - return true, nil -} - -// LoadOldEncryptedAuthInfo reads the token in the old encrypted format from the given path and decrypts it using the -// given password. It's used for backward compatibility. -func LoadOldEncryptedAuthInfo(path, password string) (AuthCachedInfo, error) { - encryptedData, err := os.ReadFile(path) - if err != nil { - return AuthCachedInfo{}, fmt.Errorf("could not read token: %v", err) - } - jsonData, err := decrypt(encryptedData, []byte(password)) - if err != nil { - return AuthCachedInfo{}, fmt.Errorf("could not decrypt token: %v", err) - } - - var cachedInfo AuthCachedInfo - if err := json.Unmarshal(jsonData, &cachedInfo); err != nil { - return AuthCachedInfo{}, fmt.Errorf("could not unmarshal token: %v", err) - } - - return cachedInfo, nil -} - -func decrypt(blob, key []byte) ([]byte, error) { - if len(blob) < saltLen { - return nil, fmt.Errorf("blob is too short to contain a valid salt") - } - - salt, data := blob[len(blob)-saltLen:], blob[:len(blob)-saltLen] - - derivedKey, err := scrypt.Key(key, salt, 32768, 8, 1, 32) - if err != nil { - return nil, err - } - - block, err := aes.NewCipher(derivedKey) - if err != nil { - return nil, err - } - - gcm, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - - if len(data) < gcm.NonceSize() { - return nil, fmt.Errorf("data is too short to contain a valid nonce") - } - - decrypted, err := gcm.Open(nil, data[:gcm.NonceSize()], data[gcm.NonceSize():], nil) - if err != nil { - return nil, err - } - - return decrypted, nil -} - -// CleanupOldEncryptedToken removes the old encrypted token file at the given path and its parent directories if they are empty. -func CleanupOldEncryptedToken(path string) { - exists, err := fileutils.FileExists(path) - if err != nil { - log.Warningf(context.Background(), "Failed to check if old encrypted token exists %s: %v", path, err) - } - if !exists { - return - } - - if err := os.Remove(path); err != nil { - log.Warningf(context.Background(), "Failed to remove old encrypted token %s: %v", path, err) - return - } - - // Also remove the parent directory and the parent's parent directory if they are empty. The directory structure was: - // $SNAP_DATA/cache/$ISSUER/$USERNAME.cache - // so we try to remove the $SNAP_DATA/cache/$ISSUER directory and the $SNAP_DATA/cache directory. - - // Check if the parent directory is empty. - empty, err := fileutils.IsDirEmpty(filepath.Dir(path)) - if err != nil { - log.Warningf(context.Background(), "Failed to check if old encrypted token parent directory %s is empty: %v", filepath.Dir(path), err) - return - } - if !empty { - return - } - if err := os.Remove(filepath.Dir(path)); err != nil { - log.Warningf(context.Background(), "Failed to remove old encrypted token directory %s: %v", filepath.Dir(path), err) - } - - // Check if the parent's parent directory is empty. - empty, err = fileutils.IsDirEmpty(filepath.Dir(filepath.Dir(path))) - if err != nil { - log.Warningf(context.Background(), "Failed to check if old encrypted token parent directory %s is empty: %v", filepath.Dir(filepath.Dir(path)), err) - return - } - if !empty { - return - } - if err := os.Remove(filepath.Dir(filepath.Dir(path))); err != nil { - log.Warningf(context.Background(), "Failed to remove old encrypted token parent directory %s: %v", filepath.Dir(filepath.Dir(path)), err) - } -} diff --git a/internal/token/migration_test.go b/internal/token/migration_test.go deleted file mode 100644 index 61508406ca..0000000000 --- a/internal/token/migration_test.go +++ /dev/null @@ -1,161 +0,0 @@ -package token_test - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "encoding/json" - "fmt" - "io" - "os" - "testing" - - "github.com/stretchr/testify/require" - "github.com/ubuntu/authd-oidc-brokers/internal/token" - "golang.org/x/crypto/scrypt" -) - -func TestUseOldEncryptedToken(t *testing.T) { - t.Parallel() - - tests := map[string]struct { - passwordFileExists bool - newTokenFileExists bool - oldEncryptedTokenFileExists bool - - wantRet bool - wantError bool - }{ - "Success_when_both_the_password_file_and_the_new_token_file_exist": {passwordFileExists: true, newTokenFileExists: true, wantRet: false}, - "Success_when_old_encrypted_token_file_exists": {oldEncryptedTokenFileExists: true, wantRet: true, wantError: false}, - - "Error_if_only_the_password_file_exists": {passwordFileExists: true, wantRet: false, wantError: true}, - "Error_if_only_the_new_token_file_exists": {newTokenFileExists: true, wantRet: false, wantError: true}, - "Error_if_neither_the_password_file_nor_the_old_encrypted_token_file_exists": {wantError: true}, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - t.Parallel() - - passwordPath := t.TempDir() + "/password" - tokenPath := t.TempDir() + "/token" - oldEncryptedTokenPath := t.TempDir() + "/oldtoken" - - if tc.passwordFileExists { - err := os.WriteFile(passwordPath, []byte("password"), 0600) - require.NoError(t, err, "WriteFile should not return an error") - } - if tc.newTokenFileExists { - err := os.WriteFile(tokenPath, []byte("token"), 0600) - require.NoError(t, err, "WriteFile should not return an error") - } - if tc.oldEncryptedTokenFileExists { - err := os.WriteFile(oldEncryptedTokenPath, []byte("encryptedtoken"), 0600) - require.NoError(t, err, "WriteFile should not return an error") - } - - got, err := token.UseOldEncryptedToken(passwordPath, tokenPath, oldEncryptedTokenPath) - if tc.wantError { - require.Error(t, err, "UseOldEncryptedToken should return an error") - return - } - require.NoError(t, err, "UseOldEncryptedToken should not return an error") - require.Equal(t, tc.wantRet, got, "UseOldEncryptedToken should return the expected value") - }) - } -} - -func TestLoadOldEncryptedAuthInfo(t *testing.T) { - t.Parallel() - - tests := map[string]struct { - noOldToken bool - invalidData bool - incorrectPassword bool - - wantToken token.AuthCachedInfo - wantError bool - }{ - "Successfully_load_old_encrypted_token": {wantToken: testToken, wantError: false}, - "Error_when_file_does_not_exist": {noOldToken: true, wantError: true}, - "Error_when_file_contains_invalid_data": {invalidData: true, wantError: true}, - "Error_when_password_is_incorrect": {incorrectPassword: true, wantError: true}, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - t.Parallel() - - tokenPath := t.TempDir() + "/oldtoken" - - enteredPassword := "password" - oldPassword := enteredPassword - if tc.incorrectPassword { - oldPassword = "wrongpassword" - } - - if !tc.noOldToken { - jsonData, err := json.Marshal(testToken) - require.NoError(t, err, "Marshal should not return an error") - encrypted, err := encrypt(jsonData, []byte(oldPassword)) - require.NoError(t, err, "encrypt should not return an error") - err = os.WriteFile(tokenPath, encrypted, 0600) - require.NoError(t, err, "WriteFile should not return an error") - } - - if tc.invalidData { - err := os.WriteFile(tokenPath, []byte("invalid data"), 0600) - require.NoError(t, err, "WriteFile should not return an error") - } - - got, err := token.LoadOldEncryptedAuthInfo(tokenPath, enteredPassword) - if tc.wantError { - require.Error(t, err, "LoadOldEncryptedAuthInfo should return an error") - return - } - require.NoError(t, err, "LoadOldEncryptedAuthInfo should not return an error") - require.Equal(t, tc.wantToken, got, "LoadOldEncryptedAuthInfo should return the expected value") - }) - } -} - -func encrypt(data, key []byte) ([]byte, error) { - // Generate a random salt - salt := make([]byte, token.Saltlen()) - if _, err := io.ReadFull(rand.Reader, salt); err != nil { - return nil, fmt.Errorf("could not generate salt: %v", err) - } - - // Derive a key from the password using the salt - derivedKey, err := scrypt.Key(key, salt, 32768, 8, 1, 32) - if err != nil { - return nil, fmt.Errorf("could not derive key: %v", err) - } - - // Create a new AES cipher block - block, err := aes.NewCipher(derivedKey) - if err != nil { - return nil, fmt.Errorf("could not create cipher block: %v", err) - } - - // Create a GCM cipher - gcm, err := cipher.NewGCM(block) - if err != nil { - return nil, fmt.Errorf("could not create GCM cipher: %v", err) - } - - // Generate a random nonce - nonce := make([]byte, gcm.NonceSize()) - if _, err := io.ReadFull(rand.Reader, nonce); err != nil { - return nil, fmt.Errorf("could not generate nonce: %v", err) - } - - // Encrypt the data - ciphertext := gcm.Seal(nonce, nonce, data, nil) - - // Concatenate the nonce, encrypted data, and salt - result := append(ciphertext, salt...) - - return result, nil -} From a96b0db5747ad1eec2bb411f8b34a09a86eca395 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 21 Jul 2025 19:44:55 +0200 Subject: [PATCH 0625/1670] Update test data "Definitely an encrypted token" -> "Definitely a token" --- internal/broker/broker_test.go | 4 ++-- .../data/provider_url/user1@example.com/token.json | 2 +- .../data/provider_url/user2@example.com/token.json | 2 +- .../data/provider_url/user1@example.com/token.json | 2 +- .../data/provider_url/user2@example.com/token.json | 2 +- .../data/provider_url/user1@example.com/token.json | 2 +- .../data/provider_url/user2@example.com/token.json | 2 +- .../data/provider_url/user1@example.com/token.json | 2 +- .../data/provider_url/user2@example.com/token.json | 2 +- .../data/provider_url/test-user@email.com/token.json | 2 +- .../data/provider_url/test-user@email.com/token.json | 2 +- .../data/provider_url/test-user@email.com/token.json | 2 +- .../data/provider_url/test-user@email.com/token.json | 2 +- .../data/provider_url/test-user@email.com/token.json | 2 +- .../data/provider_url/test-user@email.com/token.json | 2 +- .../data/provider_url/test-user@email.com/token.json | 2 +- .../data/provider_url/test-user@email.com/token.json | 2 +- .../data/provider_url/test-user@email.com/token.json | 2 +- .../data/provider_url/test-user@email.com/token.json | 2 +- .../data/provider_url/test-user@email.com/token.json | 2 +- .../data/provider_url/test-user@email.com/token.json | 2 +- .../data/provider_url/test-user@email.com/token.json | 2 +- .../data/provider_url/test-user@email.com/token.json | 2 +- .../data/provider_url/test-user@email.com/token.json | 2 +- .../data/provider_url/test-user@email.com/token.json | 2 +- .../data/provider_url/test-user@email.com/token.json | 2 +- .../data/provider_url/test-user@email.com/token.json | 2 +- .../data/provider_url/test-user@email.com/token.json | 2 +- .../data/provider_url/test-user@email.com/token.json | 2 +- .../data/provider_url/test-user@email.com/token.json | 2 +- .../data/provider_url/test-user@email.com/token.json | 2 +- .../data/provider_url/test-user@email.com/token.json | 2 +- .../data/provider_url/test-user@email.com/token.json | 2 +- 33 files changed, 34 insertions(+), 34 deletions(-) diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index c23445436f..eb6f039eb0 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -774,7 +774,7 @@ func TestIsAuthenticated(t *testing.T) { // Ensure that the token content is generic to avoid golden file conflicts if _, err := os.Stat(b.TokenPathForSession(sessionID)); err == nil { - err := os.WriteFile(b.TokenPathForSession(sessionID), []byte("Definitely an encrypted token"), 0600) + err := os.WriteFile(b.TokenPathForSession(sessionID), []byte("Definitely a token"), 0600) require.NoError(t, err, "Teardown: Failed to write generic token file") } passwordPath := b.PasswordFilepathForSession(sessionID) @@ -925,7 +925,7 @@ func TestConcurrentIsAuthenticated(t *testing.T) { for _, sessionID := range []string{firstSession, secondSession} { // Ensure that the token content is generic to avoid golden file conflicts if _, err := os.Stat(b.TokenPathForSession(sessionID)); err == nil { - err := os.WriteFile(b.TokenPathForSession(sessionID), []byte("Definitely an encrypted token"), 0600) + err := os.WriteFile(b.TokenPathForSession(sessionID), []byte("Definitely a token"), 0600) require.NoError(t, err, "Teardown: Failed to write generic token file") } passwordPath := b.PasswordFilepathForSession(sessionID) diff --git a/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_and_finishes_before_second/data/provider_url/user1@example.com/token.json b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_and_finishes_before_second/data/provider_url/user1@example.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_and_finishes_before_second/data/provider_url/user1@example.com/token.json +++ b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_and_finishes_before_second/data/provider_url/user1@example.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_and_finishes_before_second/data/provider_url/user2@example.com/token.json b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_and_finishes_before_second/data/provider_url/user2@example.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_and_finishes_before_second/data/provider_url/user2@example.com/token.json +++ b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_and_finishes_before_second/data/provider_url/user2@example.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1@example.com/token.json b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1@example.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1@example.com/token.json +++ b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first/data/provider_url/user1@example.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2@example.com/token.json b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2@example.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2@example.com/token.json +++ b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first/data/provider_url/user2@example.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/data/provider_url/user1@example.com/token.json b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/data/provider_url/user1@example.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/data/provider_url/user1@example.com/token.json +++ b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/data/provider_url/user1@example.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/data/provider_url/user2@example.com/token.json b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/data/provider_url/user2@example.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/data/provider_url/user2@example.com/token.json +++ b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_but_second_finishes_first_and_is_registered_as_the_owner/data/provider_url/user2@example.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1@example.com/token.json b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1@example.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1@example.com/token.json +++ b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user1@example.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2@example.com/token.json b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2@example.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2@example.com/token.json +++ b/internal/broker/testdata/golden/TestConcurrentIsAuthenticated/First_auth_starts_first_then_second_starts_and_first_finishes/data/provider_url/user2@example.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/token.json +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_still_allowed_if_token_is_missing_scopes/data/provider_url/test-user@email.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_to_change_password_still_allowed_if_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_to_change_password_still_allowed_if_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_to_change_password_still_allowed_if_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_to_change_password_still_allowed_if_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_when_the_auth_data_secret_field_uses_the_old_name/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_when_the_auth_data_secret_field_uses_the_old_name/data/provider_url/test-user@email.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_when_the_auth_data_secret_field_uses_the_old_name/data/provider_url/test-user@email.com/token.json +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_when_the_auth_data_secret_field_uses_the_old_name/data/provider_url/test-user@email.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_keeps_old_groups_if_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_keeps_old_groups_if_session_is_offline/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_keeps_old_groups_if_session_is_offline/data/provider_url/test-user@email.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_keeps_old_groups_if_session_is_offline/data/provider_url/test-user@email.com/token.json +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_keeps_old_groups_if_session_is_offline/data/provider_url/test-user@email.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/token.json +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_refreshes_expired_token/data/provider_url/test-user@email.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_refreshes_groups/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_refreshes_groups/data/provider_url/test-user@email.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_refreshes_groups/data/provider_url/test-user@email.com/token.json +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_refreshes_groups/data/provider_url/test-user@email.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/token.json +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_still_allowed_if_server_is_unreachable/data/provider_url/test-user@email.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/token.json +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_still_allowed_if_token_is_expired_and_server_is_unreachable/data/provider_url/test-user@email.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_provider_authentication_is_forced/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_provider_authentication_is_forced/data/provider_url/test-user@email.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_provider_authentication_is_forced/data/provider_url/test-user@email.com/token.json +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_provider_authentication_is_forced/data/provider_url/test-user@email.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode/data/provider_url/test-user@email.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode/data/provider_url/test-user@email.com/token.json +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode/data/provider_url/test-user@email.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/token.json +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_qrcode_reacquires_token/data/provider_url/test-user@email.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_existing_token_has_no_user_info_and_fetching_user_info_fails/data/provider_url/test-user@email.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/token.json +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_and_token_is_invalid/data/provider_url/test-user@email.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/token.json +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_and_token_refresh_times_out/data/provider_url/test-user@email.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/token.json +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_mode_is_password_but_server_returns_error/data/provider_url/test-user@email.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provided_wrong_secret/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provided_wrong_secret/data/provider_url/test-user@email.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provided_wrong_secret/data/provider_url/test-user@email.com/token.json +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provided_wrong_secret/data/provider_url/test-user@email.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provider_authentication_is_forced_and_session_is_offline/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provider_authentication_is_forced_and_session_is_offline/data/provider_url/test-user@email.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provider_authentication_is_forced_and_session_is_offline/data/provider_url/test-user@email.com/token.json +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_provider_authentication_is_forced_and_session_is_offline/data/provider_url/test-user@email.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/token.json +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Error_when_token_is_expired_and_refreshing_token_fails/data/provider_url/test-user@email.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Extra_groups_configured/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Extra_groups_configured/data/provider_url/test-user@email.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Extra_groups_configured/data/provider_url/test-user@email.com/token.json +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Extra_groups_configured/data/provider_url/test-user@email.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured/data/provider_url/test-user@email.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured/data/provider_url/test-user@email.com/token.json +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured/data/provider_url/test-user@email.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured_but_user_does_not_become_owner/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured_but_user_does_not_become_owner/data/provider_url/test-user@email.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured_but_user_does_not_become_owner/data/provider_url/test-user@email.com/token.json +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Owner_extra_groups_configured_but_user_does_not_become_owner/data/provider_url/test-user@email.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/token.json +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Successfully_authenticate_user_with_device_auth_and_newpassword/data/provider_url/test-user@email.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/token.json b/internal/broker/testdata/golden/TestIsAuthenticated/Successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/token.json index 80ab783820..ecaed7cd75 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/token.json +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Successfully_authenticate_user_with_password/data/provider_url/test-user@email.com/token.json @@ -1 +1 @@ -Definitely an encrypted token \ No newline at end of file +Definitely a token \ No newline at end of file From a03bd90cb99447cbb63cd6ab156e31715bd61776 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 21 Jul 2025 19:46:42 +0200 Subject: [PATCH 0626/1670] Fix typo --- internal/broker/broker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index d7866c668c..596416ff4c 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -778,7 +778,7 @@ func (b *Broker) UserPreCheck(username string) (string, error) { continue } - // If suffx is only "*", TrimPrefix will return the empty string and that works for the 'match all' case also. + // If suffix is only "*", TrimPrefix will return the empty string and that works for the 'match all' case also. suffix = strings.TrimPrefix(suffix, "*") if strings.HasSuffix(username, suffix) { found = true From c0b4873aee0e0740208d6b3da084fa169d52ea42 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 21 Jul 2025 22:40:19 +0200 Subject: [PATCH 0627/1670] Refactor script to get/set snap version To support setting consts.Version at build time. --- snap/get_version | 107 -------------------------------------------- snap/snapcraft.yaml | 3 +- snap/version | 91 +++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 108 deletions(-) delete mode 100755 snap/get_version create mode 100755 snap/version diff --git a/snap/get_version b/snap/get_version deleted file mode 100755 index b8e3ce6a8a..0000000000 --- a/snap/get_version +++ /dev/null @@ -1,107 +0,0 @@ -#!/bin/sh -set -eu - -# This scripts sets the version of the snap that's being based on the -# git tags on the current branch. - -# If there's a tag prefixed with the current branch name, that tag is -# used and the prefix is stripped. For example: -# * msentraid-0.1.0 -> 0.1.0 -# -# Else, the highest version tag that starts with a number (in contrast -# to a prefix like "msentraid-") is used. For example: -# * 0.1.0 -> 0.1.0 -# -# 1. If current commit is tagged, that tag is used as the version as is. -# 2. If current commit is not tagged, the version is: -# * When on main: + -# * Else: +. -# -# The version is appended with ".dirty" if there are uncommitted changes. - -# set_version appends ".dirty" if needed and then sets the version of the snap -# $1: version: the version to set. -set_version() { - version="${1}" - - version=$(annotate_with_dirty "${version}") - craftctl set version="${version}" -} - -# annotate_with_dirty appends ".dirty" to the version if there are -# uncommitted changes. -# $1: version: the version to annotate. -annotate_with_dirty() { - version="${1}" - - # check if current tree content is dirty. - is_dirty=$(git -C "${SNAPCRAFT_PART_SRC}" status --porcelain) - if [ -n "${is_dirty}" ]; then - version="${version}.dirty" - fi - - echo "${version}" -} - -# strip_branch_tag_prefix removes any non-numeric prefix ending with a -# dash (e.g. "msentraid-") from the tag name. We use this to remove the -# branch name prefix from the tag name, but we do not just strip the -# current branch name because we also want to support branching of a new -# branch and use the latest tag from that branch (for example when -# branching of the msentraid branch to test a fix, then that branch -# should still use a valid version). -# $1: tag: the tag name to strip the prefix from. -strip_branch_tag_prefix() { - tag="${1}" - - echo "${tag}" | sed 's/^[^0-9-]*-//' -} - -current_branch=$(git -C "${SNAPCRAFT_PART_SRC}" branch --show-current) - -# Get the highest version tag which is prefixed with the current branch name. -tag=$(git -c "versionsort.suffix=-pre" tag --sort=-v:refname --merged="${current_branch}" | grep "^${current_branch}-" | head -1) - -# If there is no tag prefixed with the current branch name, use the most -# recent tag that does not have a non-numerical prefix (that's the case -# when we're building a snap for testing on a branch that's not -# "msentraid" or "google"). -if [ -z "${tag}" ]; then - tag=$(git -c "versionsort.suffix=-pre" tag --sort=-v:refname --merged="${current_branch}" | grep -E '^[0-9]+' | head -1) -fi - -version="${tag}" -if [ -z "${version}" ]; then - # No tag found, use "notag" as version. - version="notag" -fi -version=$(strip_branch_tag_prefix "${version}") - -# If the highest version tag is on the current commit, use it as is after -# stripping the prefix. -if [ -n "${tag}" ] && [ "$(git describe --tags --exact-match 2>/dev/null)" = "${tag}" ]; then - set_version "${version}" - exit 0 -fi - -# Current commit is not tagged, append commit(s) sha. -version="${version}+$(git -C "${SNAPCRAFT_PART_SRC}" rev-parse --short=7 HEAD)" - -# Main branch will be set as is. -if [ "${current_branch}" = "main" ]; then - set_version "${version}" - exit 0 -fi - -# Get the short version of last commit merged from the main branch. -last_commit_on_main=$(git -C "${SNAPCRAFT_PART_SRC}" merge-base main HEAD) -last_commit_on_main=$(git -C "${SNAPCRAFT_PART_SRC}" rev-parse --short=7 "${last_commit_on_main}") -version="${version}.${last_commit_on_main}" - -# Check if the version is a valid semantic version. -if ! semver check "${version}"; then - echo "Version ${version} is not a valid semantic version." - exit 1 -fi - -set_version "${version}" diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index cae30e8b1c..9e31a6442e 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -54,6 +54,7 @@ parts: plugin: nil build-packages: - git # The script needs Git. - override-build: ./snap/get_version + override-build: | + craftctl set version="$(./snap/version)" after: - semver diff --git a/snap/version b/snap/version new file mode 100755 index 0000000000..e0ecc85982 --- /dev/null +++ b/snap/version @@ -0,0 +1,91 @@ +#!/bin/sh +set -eu + +# This scripts prints the version of the snap based on the git tags and the +# current branch. The version is determined as follows: +# +# If there's a tag prefixed with the current branch name, that tag is +# used and the prefix is stripped. For example: +# * msentraid-0.1.0 -> 0.1.0 +# +# Else, the highest version tag that starts with a number (in contrast +# to a prefix like "msentraid-") is used. For example: +# * 0.1.0 -> 0.1.0 +# +# 1. If current commit is tagged, that tag is used as the version as is. +# 2. If current commit is not tagged, the version is: +# * When on main: + +# * Else: +. +# +# The version is appended with ".dirty" if there are uncommitted changes. + +# strip_branch_tag_prefix removes any non-numeric prefix ending with a +# dash (e.g. "msentraid-") from the tag name. We use this to remove the +# branch name prefix from the tag name, but we do not just strip the +# current branch name because we also want to support branching of a new +# branch and use the latest tag from that branch (for example when +# branching of the msentraid branch to test a fix, then that branch +# should still use a valid version). +# $1: tag: the tag name to strip the prefix from. +strip_branch_tag_prefix() { + tag="${1}" + + echo "${tag}" | sed 's/^[^0-9-]*-//' +} + +get_version() { + current_branch=$(git branch --show-current) + + # Get the highest version tag which is prefixed with the current branch name. + tag=$(git -c "versionsort.suffix=-pre" tag --sort=-v:refname --merged="${current_branch}" | grep "^${current_branch}-" | head -1) + + # If there is no tag prefixed with the current branch name, use the most + # recent tag that does not have a non-numerical prefix (that's the case + # when we're building a snap for testing on a branch that's not + # "msentraid" or "google"). + if [ -z "${tag}" ]; then + tag=$(git -c "versionsort.suffix=-pre" tag --sort=-v:refname --merged="${current_branch}" | grep -E '^[0-9]+' | head -1) + fi + + version="${tag}" + if [ -z "${version}" ]; then + # No tag found, use "notag" as version. + version="notag" + fi + version=$(strip_branch_tag_prefix "${version}") + + # If the highest version tag is on the current commit, use it as is after + # stripping the prefix. + if [ -n "${tag}" ] && [ "$(git describe --tags --exact-match 2>/dev/null)" = "${tag}" ]; then + echo "${version}" + return + fi + + # Current commit is not tagged, append commit(s) sha. + version="${version}+$(git rev-parse --short=7 HEAD)" + + # Main branch will be set as is. + if [ "${current_branch}" = "main" ]; then + echo "${version}" + return + fi + + # Get the short version of last commit merged from the main branch. + last_commit_on_main=$(git merge-base main HEAD) + last_commit_on_main=$(git rev-parse --short=7 "${last_commit_on_main}") + echo "${version}.${last_commit_on_main}" +} + +version=$(get_version) + +if ! "${SEMVER:-semver}" check "${version}" >/dev/null; then + echo "Version ${version} is not a valid semantic version." + exit 1 +fi + +# append ".dirty" if there are uncommitted changes. +if [ -n "$(git status --porcelain)" ]; then + version="${version}.dirty" +fi + +echo "${version}" From 5f6f0b530b5d184906d2ceb427de1f20b6a6be14 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 22 Jul 2025 10:23:30 +0200 Subject: [PATCH 0628/1670] refactor: Split session.authInfo map Use typed fields in the session struct instead of a map of type map[string]any. Benefits: * Compile-time type safety * Better IDE navigation * Avoids multiple use of the term "auth info" for different things (session.authInfo["authInfo"]) * Also allows us to get rid of some tests which tested if the map values have invalid types. --- internal/broker/broker.go | 33 +++++++++++++++++---------------- internal/broker/broker_test.go | 12 ------------ internal/broker/export_test.go | 15 --------------- internal/broker/helper_test.go | 2 +- internal/token/token.go | 14 +++++++------- internal/token/token_test.go | 4 ++-- 6 files changed, 27 insertions(+), 53 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 596416ff4c..dbc4e81598 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -70,12 +70,15 @@ type session struct { oidcServer *oidc.Provider oauth2Config oauth2.Config - authInfo map[string]any isOffline bool userDataDir string passwordPath string tokenPath string + // Data to pass from one request to another. + deviceAuthResponse *oauth2.DeviceAuthResponse + authInfo *token.AuthCachedInfo + isAuthenticating *isAuthenticatedCtx } @@ -155,7 +158,6 @@ func (b *Broker) NewSession(username, lang, mode string) (sessionID, encryptionK lang: lang, mode: mode, - authInfo: make(map[string]any), attemptsPerMode: make(map[string]int), } @@ -398,7 +400,7 @@ func (b *Broker) generateUILayout(session *session, authModeID string) (map[stri if err != nil { return nil, fmt.Errorf("could not generate Device Authentication code layout: %v", err) } - session.authInfo["response"] = response + session.deviceAuthResponse = response label := fmt.Sprintf( "Access %q and use the provided login code", @@ -521,11 +523,11 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au return AuthRetry, errorMessage{Message: "could not decode secret"} } - var authInfo token.AuthCachedInfo + var authInfo *token.AuthCachedInfo switch session.selectedMode { case authmodes.Device, authmodes.DeviceQr: - response, ok := session.authInfo["response"].(*oauth2.DeviceAuthResponse) - if !ok { + response := session.deviceAuthResponse + if response == nil { log.Error(context.Background(), "could not get device auth response") return AuthDenied, errorMessage{Message: "could not get required response"} } @@ -554,7 +556,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au return AuthDenied, errorMessage{Message: "could not get ID token"} } - authInfo = token.NewAuthCachedInfo(t, rawIDToken, b.provider) + authInfo := token.NewAuthCachedInfo(t, rawIDToken, b.provider) authInfo.ProviderMetadata, err = b.provider.GetMetadata(session.oidcServer) if err != nil { @@ -562,7 +564,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au return AuthDenied, errorMessage{Message: "could not get provider metadata"} } - authInfo.UserInfo, err = b.fetchUserInfo(ctx, session, &authInfo) + authInfo.UserInfo, err = b.fetchUserInfo(ctx, session, authInfo) if err != nil { log.Errorf(context.Background(), "could not fetch user info: %s", err) return AuthDenied, errorMessageForDisplay(err, "could not fetch user info") @@ -573,7 +575,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au return AuthDenied, errorMessage{Message: "user not allowed in broker configuration"} } - session.authInfo["auth_info"] = authInfo + session.authInfo = authInfo session.nextAuthModes = []string{authmodes.NewPassword} return AuthNext, nil @@ -598,7 +600,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au // If the session is for changing the password, we don't need to refresh the token and user info (and we don't // want the method call to return an error if refreshing the token or user info fails). if session.mode == sessionmode.ChangePassword || session.mode == sessionmode.ChangePasswordOld { - session.authInfo["auth_info"] = authInfo + session.authInfo = authInfo return AuthNext, nil } @@ -622,7 +624,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au } // Try to refresh the user info - userInfo, err := b.fetchUserInfo(ctx, session, &authInfo) + userInfo, err := b.fetchUserInfo(ctx, session, authInfo) if err != nil && authInfo.UserInfo.Name == "" { // We don't have a valid user info, so we can't proceed. log.Errorf(context.Background(), "could not fetch user info: %s", err) @@ -640,10 +642,9 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au return AuthRetry, errorMessage{Message: "empty secret"} } - var ok bool // This mode must always come after a authentication mode, so it has to have an auth_info. - authInfo, ok = session.authInfo["auth_info"].(token.AuthCachedInfo) - if !ok { + authInfo = session.authInfo + if authInfo == nil { log.Error(context.Background(), "could not get required information") return AuthDenied, errorMessage{Message: "could not get required information"} } @@ -823,7 +824,7 @@ func (b *Broker) updateSession(sessionID string, session session) error { } // refreshToken refreshes the OAuth2 token and returns the updated AuthCachedInfo. -func (b *Broker) refreshToken(ctx context.Context, oauth2Config oauth2.Config, oldToken token.AuthCachedInfo) (token.AuthCachedInfo, error) { +func (b *Broker) refreshToken(ctx context.Context, oauth2Config oauth2.Config, oldToken *token.AuthCachedInfo) (*token.AuthCachedInfo, error) { timeoutCtx, cancel := context.WithTimeout(ctx, maxRequestDuration) defer cancel() // set cached token expiry time to one hour in the past @@ -831,7 +832,7 @@ func (b *Broker) refreshToken(ctx context.Context, oauth2Config oauth2.Config, o oldToken.Token.Expiry = time.Now().Add(-time.Hour) oauthToken, err := oauth2Config.TokenSource(timeoutCtx, oldToken.Token).Token() if err != nil { - return token.AuthCachedInfo{}, err + return nil, err } // Update the raw ID token diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index eb6f039eb0..cf5bc472f8 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -402,7 +402,6 @@ func TestIsAuthenticated(t *testing.T) { firstMode string firstSecret string - firstAuthInfo map[string]any badFirstKey bool getUserInfoFails bool useOldNameForSecretField bool @@ -541,7 +540,6 @@ func TestIsAuthenticated(t *testing.T) { getUserInfoFails: true, }, - "Error_when_mode_is_qrcode_and_response_is_invalid": {firstAuthInfo: map[string]any{"response": "not a valid response"}}, "Error_when_mode_is_qrcode_and_link_expires": { customHandlers: map[string]testutils.EndpointHandler{ "/device_auth": testutils.ExpiryDeviceAuthHandler(), @@ -557,10 +555,6 @@ func TestIsAuthenticated(t *testing.T) { "/token": testutils.HangingHandler(broker.MaxRequestDuration + 1), }, }, - "Error_when_mode_is_link_code_and_response_is_invalid": { - firstMode: authmodes.Device, - firstAuthInfo: map[string]any{"response": "not a valid response"}, - }, "Error_when_mode_is_link_code_and_link_expires": { customHandlers: map[string]testutils.EndpointHandler{ "/device_auth": testutils.ExpiryDeviceAuthHandler(), @@ -684,12 +678,6 @@ func TestIsAuthenticated(t *testing.T) { } updateAuthModes(t, b, sessionID, tc.firstMode) - if tc.firstAuthInfo != nil { - for k, v := range tc.firstAuthInfo { - require.NoError(t, b.SetAuthInfo(sessionID, k, v), "Setup: Failed to set AuthInfo for tests") - } - } - access, data, err := b.IsAuthenticated(sessionID, authData) require.True(t, json.Valid([]byte(data)), "IsAuthenticated returned data must be a valid JSON") diff --git a/internal/broker/export_test.go b/internal/broker/export_test.go index eb06b72baf..5f651aaef3 100644 --- a/internal/broker/export_test.go +++ b/internal/broker/export_test.go @@ -151,21 +151,6 @@ func (b *Broker) SetNextAuthModes(sessionID string, authModes []string) { b.currentSessions[sessionID] = session } -// SetAuthInfo sets the given key and value for the given session.AuthInfo. -func (b *Broker) SetAuthInfo(sessionID, key string, value any) error { - s, err := b.getSession(sessionID) - if err != nil { - return err - } - - s.authInfo[key] = value - if err = b.updateSession(sessionID, s); err != nil { - return err - } - - return nil -} - func (b *Broker) SetAvailableMode(sessionID, mode string) error { s, err := b.getSession(sessionID) if err != nil { diff --git a/internal/broker/helper_test.go b/internal/broker/helper_test.go index 770c6adc50..9381ce7090 100644 --- a/internal/broker/helper_test.go +++ b/internal/broker/helper_test.go @@ -180,7 +180,7 @@ func generateAndStoreCachedInfo(t *testing.T, options tokenOptions, path string) writeTrashToken(t, path) return } - err := token.CacheAuthInfo(path, *tok) + err := token.CacheAuthInfo(path, tok) require.NoError(t, err, "Setup: storing token should not have failed") } diff --git a/internal/token/token.go b/internal/token/token.go index baa2692a84..532d62ffc4 100644 --- a/internal/token/token.go +++ b/internal/token/token.go @@ -23,8 +23,8 @@ type AuthCachedInfo struct { // NewAuthCachedInfo creates a new AuthCachedInfo. It sets the provided token and rawIDToken and the provider-specific // extra fields which should be stored persistently. -func NewAuthCachedInfo(token *oauth2.Token, rawIDToken string, provider providers.Provider) AuthCachedInfo { - return AuthCachedInfo{ +func NewAuthCachedInfo(token *oauth2.Token, rawIDToken string, provider providers.Provider) *AuthCachedInfo { + return &AuthCachedInfo{ Token: token, RawIDToken: rawIDToken, ExtraFields: provider.GetExtraFields(token), @@ -32,7 +32,7 @@ func NewAuthCachedInfo(token *oauth2.Token, rawIDToken string, provider provider } // CacheAuthInfo saves the token to the given path. -func CacheAuthInfo(path string, token AuthCachedInfo) (err error) { +func CacheAuthInfo(path string, token *AuthCachedInfo) (err error) { jsonData, err := json.Marshal(token) if err != nil { return fmt.Errorf("could not marshal token: %v", err) @@ -51,15 +51,15 @@ func CacheAuthInfo(path string, token AuthCachedInfo) (err error) { } // LoadAuthInfo reads the token from the given path. -func LoadAuthInfo(path string) (AuthCachedInfo, error) { +func LoadAuthInfo(path string) (*AuthCachedInfo, error) { jsonData, err := os.ReadFile(path) if err != nil { - return AuthCachedInfo{}, fmt.Errorf("could not read token: %v", err) + return nil, fmt.Errorf("could not read token: %v", err) } var cachedInfo AuthCachedInfo if err := json.Unmarshal(jsonData, &cachedInfo); err != nil { - return AuthCachedInfo{}, fmt.Errorf("could not unmarshal token: %v", err) + return nil, fmt.Errorf("could not unmarshal token: %v", err) } // Set the extra fields of the token. @@ -67,5 +67,5 @@ func LoadAuthInfo(path string) (AuthCachedInfo, error) { cachedInfo.Token = cachedInfo.Token.WithExtra(cachedInfo.ExtraFields) } - return cachedInfo, nil + return &cachedInfo, nil } diff --git a/internal/token/token_test.go b/internal/token/token_test.go index a5318fc72a..70bc9a0df0 100644 --- a/internal/token/token_test.go +++ b/internal/token/token_test.go @@ -11,7 +11,7 @@ import ( "golang.org/x/oauth2" ) -var testToken = token.AuthCachedInfo{ +var testToken = &token.AuthCachedInfo{ Token: &oauth2.Token{ AccessToken: "accesstoken", RefreshToken: "refreshtoken", @@ -86,7 +86,7 @@ func TestLoadAuthInfo(t *testing.T) { t.Parallel() tests := map[string]struct { - expectedRet token.AuthCachedInfo + expectedRet *token.AuthCachedInfo fileExists bool invalidJSON bool From 0288d0cbaf547179b08d7f5809e827358c3978d4 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 22 Jul 2025 11:39:27 +0200 Subject: [PATCH 0629/1670] Explain better why we store auth info in the session --- internal/broker/broker.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index dbc4e81598..2731715bbb 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -575,6 +575,8 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au return AuthDenied, errorMessage{Message: "user not allowed in broker configuration"} } + // Store the auth info in the session so that we can use it when handling the + // next IsAuthenticated call for the new password mode. session.authInfo = authInfo session.nextAuthModes = []string{authmodes.NewPassword} @@ -600,6 +602,8 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au // If the session is for changing the password, we don't need to refresh the token and user info (and we don't // want the method call to return an error if refreshing the token or user info fails). if session.mode == sessionmode.ChangePassword || session.mode == sessionmode.ChangePasswordOld { + // Store the auth info in the session so that we can use it when handling the + // next IsAuthenticated call for the new password mode. session.authInfo = authInfo return AuthNext, nil } @@ -642,7 +646,8 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au return AuthRetry, errorMessage{Message: "empty secret"} } - // This mode must always come after a authentication mode, so it has to have an auth_info. + // This mode must always come after an authentication mode, so we should have auth info from the previous mode + // stored in the session. authInfo = session.authInfo if authInfo == nil { log.Error(context.Background(), "could not get required information") From 960e5e74e47bb95e93564caed1ddc88b15863835 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 23 Jul 2025 11:16:24 +0200 Subject: [PATCH 0630/1670] Log version on start Useful for debugging. --- cmd/authd-oidc/daemon/daemon.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/authd-oidc/daemon/daemon.go b/cmd/authd-oidc/daemon/daemon.go index e721a7a461..0b1c7bae0f 100644 --- a/cmd/authd-oidc/daemon/daemon.go +++ b/cmd/authd-oidc/daemon/daemon.go @@ -11,6 +11,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/ubuntu/authd-oidc-brokers/internal/broker" + "github.com/ubuntu/authd-oidc-brokers/internal/consts" "github.com/ubuntu/authd-oidc-brokers/internal/daemon" "github.com/ubuntu/authd-oidc-brokers/internal/dbusservice" log "github.com/ubuntu/authd/log" @@ -83,6 +84,8 @@ func New(name string) *App { } setVerboseMode(a.config.Verbosity) + + log.Infof(context.Background(), "Version: %s", consts.Version) log.Debug(context.Background(), "Debug mode is enabled") return nil From 4060ac1528fe6b3f0dcb4812aa0914e01664c624 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 23 Jul 2025 11:21:31 +0200 Subject: [PATCH 0631/1670] Fix comment --- cmd/authd-oidc/daemon/daemon.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/authd-oidc/daemon/daemon.go b/cmd/authd-oidc/daemon/daemon.go index 0b1c7bae0f..936239068b 100644 --- a/cmd/authd-oidc/daemon/daemon.go +++ b/cmd/authd-oidc/daemon/daemon.go @@ -53,7 +53,7 @@ func New(name string) *App { // First thing, initialize the log handler log.InitJournalHandler(false) - // Command parsing has been successful. Returns to not print usage anymore. + // Command parsing has been successful, so don't print the usage message on errors anymore. a.rootCmd.SilenceUsage = true dataDir := filepath.Join("/var", "lib", name) From e0182a31c730d166919a99640d23a3e2ad9fce0b Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 23 Jul 2025 11:22:54 +0200 Subject: [PATCH 0632/1670] Hide confusing log message viper.ConfigFileUsed() is usually the empty string, resulting the log message Using configuration file: --- cmd/authd-oidc/daemon/config.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/authd-oidc/daemon/config.go b/cmd/authd-oidc/daemon/config.go index 3663367d9d..13a255b45c 100644 --- a/cmd/authd-oidc/daemon/config.go +++ b/cmd/authd-oidc/daemon/config.go @@ -51,7 +51,9 @@ func initViperConfig(name string, cmd *cobra.Command, vip *viper.Viper) (err err if err != nil && !errors.As(err, &configNotFoundErr) { return fmt.Errorf("invalid configuration file: %w", err) } - log.Infof(context.Background(), "Using configuration file: %v", vip.ConfigFileUsed()) + if vip.ConfigFileUsed() != "" { + log.Infof(context.Background(), "Using configuration file: %v", vip.ConfigFileUsed()) + } // Handle environment. vip.SetEnvPrefix(name) From 6e5750fd5272991adedd82a2e72d5717b98d42dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 09:44:31 +0000 Subject: [PATCH 0633/1670] deps(go): bump the minor-updates group across 1 directory with 2 updates Bumps the minor-updates group with 2 updates in the / directory: [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) and [google.golang.org/grpc](https://github.com/grpc/grpc-go). Updates `github.com/mattn/go-sqlite3` from 1.14.28 to 1.14.30 - [Release notes](https://github.com/mattn/go-sqlite3/releases) - [Commits](https://github.com/mattn/go-sqlite3/compare/v1.14.28...v1.14.30) Updates `google.golang.org/grpc` from 1.74.0 to 1.74.2 - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.74.0...v1.74.2) --- updated-dependencies: - dependency-name: github.com/mattn/go-sqlite3 dependency-version: 1.14.30 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-updates - dependency-name: google.golang.org/grpc dependency-version: 1.74.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-updates ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index ca109e22b1..2a002344c8 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/coreos/go-systemd/v22 v22.5.0 github.com/godbus/dbus/v5 v5.1.0 github.com/google/uuid v1.6.0 - github.com/mattn/go-sqlite3 v1.14.28 + github.com/mattn/go-sqlite3 v1.14.30 github.com/msteinert/pam/v2 v2.0.0 github.com/muesli/termenv v0.16.0 github.com/otiai10/copy v1.14.1 @@ -26,7 +26,7 @@ require ( golang.org/x/exp v0.0.0-20230905200255-921286631fa9 golang.org/x/sys v0.34.0 golang.org/x/term v0.33.0 - google.golang.org/grpc v1.74.0 + google.golang.org/grpc v1.74.2 google.golang.org/protobuf v1.36.6 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index 01fc4aca4a..9dc66c48dc 100644 --- a/go.sum +++ b/go.sum @@ -62,8 +62,8 @@ github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2J github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= -github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.30 h1:bVreufq3EAIG1Quvws73du3/QgdeZ3myglJlrzSYYCY= +github.com/mattn/go-sqlite3 v1.14.30/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= @@ -151,8 +151,8 @@ golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.74.0 h1:sxRSkyLxlceWQiqDofxDot3d4u7DyoHPc7SBXMj8gGY= -google.golang.org/grpc v1.74.0/go.mod h1:NZUaK8dAMUfzhK6uxZ+9511LtOrk73UGWOFoNvz7z+s= +google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= +google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From f0142a65dc5d439d1395be990c3d2977414614ca Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Fri, 1 Aug 2025 08:41:56 -0400 Subject: [PATCH 0634/1670] Refactor parseConfigFile to support fuzz tests The code used to care about filepaths and let the ini package do the rest. This would result in a quite ineffective fuzz test, since we would need to keep doing disk writing operations which are very costly. In order to improve this, we should read the files ourselves and send only the read content to the ini.Load function. This allows us to fuzz with more speed and effectiveness without affecting the current code behavior too much. --- internal/broker/broker.go | 2 +- internal/broker/config.go | 34 +++++++++++++++++++++++++--------- internal/broker/config_test.go | 4 ++-- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 2731715bbb..5c0d9058c1 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -99,7 +99,7 @@ func New(cfg Config, args ...Option) (b *Broker, err error) { p := providers.CurrentProvider() if cfg.ConfigFile != "" { - cfg.userConfig, err = parseConfigFile(cfg.ConfigFile, p) + cfg.userConfig, err = parseConfigFromPath(cfg.ConfigFile, p) if err != nil { return nil, fmt.Errorf("could not parse config file '%s': %v", cfg.ConfigFile, err) } diff --git a/internal/broker/config.go b/internal/broker/config.go index 870f944c50..9a114f7240 100644 --- a/internal/broker/config.go +++ b/internal/broker/config.go @@ -93,7 +93,7 @@ func GetDropInDir(cfgPath string) string { return cfgPath + ".d" } -func getDropInFiles(cfgPath string) ([]any, error) { +func readDropInFiles(cfgPath string) ([]any, error) { // Check if a .d directory exists and return the paths to the files in it. dropInDir := GetDropInDir(cfgPath) files, err := os.ReadDir(dropInDir) @@ -110,7 +110,12 @@ func getDropInFiles(cfgPath string) ([]any, error) { if file.IsDir() { continue } - dropInFiles = append(dropInFiles, filepath.Join(dropInDir, file.Name())) + + dropInFile, err := os.ReadFile(filepath.Join(dropInDir, file.Name())) + if err != nil { + return nil, fmt.Errorf("could not read drop-in file %q: %v", file.Name(), err) + } + dropInFiles = append(dropInFiles, dropInFile) } return dropInFiles, nil @@ -170,16 +175,27 @@ func (uc *userConfig) populateUsersConfig(users *ini.Section) { uc.ownerExtraGroups = users.Key(ownerExtraGroupsKey).Strings(",") } -// parseConfigFile parses the config file and returns a map with the configuration keys and values. -func parseConfigFile(cfgPath string, p provider) (userConfig, error) { - cfg := userConfig{provider: p, ownerMutex: &sync.RWMutex{}} +// parseConfigFromPath parses the config file and returns a map with the configuration keys and values. +func parseConfigFromPath(cfgPath string, p provider) (userConfig, error) { + cfgFile, err := os.ReadFile(cfgPath) + if err != nil { + return userConfig{}, fmt.Errorf("could not open config file %q: %v", cfgPath, err) + } - dropInFiles, err := getDropInFiles(cfgPath) + dropInFiles, err := readDropInFiles(cfgPath) if err != nil { - return cfg, err + return userConfig{}, err } - iniCfg, err := ini.Load(cfgPath, dropInFiles...) + return parseConfig(cfgFile, dropInFiles, p) +} + +// parseConfig parses the config file and returns a userConfig struct with the configuration keys and values. +// It also checks if the keys contain any placeholders and returns an error if they do. +func parseConfig(cfgContent []byte, dropInContent []any, p provider) (userConfig, error) { + cfg := userConfig{provider: p, ownerMutex: &sync.RWMutex{}} + + iniCfg, err := ini.Load(cfgContent, dropInContent...) if err != nil { return cfg, err } @@ -193,7 +209,7 @@ func parseConfigFile(cfgPath string, p provider) (userConfig, error) { } } if err != nil { - return cfg, fmt.Errorf("config file has invalid values, did you edit the file %q?\n%w", cfgPath, err) + return cfg, fmt.Errorf("config file has invalid values, did you edit the config file?\n%w", err) } oidc := iniCfg.Section(oidcSection) diff --git a/internal/broker/config_test.go b/internal/broker/config_test.go index 5b3ce660e4..029ab5aa97 100644 --- a/internal/broker/config_test.go +++ b/internal/broker/config_test.go @@ -134,7 +134,7 @@ func TestParseConfig(t *testing.T) { require.NoError(t, err, "Setup: Failed to make drop-in file unreadable") } - cfg, err := parseConfigFile(confPath, p) + cfg, err := parseConfigFromPath(confPath, p) if tc.wantErr { require.Error(t, err) return @@ -307,7 +307,7 @@ func TestParseUserConfig(t *testing.T) { err = os.Mkdir(dropInDir, 0700) require.NoError(t, err, "Setup: Failed to create drop-in directory") - cfg, err := parseConfigFile(confPath, p) + cfg, err := parseConfigFromPath(confPath, p) // convert the allowed users array to a map allowedUsersMap := map[string]struct{}{} From c29c035d61d8d9c341802b2736d142542c34c5f1 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Thu, 31 Jul 2025 10:26:34 -0400 Subject: [PATCH 0635/1670] Add fuzz test for parseConfig func This is one of the points of free and "uncontrolled" user input. To ensure our code is robust and safe, fuzz testing this function is a nice improvement to make sure it's stable. --- internal/broker/config_test.go | 7 +++++++ internal/broker/testdata/fuzz/FuzzParseConfig/00 | 2 ++ internal/broker/testdata/fuzz/FuzzParseConfig/01 | 2 ++ internal/broker/testdata/fuzz/FuzzParseConfig/02 | 2 ++ ...3ce3e3c893c1c1225351f4a3b9d700b1836c1087afdc595cf44fb54 | 2 ++ .../broker/testdata/fuzz/FuzzParseConfig/caf81e9797b19c76 | 2 ++ 6 files changed, 17 insertions(+) create mode 100644 internal/broker/testdata/fuzz/FuzzParseConfig/00 create mode 100644 internal/broker/testdata/fuzz/FuzzParseConfig/01 create mode 100644 internal/broker/testdata/fuzz/FuzzParseConfig/02 create mode 100644 internal/broker/testdata/fuzz/FuzzParseConfig/93a65ad473ce3e3c893c1c1225351f4a3b9d700b1836c1087afdc595cf44fb54 create mode 100644 internal/broker/testdata/fuzz/FuzzParseConfig/caf81e9797b19c76 diff --git a/internal/broker/config_test.go b/internal/broker/config_test.go index 029ab5aa97..8fa12cd0a9 100644 --- a/internal/broker/config_test.go +++ b/internal/broker/config_test.go @@ -353,3 +353,10 @@ func TestRegisterOwner(t *testing.T) { golden.CheckOrUpdateFileTree(t, outDir) } + +func FuzzParseConfig(f *testing.F) { + p := &testutils.MockProvider{} + f.Fuzz(func(t *testing.T, a []byte) { + _, _ = parseConfig(a, nil, p) + }) +} diff --git a/internal/broker/testdata/fuzz/FuzzParseConfig/00 b/internal/broker/testdata/fuzz/FuzzParseConfig/00 new file mode 100644 index 0000000000..b349e97638 --- /dev/null +++ b/internal/broker/testdata/fuzz/FuzzParseConfig/00 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("clientID = client_id\nclientSecret = client_secret\nissuerURL = https://issuer.url.com\nhomeBaseDir = /home\nallowedSSHSuffixes = []\n") diff --git a/internal/broker/testdata/fuzz/FuzzParseConfig/01 b/internal/broker/testdata/fuzz/FuzzParseConfig/01 new file mode 100644 index 0000000000..ee3e7da3b2 --- /dev/null +++ b/internal/broker/testdata/fuzz/FuzzParseConfig/01 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("clientID = \nclientSecret = client_secret\nissuerURL = https://issuer.url.com\nhomeBaseDir = /home\nallowedSSHSuffixes = []\n") diff --git a/internal/broker/testdata/fuzz/FuzzParseConfig/02 b/internal/broker/testdata/fuzz/FuzzParseConfig/02 new file mode 100644 index 0000000000..34393fab24 --- /dev/null +++ b/internal/broker/testdata/fuzz/FuzzParseConfig/02 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("[oidc]\nforce_provider_authentication = nope\n") diff --git a/internal/broker/testdata/fuzz/FuzzParseConfig/93a65ad473ce3e3c893c1c1225351f4a3b9d700b1836c1087afdc595cf44fb54 b/internal/broker/testdata/fuzz/FuzzParseConfig/93a65ad473ce3e3c893c1c1225351f4a3b9d700b1836c1087afdc595cf44fb54 new file mode 100644 index 0000000000..c61979ae4c --- /dev/null +++ b/internal/broker/testdata/fuzz/FuzzParseConfig/93a65ad473ce3e3c893c1c1225351f4a3b9d700b1836c1087afdc595cf44fb54 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("[oidc]\nissuer = \nclient_id = \n\n## Depending on the identity provider, you may need to provide a\n## client secret to authenticate with the provider.\n#client_secret = \n\n## Force remote authentication with the identity provider during login,\n## even if a local method (e.g. local password) is used.\n## This works by forcing a token refresh during login, which fails if the\n## user does not have the necessary permissions in the identity provider.\n##\n## If set to false (the default), remote authentication with the identity\n## provider only happens if there is a working internet connection and\n## the provider is reachable during login.\n##\n## Important: Enabling this option prevents authd users from logging in\n## if the identity provider is unreachable (e.g. due to network issues).\n#force_provider_authentication = false\n\n[users]\n## The directory where the home directories of new users are created.\n## Existing users will keep their current home directory.\n## The home directories are created in the format /\n#home_base_dir = /home\n\n## By default, SSH only allows logins from users that already exist on the\n## system.\n## New authd users (who have never logged in before) are *not* allowed to log\n## in for the first time via SSH unless this option is configured.\n##\n## If configured, only users with a suffix in this list are allowed to\n## authenticate for the first time directly through SSH.\n## Note that this does not affect users that already authenticated for\n## the first time and already exist on the system.\n##\n## Suffixes must be comma-separated (e.g., '@example.com,@example.org').\n## To allow all suffixes, use a single asterisk ('*').\n##\n## Example:\n## ssh_allowed_suffixes_first_auth = @example.com,@anotherexample.org\n##\n## Example (allow all):\n## ssh_allowed_suffixes_first_auth = *\n##\n#ssh_allowed_suffixes_first_auth =\n\n## 'allowed_users' specifies the users who are permitted to log in after\n## successfully authenticating with the identity provider.\n## Values are separated by commas. Supported values:\n## - 'OWNER': Grants access to the user specified in the 'owner' option\n## (see below). This is the default.\n## - 'ALL': Grants access to all users who successfully authenticate\n## with the identity provider.\n## - : Grants access to specific additional users\n## (e.g. user1@example.com).\n## Example: allowed_users = OWNER,user1@example.com,admin@example.com\n#allowed_users = OWNER\n\n## 'owner' specifies the user assigned the owner role. This user is\n## permitted to log in if 'OWNER' is included in the 'allowed_users'\n## option.\n##\n## If this option is left unset, the first user to successfully log in\n## via this broker will automatically be assigned the owner role. A\n## drop-in configuration file will be created in broker.conf.d/ to set\n## the 'owner' option.\n##\n## To disable automatic assignment, you can either:\n## 1. Explicitly set this option to an empty value (e.g. owner = \"\")\n## 2. Remove 'OWNER' from the 'allowed_users' option\n##\n## Example: owner = user2@example.com\n#owner =\n\n## A comma-separated list of local groups which authd users will be\n## added to upon login.\n## Example: extra_groups = users\n#extra_groups =\n\n## Like 'extra_groups', but only the user assigned the owner role\n## (see 'owner' option) will be added to these groups.\n## Example: owner_extra_groups = sudo,lpadmin\n#owner_extra_groups =\n") \ No newline at end of file diff --git a/internal/broker/testdata/fuzz/FuzzParseConfig/caf81e9797b19c76 b/internal/broker/testdata/fuzz/FuzzParseConfig/caf81e9797b19c76 new file mode 100644 index 0000000000..67322c7048 --- /dev/null +++ b/internal/broker/testdata/fuzz/FuzzParseConfig/caf81e9797b19c76 @@ -0,0 +1,2 @@ +go test fuzz v1 +[]byte("") From 71ad8f89424a940524cd7ee0b7fac5fa041f84a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 11:36:19 +0000 Subject: [PATCH 0636/1670] deps(go): bump github.com/Azure/azure-sdk-for-go/sdk/azcore Bumps [github.com/Azure/azure-sdk-for-go/sdk/azcore](https://github.com/Azure/azure-sdk-for-go) from 1.18.1 to 1.18.2. - [Release notes](https://github.com/Azure/azure-sdk-for-go/releases) - [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/go-mgmt-sdk-release-guideline.md) - [Commits](https://github.com/Azure/azure-sdk-for-go/compare/sdk/azcore/v1.18.1...sdk/azcore/v1.18.2) --- updated-dependencies: - dependency-name: github.com/Azure/azure-sdk-for-go/sdk/azcore dependency-version: 1.18.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 8c79c84ef6..ded7ebef22 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.24.0 toolchain go1.24.4 require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2 github.com/coreos/go-oidc/v3 v3.14.1 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/go-jose/go-jose/v4 v4.1.1 @@ -30,7 +30,7 @@ require ( ) require ( - github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect @@ -64,7 +64,7 @@ require ( go.opentelemetry.io/otel/metric v1.35.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.41.0 // indirect + golang.org/x/net v0.42.0 // indirect golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.34.0 // indirect golang.org/x/text v0.27.0 // indirect diff --git a/go.sum b/go.sum index 779e58bf6e..bf744a0f8a 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 h1:Wc1ml6QlJs2BHQ/9Bqu1jiyggbsSjramq2oUmp5WeIo= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.0 h1:Bg8m3nq/X1DeePkAbCfb6ml6F3F0IunEhE8TMh+lY48= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.0/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2 h1:Hr5FTipp7SL07o2FvoVOX9HRiRH3CR3Mj8pxqCcdD5A= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2/go.mod h1:QyVsSSN64v5TGltphKLQ2sQxe4OBQg0J1eKRcVBnfgE= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk= github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= @@ -130,8 +130,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 09432d11d379812e75ce850ade78fceb4275d353 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 11:36:44 +0000 Subject: [PATCH 0637/1670] deps(go): bump github.com/golang-jwt/jwt/v5 from 5.2.3 to 5.3.0 Bumps [github.com/golang-jwt/jwt/v5](https://github.com/golang-jwt/jwt) from 5.2.3 to 5.3.0. - [Release notes](https://github.com/golang-jwt/jwt/releases) - [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md) - [Commits](https://github.com/golang-jwt/jwt/compare/v5.2.3...v5.3.0) --- updated-dependencies: - dependency-name: github.com/golang-jwt/jwt/v5 dependency-version: 5.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8c79c84ef6..a727b53f5f 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/go-jose/go-jose/v4 v4.1.1 github.com/godbus/dbus/v5 v5.1.0 - github.com/golang-jwt/jwt/v5 v5.2.3 + github.com/golang-jwt/jwt/v5 v5.3.0 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible github.com/microsoftgraph/msgraph-sdk-go v1.79.0 diff --git a/go.sum b/go.sum index 779e58bf6e..d963033032 100644 --- a/go.sum +++ b/go.sum @@ -29,8 +29,8 @@ github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlnd github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/golang-jwt/jwt/v5 v5.2.3 h1:kkGXqQOBSDDWRhWNXTFpqGSCMyh/PLnqUvMGJPDJDs0= -github.com/golang-jwt/jwt/v5 v5.2.3/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= From bb18ae4f84da3f4926ea8286e2b50ff1affc04f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 09:49:30 +0000 Subject: [PATCH 0638/1670] deps(ci): bump actions/download-artifact in the gh-actions group Bumps the gh-actions group with 1 update: [actions/download-artifact](https://github.com/actions/download-artifact). Updates `actions/download-artifact` from 4 to 5 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: gh-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/build-deb.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-deb.yaml b/.github/workflows/build-deb.yaml index ad25858fa3..c9264cc67b 100644 --- a/.github/workflows/build-deb.yaml +++ b/.github/workflows/build-deb.yaml @@ -155,7 +155,7 @@ jobs: done - name: Download artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: run-id: ${{ needs.build-deb-package.outputs.run-id }} merge-multiple: true @@ -260,7 +260,7 @@ jobs: done - name: Download artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v5 with: run-id: ${{ needs.build-deb-package.outputs.run-id }} merge-multiple: true From d65798c780f4f3db986726c88c50b5cf18efd643 Mon Sep 17 00:00:00 2001 From: denisonbarbosa Date: Tue, 5 Aug 2025 11:16:48 -0400 Subject: [PATCH 0639/1670] Fix for changing local password After #325, there was a regression where we stopped returning the NewPassword authentication mode as a next step for passwd sessions (to change the local password once it's already defined). This fixes that by returning the expected next authentication mode (i.e. NewPassword). --- internal/broker/broker.go | 1 + internal/broker/broker_test.go | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 2731715bbb..ca15be8233 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -605,6 +605,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au // Store the auth info in the session so that we can use it when handling the // next IsAuthenticated call for the new password mode. session.authInfo = authInfo + session.nextAuthModes = []string{authmodes.NewPassword} return AuthNext, nil } diff --git a/internal/broker/broker_test.go b/internal/broker/broker_test.go index cf5bc472f8..dfa7542ab1 100644 --- a/internal/broker/broker_test.go +++ b/internal/broker/broker_test.go @@ -472,10 +472,11 @@ func TestIsAuthenticated(t *testing.T) { useOldNameForSecretField: true, }, "Authenticating_to_change_password_still_allowed_if_fetching_user_info_fails": { - sessionMode: sessionmode.ChangePassword, - firstMode: authmodes.Password, - token: &tokenOptions{noUserInfo: true}, - getUserInfoFails: true, + sessionMode: sessionmode.ChangePassword, + firstMode: authmodes.Password, + wantNextAuthModes: []string{authmodes.NewPassword}, + token: &tokenOptions{noUserInfo: true}, + getUserInfoFails: true, }, "Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode": { firstMode: authmodes.Password, From ba577015a1169584f7d651287ab547c6239b58e1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 09:26:31 +0000 Subject: [PATCH 0640/1670] deps(go-tools): bump golang.org/x/mod from 0.26.0 to 0.27.0 in /tools Bumps [golang.org/x/mod](https://github.com/golang/mod) from 0.26.0 to 0.27.0. - [Commits](https://github.com/golang/mod/compare/v0.26.0...v0.27.0) --- updated-dependencies: - dependency-name: golang.org/x/mod dependency-version: 0.27.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- tools/go.mod | 10 ++++++---- tools/go.sum | 24 ++++++++++++++---------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index 604c2ed69c..858a40a238 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -6,7 +6,7 @@ toolchain go1.24.1 require ( github.com/golangci/golangci-lint v1.64.8 - golang.org/x/mod v0.26.0 + golang.org/x/mod v0.27.0 ) require ( @@ -182,10 +182,12 @@ require ( go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect - golang.org/x/sync v0.15.0 // indirect - golang.org/x/sys v0.33.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.34.0 // indirect golang.org/x/text v0.22.0 // indirect - golang.org/x/tools v0.34.0 // indirect + golang.org/x/tools v0.35.0 // indirect + golang.org/x/tools/go/expect v0.1.1-deprecated // indirect + golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect google.golang.org/protobuf v1.36.5 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/tools/go.sum b/tools/go.sum index 5a8fca205a..bec43217a8 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -653,8 +653,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= -golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -695,8 +695,8 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= 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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -718,8 +718,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -772,8 +772,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -855,8 +855,12 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= -golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= -golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= +golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= +golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= +golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 22fe5ff0d6e0d8ea8713e90c2c7e904c947a550e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 09:30:31 +0000 Subject: [PATCH 0641/1670] deps(go): bump github.com/microsoftgraph/msgraph-sdk-go Bumps [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) from 1.79.0 to 1.82.0. - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.79.0...v1.82.0) --- updated-dependencies: - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-version: 1.82.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 8c79c84ef6..a3415aad85 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.24.0 toolchain go1.24.4 require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2 github.com/coreos/go-oidc/v3 v3.14.1 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/go-jose/go-jose/v4 v4.1.1 @@ -13,7 +13,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.3 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.79.0 + github.com/microsoftgraph/msgraph-sdk-go v1.82.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 @@ -30,7 +30,7 @@ require ( ) require ( - github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect @@ -42,7 +42,7 @@ require ( github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/microsoft/kiota-abstractions-go v1.9.2 // indirect + github.com/microsoft/kiota-abstractions-go v1.9.3 // indirect github.com/microsoft/kiota-authentication-azure-go v1.3.0 // indirect github.com/microsoft/kiota-http-go v1.5.2 // indirect github.com/microsoft/kiota-serialization-form-go v1.1.2 // indirect @@ -64,7 +64,7 @@ require ( go.opentelemetry.io/otel/metric v1.35.0 // indirect go.opentelemetry.io/otel/trace v1.35.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.41.0 // indirect + golang.org/x/net v0.42.0 // indirect golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.34.0 // indirect golang.org/x/text v0.27.0 // indirect diff --git a/go.sum b/go.sum index 779e58bf6e..9ded1d67ef 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 h1:Wc1ml6QlJs2BHQ/9Bqu1jiyggbsSjramq2oUmp5WeIo= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.0 h1:Bg8m3nq/X1DeePkAbCfb6ml6F3F0IunEhE8TMh+lY48= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.0/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2 h1:Hr5FTipp7SL07o2FvoVOX9HRiRH3CR3Mj8pxqCcdD5A= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2/go.mod h1:QyVsSSN64v5TGltphKLQ2sQxe4OBQg0J1eKRcVBnfgE= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk= github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= @@ -51,8 +51,8 @@ github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHP github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/microsoft/kiota-abstractions-go v1.9.2 h1:3U5VgN2YGe3lsu1pyuS0t5jxv1llxX2ophwX8ewE6wQ= -github.com/microsoft/kiota-abstractions-go v1.9.2/go.mod h1:f06pl3qSyvUHEfVNkiRpXPkafx7khZqQEb71hN/pmuU= +github.com/microsoft/kiota-abstractions-go v1.9.3 h1:cqhbqro+VynJ7kObmo7850h3WN2SbvoyhypPn8uJ1SE= +github.com/microsoft/kiota-abstractions-go v1.9.3/go.mod h1:f06pl3qSyvUHEfVNkiRpXPkafx7khZqQEb71hN/pmuU= github.com/microsoft/kiota-authentication-azure-go v1.3.0 h1:PWH6PgtzhJjnmvR6N1CFjriwX09Kv7S5K3vL6VbPVrg= github.com/microsoft/kiota-authentication-azure-go v1.3.0/go.mod h1:l/MPGUVvD7xfQ+MYSdZaFPv0CsLDqgSOp8mXwVgArIs= github.com/microsoft/kiota-http-go v1.5.2 h1:xqvo4ssWwSvCJw2yuRocKFTxm3Y1iN+a4rrhuTYtBWg= @@ -65,8 +65,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.1.2 h1:1pUyA1QgIeKslQwb github.com/microsoft/kiota-serialization-multipart-go v1.1.2/go.mod h1:j2K7ZyYErloDu7Kuuk993DsvfoP7LPWvAo7rfDpdPio= github.com/microsoft/kiota-serialization-text-go v1.1.2 h1:7OfKFlzdjpPygca/+OtqafkEqCWR7+94efUFGC28cLw= github.com/microsoft/kiota-serialization-text-go v1.1.2/go.mod h1:QNTcswkBPFY3QVBFmzfk00UMNViKQtV0AQKCrRw5ibM= -github.com/microsoftgraph/msgraph-sdk-go v1.79.0 h1:Pz6xWAQ097DP2/SKm4xtnSAHZSHbQMkSVLzNfFrTUEs= -github.com/microsoftgraph/msgraph-sdk-go v1.79.0/go.mod h1:w+NAJ1j3fyxb2An9NWB1CsY+KQMcgBbcrXXA/cNtZ3c= +github.com/microsoftgraph/msgraph-sdk-go v1.82.0 h1:A+MxDbY3TFAjrUdbUH6YJmxSwApuykVToXWLO+L+sGw= +github.com/microsoftgraph/msgraph-sdk-go v1.82.0/go.mod h1:vZjQkQLX2vma7uMxvFHjSmy1edTOMkkWn6DhzVR55m8= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 h1:5jCUSosTKaINzPPQXsz7wsHWwknyBmJSu8+ZWxx3kdQ= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2/go.mod h1:iD75MK3LX8EuwjDYCmh0hkojKXK6VKME33u4daCo3cE= github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= @@ -130,8 +130,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 8f14bab555b0f87013e4f4d7c0fc21aeb7633acc Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 11 Aug 2025 12:45:49 +0200 Subject: [PATCH 0642/1670] dependabot: Group minor updates Same as in the authd repo, to reduce the manual effort of merging and rebasing dependabot PRs. --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 01e37164fa..ae197a4903 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -20,6 +20,9 @@ updates: directory: "/" # Location of package manifests schedule: interval: "weekly" + groups: + minor-updates: + update-types: [ "minor", "patch" ] commit-message: prefix: "deps(go)" @@ -27,5 +30,8 @@ updates: directory: "/tools" schedule: interval: "weekly" + groups: + minor-updates: + update-types: [ "minor", "patch" ] commit-message: prefix: "deps(go-tools)" From ce752ee7c2642fd0743b3fcea04bc38c492484e8 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 11 Aug 2025 12:38:52 +0200 Subject: [PATCH 0643/1670] deps(go): bump github.com/go-viper/mapstructure/v2 from 2.2.1 to 2.4.0 govulncheck reports the following vulnerability: Vulnerability #1: GO-2025-3787 May leak sensitive information in logs when processing malformed data in github.com/go-viper/mapstructure More info: https://pkg.go.dev/vuln/GO-2025-3787 Module: github.com/go-viper/mapstructure/v2 Found in: github.com/go-viper/mapstructure/v2@v2.2.1 Fixed in: github.com/go-viper/mapstructure/v2@v2.3.0 Dependabot doesn't create a PR which updates that dependency because it's an indirect dependency. --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8c79c84ef6..bcd6188fc5 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect github.com/leonelquinteros/gotext v1.5.3-0.20230829162019-37f474cfb069 // indirect diff --git a/go.sum b/go.sum index 779e58bf6e..08571c546f 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,8 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= From 207f50de72afaa80e902ea2313b2a5df892abf95 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 11:31:16 +0000 Subject: [PATCH 0644/1670] deps(go): bump github.com/go-jose/go-jose/v4 from 4.1.1 to 4.1.2 Bumps [github.com/go-jose/go-jose/v4](https://github.com/go-jose/go-jose) from 4.1.1 to 4.1.2. - [Release notes](https://github.com/go-jose/go-jose/releases) - [Commits](https://github.com/go-jose/go-jose/compare/v4.1.1...v4.1.2) --- updated-dependencies: - dependency-name: github.com/go-jose/go-jose/v4 dependency-version: 4.1.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bcd6188fc5..8c74b508b3 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 github.com/coreos/go-oidc/v3 v3.14.1 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf - github.com/go-jose/go-jose/v4 v4.1.1 + github.com/go-jose/go-jose/v4 v4.1.2 github.com/godbus/dbus/v5 v5.1.0 github.com/golang-jwt/jwt/v5 v5.2.3 github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum index 08571c546f..708439b51b 100644 --- a/go.sum +++ b/go.sum @@ -17,8 +17,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= -github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= +github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI= +github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= From d126f14932cce5fe06ab13f4b755b1c1a933e0ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 11:59:20 +0000 Subject: [PATCH 0645/1670] deps(go): bump github.com/coreos/go-oidc/v3 from 3.14.1 to 3.15.0 Bumps [github.com/coreos/go-oidc/v3](https://github.com/coreos/go-oidc) from 3.14.1 to 3.15.0. - [Release notes](https://github.com/coreos/go-oidc/releases) - [Commits](https://github.com/coreos/go-oidc/compare/v3.14.1...v3.15.0) --- updated-dependencies: - dependency-name: github.com/coreos/go-oidc/v3 dependency-version: 3.15.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 29f7332c3e..69e307f2d5 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.24.4 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2 - github.com/coreos/go-oidc/v3 v3.14.1 + github.com/coreos/go-oidc/v3 v3.15.0 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/go-jose/go-jose/v4 v4.1.2 github.com/godbus/dbus/v5 v5.1.0 diff --git a/go.sum b/go.sum index 4016abf78d..4f432c7d08 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2 h1:Hr5FTipp7SL07o2FvoVOX9HR github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2/go.mod h1:QyVsSSN64v5TGltphKLQ2sQxe4OBQg0J1eKRcVBnfgE= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= -github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk= -github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU= +github.com/coreos/go-oidc/v3 v3.15.0 h1:R6Oz8Z4bqWR7VFQ+sPSvZPQv4x8M+sJkDO5ojgwlyAg= +github.com/coreos/go-oidc/v3 v3.15.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= From bae84b20721c77abef12e03b0d8dece095f643ff Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 11 Aug 2025 14:03:06 +0200 Subject: [PATCH 0646/1670] Bump Go toolchain version to 1.23.12 govulncheck reports the following vulnerability in go1.23.10 Vulnerability #1: GO-2025-3849 Incorrect results returned from Rows.Scan in database/sql More info: https://pkg.go.dev/vuln/GO-2025-3849 Standard library Found in: database/sql@go1.23.10 Fixed in: database/sql@go1.23.12 --- go.mod | 2 +- tools/go.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 2a002344c8..647eed7804 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/ubuntu/authd go 1.23.0 -toolchain go1.23.10 +toolchain go1.23.12 require ( github.com/charmbracelet/bubbles v0.20.0 diff --git a/tools/go.mod b/tools/go.mod index 54acb806eb..574ba5a16b 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -2,7 +2,7 @@ module github.com/ubuntu/authd/tools go 1.23.0 -toolchain go1.23.10 +toolchain go1.23.12 require ( github.com/golang/protobuf v1.5.4 From db13075a3a6049f55d44ecd23ed68970fca7e1e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 15:30:42 +0000 Subject: [PATCH 0647/1670] deps(go): bump golang.org/x/crypto Bumps the minor-updates group with 1 update in the / directory: [golang.org/x/crypto](https://github.com/golang/crypto). Updates `golang.org/x/crypto` from 0.40.0 to 0.41.0 - [Commits](https://github.com/golang/crypto/compare/v0.40.0...v0.41.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-version: 0.41.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-updates ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 69e307f2d5..3cba567189 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/ubuntu/authd v0.5.6 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 - golang.org/x/crypto v0.40.0 + golang.org/x/crypto v0.41.0 golang.org/x/oauth2 v0.30.0 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 @@ -66,6 +66,6 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/net v0.42.0 // indirect golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.34.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect ) diff --git a/go.sum b/go.sum index 4f432c7d08..d36d7a8927 100644 --- a/go.sum +++ b/go.sum @@ -124,8 +124,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -145,16 +145,16 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc 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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= From 9b9c0416d817972bdbe174d65f9aa51f5e450459 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 12 Aug 2025 21:31:16 +0200 Subject: [PATCH 0648/1670] Print a message when the refresh token is expired I encountered the case that the refresh token in one of my VMs was expired and was puzzled for a while why the PAM module auto-selects device authentication after I entered my local password. Let's print a message which makes it clear to the user why device authentication is required. --- internal/broker/broker.go | 4 ++-- .../first_call | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index ca15be8233..40154f2564 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -618,9 +618,9 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au authInfo, err = b.refreshToken(ctx, session.oauth2Config, authInfo) var retrieveErr *oauth2.RetrieveError if errors.As(err, &retrieveErr) && b.provider.IsTokenExpiredError(*retrieveErr) { - // The refresh token is expired, so the user needs to authenticate via OIDC again. + log.Noticef(context.Background(), "Refresh token expired for user %q, new device authentication required", session.username) session.nextAuthModes = []string{authmodes.Device, authmodes.DeviceQr} - return AuthNext, nil + return AuthNext, errorMessage{Message: "refresh token expired, please authenticate again using device authentication"} } if err != nil { log.Error(context.Background(), err.Error()) diff --git a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode/first_call b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode/first_call index d0887a134f..6d5619a4b1 100644 --- a/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode/first_call +++ b/internal/broker/testdata/golden/TestIsAuthenticated/Authenticating_with_password_when_refresh_token_is_expired_results_in_device_auth_as_next_mode/first_call @@ -1,3 +1,3 @@ access: next -data: '{}' +data: '{"message":"authentication failure: refresh token expired, please authenticate again using device authentication"}' err: From d5f40fbfc94d392747f98f841af03d508d0b8be5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 09:10:22 +0000 Subject: [PATCH 0649/1670] deps(ci): bump actions/checkout from 4 to 5 in the gh-actions group Bumps the gh-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 4 to 5 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: gh-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/auto-updates.yaml | 2 +- .github/workflows/build-deb.yaml | 6 +++--- .github/workflows/git.yml | 2 +- .github/workflows/qa.yaml | 8 ++++---- .github/workflows/validate-dependabot.yaml | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/auto-updates.yaml b/.github/workflows/auto-updates.yaml index f7fd9ff2a7..c017040784 100644 --- a/.github/workflows/auto-updates.yaml +++ b/.github/workflows/auto-updates.yaml @@ -53,7 +53,7 @@ jobs: fi - name: Checkout the code - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: ${{ matrix.branch }} diff --git a/.github/workflows/build-deb.yaml b/.github/workflows/build-deb.yaml index c9264cc67b..3354481c41 100644 --- a/.github/workflows/build-deb.yaml +++ b/.github/workflows/build-deb.yaml @@ -50,7 +50,7 @@ jobs: steps: - name: Checkout authd code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Build debian packages and sources uses: canonical/desktop-engineering/gh-actions/common/build-debian@main @@ -89,7 +89,7 @@ jobs: steps: - name: Checkout authd code - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 100 @@ -169,7 +169,7 @@ jobs: dpkg-dev devscripts - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 100 path: repo diff --git a/.github/workflows/git.yml b/.github/workflows/git.yml index e3f3a5cfc2..6416b56897 100644 --- a/.github/workflows/git.yml +++ b/.github/workflows/git.yml @@ -7,6 +7,6 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Block Fixup Commit Merge uses: 13rac1/block-fixup-merge-action@v2.0.0 diff --git a/.github/workflows/qa.yaml b/.github/workflows/qa.yaml index 7590751dc7..d92a5d17b1 100644 --- a/.github/workflows/qa.yaml +++ b/.github/workflows/qa.yaml @@ -46,7 +46,7 @@ jobs: run: | sudo apt update sudo apt install -y ${{ env.apt_deps }} - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Go code sanity check uses: canonical/desktop-engineering/gh-actions/go/code-sanity@main with: @@ -83,7 +83,7 @@ jobs: run: | sudo apt update sudo apt install -y ${{ env.apt_deps }} ${{ env.protobuf_compilers}} - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Rust code sanity check uses: canonical/desktop-engineering/gh-actions/rust/code-sanity@main with: @@ -107,7 +107,7 @@ jobs: scan_build_dir=$(mktemp -d --tmpdir scan-build-dir-XXXXXX) echo SCAN_BUILD_REPORTS_PATH="${scan_build_dir}" >> $GITHUB_ENV - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Run scan build on GDM extensions run: | set -eu @@ -183,7 +183,7 @@ jobs: sudo apt remove -y ubuntu-dev-tools sudo apt autoremove -y fi - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-go@v5 with: go-version-file: go.mod diff --git a/.github/workflows/validate-dependabot.yaml b/.github/workflows/validate-dependabot.yaml index 135dbdb499..770bb18e3b 100644 --- a/.github/workflows/validate-dependabot.yaml +++ b/.github/workflows/validate-dependabot.yaml @@ -9,7 +9,7 @@ jobs: validate: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: marocchino/validate-dependabot@v3 id: validate - uses: marocchino/sticky-pull-request-comment@v2 From 4e3dd57bf316fbbff25f49add147ad102c235819 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Aug 2025 09:51:15 +0000 Subject: [PATCH 0650/1670] deps(go): bump the minor-updates group across 1 directory with 7 updates Bumps the minor-updates group with 7 updates in the / directory: | Package | From | To | | --- | --- | --- | | [github.com/coreos/go-systemd/v22](https://github.com/coreos/go-systemd) | `22.5.0` | `22.6.0` | | [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) | `1.14.30` | `1.14.32` | | [go.etcd.io/bbolt](https://github.com/etcd-io/bbolt) | `1.4.2` | `1.4.3` | | [golang.org/x/sys](https://github.com/golang/sys) | `0.34.0` | `0.35.0` | | [golang.org/x/term](https://github.com/golang/term) | `0.33.0` | `0.34.0` | | [google.golang.org/grpc](https://github.com/grpc/grpc-go) | `1.74.2` | `1.75.0` | | google.golang.org/protobuf | `1.36.6` | `1.36.8` | Updates `github.com/coreos/go-systemd/v22` from 22.5.0 to 22.6.0 - [Release notes](https://github.com/coreos/go-systemd/releases) - [Commits](https://github.com/coreos/go-systemd/compare/v22.5.0...v22.6.0) Updates `github.com/mattn/go-sqlite3` from 1.14.30 to 1.14.32 - [Release notes](https://github.com/mattn/go-sqlite3/releases) - [Commits](https://github.com/mattn/go-sqlite3/compare/v1.14.30...v1.14.32) Updates `go.etcd.io/bbolt` from 1.4.2 to 1.4.3 - [Release notes](https://github.com/etcd-io/bbolt/releases) - [Commits](https://github.com/etcd-io/bbolt/compare/v1.4.2...v1.4.3) Updates `golang.org/x/sys` from 0.34.0 to 0.35.0 - [Commits](https://github.com/golang/sys/compare/v0.34.0...v0.35.0) Updates `golang.org/x/term` from 0.33.0 to 0.34.0 - [Commits](https://github.com/golang/term/compare/v0.33.0...v0.34.0) Updates `google.golang.org/grpc` from 1.74.2 to 1.75.0 - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.74.2...v1.75.0) Updates `google.golang.org/protobuf` from 1.36.6 to 1.36.8 --- updated-dependencies: - dependency-name: github.com/coreos/go-systemd/v22 dependency-version: 22.6.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-updates - dependency-name: github.com/mattn/go-sqlite3 dependency-version: 1.14.32 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-updates - dependency-name: go.etcd.io/bbolt dependency-version: 1.4.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-updates - dependency-name: golang.org/x/sys dependency-version: 0.35.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-updates - dependency-name: golang.org/x/term dependency-version: 0.34.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-updates - dependency-name: google.golang.org/grpc dependency-version: 1.75.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-updates - dependency-name: google.golang.org/protobuf dependency-version: 1.36.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-updates ... Signed-off-by: dependabot[bot] --- go.mod | 22 +++++++++---------- go.sum | 67 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 45 insertions(+), 44 deletions(-) diff --git a/go.mod b/go.mod index 647eed7804..446829aa49 100644 --- a/go.mod +++ b/go.mod @@ -9,10 +9,10 @@ require ( github.com/charmbracelet/bubbletea v1.3.4 github.com/charmbracelet/lipgloss v1.1.0 github.com/charmbracelet/x/term v0.2.1 - github.com/coreos/go-systemd/v22 v22.5.0 + github.com/coreos/go-systemd/v22 v22.6.0 github.com/godbus/dbus/v5 v5.1.0 github.com/google/uuid v1.6.0 - github.com/mattn/go-sqlite3 v1.14.30 + github.com/mattn/go-sqlite3 v1.14.32 github.com/msteinert/pam/v2 v2.0.0 github.com/muesli/termenv v0.16.0 github.com/otiai10/copy v1.14.1 @@ -22,12 +22,12 @@ require ( github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.10.0 github.com/ubuntu/decorate v0.0.0-20230606064312-bc4ac83958d6 - go.etcd.io/bbolt v1.4.2 + go.etcd.io/bbolt v1.4.3 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 - golang.org/x/sys v0.34.0 - golang.org/x/term v0.33.0 - google.golang.org/grpc v1.74.2 - google.golang.org/protobuf v1.36.6 + golang.org/x/sys v0.35.0 + golang.org/x/term v0.34.0 + google.golang.org/grpc v1.75.0 + google.golang.org/protobuf v1.36.8 gopkg.in/ini.v1 v1.67.0 gopkg.in/yaml.v3 v3.0.1 gorbe.io/go/osrelease v0.3.0 @@ -64,10 +64,10 @@ require ( github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/net v0.40.0 // indirect - golang.org/x/sync v0.14.0 // indirect - golang.org/x/text v0.25.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/sync v0.15.0 // indirect + golang.org/x/text v0.26.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect ) // FIXME: Use released version once we have one! diff --git a/go.sum b/go.sum index 9dc66c48dc..4985a7ef03 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,8 @@ github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0G github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo= +github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -37,7 +37,6 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -62,8 +61,8 @@ github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2J github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.30 h1:bVreufq3EAIG1Quvws73du3/QgdeZ3myglJlrzSYYCY= -github.com/mattn/go-sqlite3 v1.14.30/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs= +github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= @@ -116,45 +115,47 @@ github.com/ubuntu/decorate v0.0.0-20230606064312-bc4ac83958d6 h1:J0625LLHcZxxnnK github.com/ubuntu/decorate v0.0.0-20230606064312-bc4ac83958d6/go.mod h1:edGgz97NOqS2oqzbKrZqO9YU9neosRrkEZbVJVQynAA= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= -go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= +go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= +go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= -golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= -golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= -google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 06c8f05e875ba2280c0634936cdf0b32fc0ccfee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Aug 2025 14:58:55 +0000 Subject: [PATCH 0651/1670] deps(go-tools): bump github.com/go-viper/mapstructure/v2 in /tools Bumps [github.com/go-viper/mapstructure/v2](https://github.com/go-viper/mapstructure) from 2.3.0 to 2.4.0. - [Release notes](https://github.com/go-viper/mapstructure/releases) - [Changelog](https://github.com/go-viper/mapstructure/blob/main/CHANGELOG.md) - [Commits](https://github.com/go-viper/mapstructure/compare/v2.3.0...v2.4.0) --- updated-dependencies: - dependency-name: github.com/go-viper/mapstructure/v2 dependency-version: 2.4.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- tools/go.mod | 2 +- tools/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index 574ba5a16b..583f38eacb 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -65,7 +65,7 @@ require ( github.com/go-toolsmith/astp v1.1.0 // indirect github.com/go-toolsmith/strparse v1.1.0 // indirect github.com/go-toolsmith/typep v1.1.0 // indirect - github.com/go-viper/mapstructure/v2 v2.3.0 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gofrs/flock v0.12.1 // indirect diff --git a/tools/go.sum b/tools/go.sum index b829c47b43..7dd965448b 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -193,8 +193,8 @@ github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQi github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= -github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= -github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY= github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= From af27f23cb1906eb7a30065f78c682bdb61fa7cc8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 09:39:12 +0000 Subject: [PATCH 0652/1670] deps(rust): bump the minor-updates group across 1 directory with 9 updates Bumps the minor-updates group with 9 updates in the / directory: | Package | From | To | | --- | --- | --- | | [libc](https://github.com/rust-lang/libc) | `0.2.172` | `0.2.174` | | [tonic](https://github.com/hyperium/tonic) | `0.13.1` | `0.14.0` | | [prost](https://github.com/tokio-rs/prost) | `0.13.5` | `0.14.1` | | [rustix](https://github.com/bytecodealliance/rustix) | `1.0.7` | `1.0.8` | | [tokio](https://github.com/tokio-rs/tokio) | `1.45.0` | `1.47.0` | | [ctor](https://github.com/mmastrac/rust-ctor) | `0.4.2` | `0.4.3` | | [hyper-util](https://github.com/hyperium/hyper-util) | `0.1.11` | `0.1.16` | | [tonic-build](https://github.com/hyperium/tonic) | `0.13.1` | `0.14.0` | | [cc](https://github.com/rust-lang/cc-rs) | `1.2.21` | `1.2.30` | Updates `libc` from 0.2.172 to 0.2.174 - [Release notes](https://github.com/rust-lang/libc/releases) - [Changelog](https://github.com/rust-lang/libc/blob/0.2.174/CHANGELOG.md) - [Commits](https://github.com/rust-lang/libc/compare/0.2.172...0.2.174) Updates `tonic` from 0.13.1 to 0.14.0 - [Release notes](https://github.com/hyperium/tonic/releases) - [Changelog](https://github.com/hyperium/tonic/blob/master/CHANGELOG.md) - [Commits](https://github.com/hyperium/tonic/compare/v0.13.1...v0.14.0) Updates `prost` from 0.13.5 to 0.14.1 - [Release notes](https://github.com/tokio-rs/prost/releases) - [Changelog](https://github.com/tokio-rs/prost/blob/master/CHANGELOG.md) - [Commits](https://github.com/tokio-rs/prost/compare/v0.13.5...v0.14.1) Updates `rustix` from 1.0.7 to 1.0.8 - [Release notes](https://github.com/bytecodealliance/rustix/releases) - [Changelog](https://github.com/bytecodealliance/rustix/blob/main/CHANGES.md) - [Commits](https://github.com/bytecodealliance/rustix/compare/v1.0.7...v1.0.8) Updates `tokio` from 1.45.0 to 1.47.0 - [Release notes](https://github.com/tokio-rs/tokio/releases) - [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.45.0...tokio-1.47.0) Updates `ctor` from 0.4.2 to 0.4.3 - [Commits](https://github.com/mmastrac/rust-ctor/commits) Updates `hyper-util` from 0.1.11 to 0.1.16 - [Release notes](https://github.com/hyperium/hyper-util/releases) - [Changelog](https://github.com/hyperium/hyper-util/blob/master/CHANGELOG.md) - [Commits](https://github.com/hyperium/hyper-util/compare/v0.1.11...v0.1.16) Updates `tonic-build` from 0.13.1 to 0.14.0 - [Release notes](https://github.com/hyperium/tonic/releases) - [Changelog](https://github.com/hyperium/tonic/blob/master/CHANGELOG.md) - [Commits](https://github.com/hyperium/tonic/compare/v0.13.1...v0.14.0) Updates `cc` from 1.2.21 to 1.2.30 - [Release notes](https://github.com/rust-lang/cc-rs/releases) - [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md) - [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.21...cc-v1.2.30) --- updated-dependencies: - dependency-name: libc dependency-version: 0.2.174 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-updates - dependency-name: tonic dependency-version: 0.14.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-updates - dependency-name: prost dependency-version: 0.14.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-updates - dependency-name: rustix dependency-version: 1.0.8 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-updates - dependency-name: tokio dependency-version: 1.47.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-updates - dependency-name: ctor dependency-version: 0.4.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-updates - dependency-name: hyper-util dependency-version: 0.1.16 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-updates - dependency-name: tonic-build dependency-version: 0.14.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-updates - dependency-name: cc dependency-version: 1.2.30 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-updates ... Signed-off-by: dependabot[bot] --- Cargo.lock | 226 ++++++++++--------------------------------------- nss/Cargo.toml | 18 ++-- 2 files changed, 53 insertions(+), 191 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4cdf3abe21..b84d7e1d9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -17,15 +17,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - [[package]] name = "android-tzdata" version = "0.1.1" @@ -156,9 +147,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.21" +version = "1.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" dependencies = [ "shlex", ] @@ -208,9 +199,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4735f265ba6a1188052ca32d461028a7d1125868be18e287e756019da7607b5" +checksum = "ec09e802f5081de6157da9a75701d6c713d8dc3ba52571fd4bd25f412644e8a6" dependencies = [ "ctor-proc-macro", "dtor", @@ -218,9 +209,9 @@ dependencies = [ [[package]] name = "ctor-proc-macro" -version = "0.0.5" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f211af61d8efdd104f96e57adf5e426ba1bc3ed7a4ead616e15e5881fd79c4d" +checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2" [[package]] name = "deranged" @@ -268,18 +259,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "fixedbitset" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" - [[package]] name = "flate2" version = "1.1.1" @@ -335,18 +314,6 @@ dependencies = [ "pin-utils", ] -[[package]] -name = "getrandom" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", -] - [[package]] name = "gimli" version = "0.31.1" @@ -378,12 +345,6 @@ version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - [[package]] name = "hex" version = "0.4.3" @@ -483,12 +444,13 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.11" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" dependencies = [ "bytes", "futures-channel", + "futures-core", "futures-util", "http", "http-body", @@ -535,6 +497,17 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "io-uring" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "itertools" version = "0.14.0" @@ -568,9 +541,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.172" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libnss" @@ -635,16 +608,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.52.0", ] -[[package]] -name = "multimap" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" - [[package]] name = "nss" version = "0.1.0" @@ -657,7 +624,7 @@ dependencies = [ "log", "procfs", "prost", - "rustix 1.0.7", + "rustix 1.0.8", "simple_logger", "syslog", "tokio", @@ -717,16 +684,6 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "petgraph" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" -dependencies = [ - "fixedbitset", - "indexmap", -] - [[package]] name = "pin-project" version = "1.1.10" @@ -811,39 +768,19 @@ dependencies = [ [[package]] name = "prost" -version = "0.13.5" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" dependencies = [ "bytes", "prost-derive", ] -[[package]] -name = "prost-build" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" -dependencies = [ - "heck", - "itertools", - "log", - "multimap", - "once_cell", - "petgraph", - "prettyplease", - "prost", - "prost-types", - "regex", - "syn", - "tempfile", -] - [[package]] name = "prost-derive" -version = "0.13.5" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" dependencies = [ "anyhow", "itertools", @@ -852,15 +789,6 @@ dependencies = [ "syn", ] -[[package]] -name = "prost-types" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" -dependencies = [ - "prost", -] - [[package]] name = "quote" version = "1.0.40" @@ -870,41 +798,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" - -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - [[package]] name = "rustc-demangle" version = "0.1.24" @@ -926,9 +819,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags", "errno", @@ -998,12 +891,12 @@ checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "socket2" -version = "0.5.9" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1035,19 +928,6 @@ dependencies = [ "time", ] -[[package]] -name = "tempfile" -version = "3.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" -dependencies = [ - "fastrand", - "getrandom", - "once_cell", - "rustix 1.0.7", - "windows-sys 0.59.0", -] - [[package]] name = "time" version = "0.3.41" @@ -1083,18 +963,20 @@ dependencies = [ [[package]] name = "tokio" -version = "1.45.0" +version = "1.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" +checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "pin-project-lite", + "slab", "socket2", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1134,9 +1016,9 @@ dependencies = [ [[package]] name = "tonic" -version = "0.13.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e581ba15a835f4d9ea06c55ab1bd4dce26fc53752c69a04aac00703bfb49ba9" +checksum = "308e1db96abdccdf0a9150fb69112bf6ea72640e0bd834ef0c4a618ccc8c8ddc" dependencies = [ "async-trait", "axum", @@ -1151,8 +1033,8 @@ dependencies = [ "hyper-util", "percent-encoding", "pin-project", - "prost", "socket2", + "sync_wrapper", "tokio", "tokio-stream", "tower 0.5.2", @@ -1163,14 +1045,12 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.13.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac6f67be712d12f0b41328db3137e0d0757645d8904b4cb7d51cd9c2279e847" +checksum = "18262cdd13dec66e8e3f2e3fe535e4b2cc706fab444a7d3678d75d8ac2557329" dependencies = [ "prettyplease", "proc-macro2", - "prost-build", - "prost-types", "quote", "syn", ] @@ -1280,15 +1160,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" -dependencies = [ - "wit-bindgen-rt", -] - [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -1553,12 +1424,3 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] diff --git a/nss/Cargo.toml b/nss/Cargo.toml index 262c58c027..df80d85811 100644 --- a/nss/Cargo.toml +++ b/nss/Cargo.toml @@ -18,20 +18,20 @@ should_pre_check_env = [] [dependencies] libnss = "0.9.0" -libc = "0.2.171" -tonic = "0.13.1" -prost = "0.13.5" -rustix = { version = "1.0.7", features = ["use-libc"] } -tokio = { version = "1.45.0", features = ["macros", "rt-multi-thread"] } +libc = "0.2.174" +tonic = "0.14.0" +prost = "0.14.1" +rustix = { version = "1.0.8", features = ["use-libc"] } +tokio = { version = "1.47.0", features = ["macros", "rt-multi-thread"] } tower = { version = "0.4.13", features = ["util"] } log = "0.4.27" simple_logger = {version = "5.0.0", features = ["stderr"]} syslog = "7.0.0" -ctor = "0.4.1" +ctor = "0.4.3" procfs = "0.17.0" -hyper-util = "0.1.11" +hyper-util = "0.1.16" [build-dependencies] # We need to pin tonic-build to 0.11.* for now until https://github.com/hyperium/tonic/issues/1909 is fixed. -tonic-build = "0.13.*" -cc = "1.2.21" +tonic-build = "0.14.*" +cc = "1.2.30" From 601ec9d75a2357729bd52b19d3835a38d73cde01 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 11 Aug 2025 14:33:17 +0200 Subject: [PATCH 0653/1670] Pin tonic and prost to 0.13.* for now ... until we migrated our codebase to be compatible with tonic 0.14.0. --- nss/Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nss/Cargo.toml b/nss/Cargo.toml index df80d85811..ade4f39c34 100644 --- a/nss/Cargo.toml +++ b/nss/Cargo.toml @@ -19,8 +19,9 @@ should_pre_check_env = [] [dependencies] libnss = "0.9.0" libc = "0.2.174" -tonic = "0.14.0" -prost = "0.14.1" +# TODO: Migrate to tonic 0.14.0 +tonic = "0.13.*" +prost = "0.13.*" rustix = { version = "1.0.8", features = ["use-libc"] } tokio = { version = "1.47.0", features = ["macros", "rt-multi-thread"] } tower = { version = "0.4.13", features = ["util"] } @@ -32,6 +33,5 @@ procfs = "0.17.0" hyper-util = "0.1.16" [build-dependencies] -# We need to pin tonic-build to 0.11.* for now until https://github.com/hyperium/tonic/issues/1909 is fixed. -tonic-build = "0.14.*" +tonic-build = "0.13.*" cc = "1.2.30" From daac723f9edf322334787190d90fbbd5355efdc2 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 11 Aug 2025 14:53:02 +0200 Subject: [PATCH 0654/1670] Regenerate Cargo.lock with cargo from noble --- Cargo.lock | 403 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 318 insertions(+), 85 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b84d7e1d9d..b43d060a3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "addr2line" @@ -13,9 +13,18 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] [[package]] name = "android-tzdata" @@ -57,9 +66,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axum" @@ -129,15 +138,15 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytes" @@ -147,18 +156,18 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.30" +version = "1.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e" dependencies = [ "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "chrono" @@ -190,9 +199,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -251,19 +260,31 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.11" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", "miniz_oxide", @@ -314,6 +335,18 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + [[package]] name = "gimli" version = "0.31.1" @@ -322,9 +355,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "h2" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9421a676d1b147b16b82c9225157dc629087ef8ec4d5e2960f9437a90dac0a5" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", @@ -341,9 +374,15 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.3" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" + +[[package]] +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" @@ -457,7 +496,7 @@ dependencies = [ "hyper", "libc", "pin-project-lite", - "socket2", + "socket2 0.6.0", "tokio", "tower-service", "tracing", @@ -489,9 +528,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown", @@ -541,9 +580,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.174" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libnss" @@ -582,9 +621,9 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "mime" @@ -594,24 +633,30 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi", - "windows-sys 0.52.0", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + [[package]] name = "nss" version = "0.1.0" @@ -684,6 +729,16 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -724,9 +779,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "prettyplease" -version = "0.2.32" +version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" +checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" dependencies = [ "proc-macro2", "syn", @@ -734,9 +789,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "beef09f85ae72cea1ef96ba6870c51e6382ebfa4f0e85b643459331f3daa5be0" dependencies = [ "unicode-ident", ] @@ -768,19 +823,39 @@ dependencies = [ [[package]] name = "prost" -version = "0.14.1" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" dependencies = [ "bytes", "prost-derive", ] +[[package]] +name = "prost-build" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +dependencies = [ + "heck", + "itertools", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", +] + [[package]] name = "prost-derive" -version = "0.14.1" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", "itertools", @@ -789,6 +864,15 @@ dependencies = [ "syn", ] +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + [[package]] name = "quote" version = "1.0.40" @@ -798,11 +882,46 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustix" @@ -827,14 +946,14 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "serde" @@ -876,18 +995,25 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] [[package]] name = "socket2" @@ -901,9 +1027,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -928,6 +1054,19 @@ dependencies = [ "time", ] +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix 1.0.8", + "windows-sys 0.59.0", +] + [[package]] name = "time" version = "0.3.41" @@ -963,9 +1102,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.47.0" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", @@ -974,7 +1113,7 @@ dependencies = [ "mio", "pin-project-lite", "slab", - "socket2", + "socket2 0.6.0", "tokio-macros", "windows-sys 0.59.0", ] @@ -1003,9 +1142,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", @@ -1016,9 +1155,9 @@ dependencies = [ [[package]] name = "tonic" -version = "0.14.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308e1db96abdccdf0a9150fb69112bf6ea72640e0bd834ef0c4a618ccc8c8ddc" +checksum = "7e581ba15a835f4d9ea06c55ab1bd4dce26fc53752c69a04aac00703bfb49ba9" dependencies = [ "async-trait", "axum", @@ -1033,8 +1172,8 @@ dependencies = [ "hyper-util", "percent-encoding", "pin-project", - "socket2", - "sync_wrapper", + "prost", + "socket2 0.5.10", "tokio", "tokio-stream", "tower 0.5.2", @@ -1045,12 +1184,14 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.14.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18262cdd13dec66e8e3f2e3fe535e4b2cc706fab444a7d3678d75d8ac2557329" +checksum = "eac6f67be712d12f0b41328db3137e0d0757645d8904b4cb7d51cd9c2279e847" dependencies = [ "prettyplease", "proc-macro2", + "prost-build", + "prost-types", "quote", "syn", ] @@ -1115,9 +1256,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.28" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", @@ -1126,9 +1267,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", ] @@ -1156,9 +1297,18 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] [[package]] name = "wasm-bindgen" @@ -1220,9 +1370,9 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", @@ -1255,24 +1405,24 @@ dependencies = [ [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-result" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] @@ -1304,6 +1454,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -1328,13 +1487,30 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -1347,6 +1523,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -1359,6 +1541,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -1371,12 +1559,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -1389,6 +1589,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -1401,6 +1607,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -1413,6 +1625,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -1424,3 +1642,18 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] From cddee59f363c6a620eca1bc2885e289d1ea9f2fe Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 11 Aug 2025 14:53:12 +0200 Subject: [PATCH 0655/1670] debian/control: Update XS-Vendored-Sources-Rust --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index dfaf8e3d0a..137d5aaa0d 100644 --- a/debian/control +++ b/debian/control @@ -25,7 +25,7 @@ Build-Depends: debhelper-compat (= 13), systemd-dev, Standards-Version: 4.6.2 XS-Go-Import-Path: github.com/ubuntu/authd -XS-Vendored-Sources-Rust: adler2@2.0.0, aho-corasick@1.1.3, anyhow@1.0.98, async-trait@0.1.88, atomic-waker@1.1.2, autocfg@1.4.0, axum-core@0.5.2, axum@0.8.4, base64@0.22.1, bitflags@2.9.0, bytes@1.10.1, cc@1.2.21, cfg-if@1.0.0, chrono@0.4.41, colored@2.2.0, crc32fast@1.4.2, ctor-proc-macro@0.0.5, ctor@0.4.2, deranged@0.4.0, dtor-proc-macro@0.0.5, dtor@0.0.6, either@1.15.0, equivalent@1.0.2, errno@0.3.11, fastrand@2.3.0, fixedbitset@0.5.7, flate2@1.1.1, fnv@1.0.7, futures-channel@0.3.31, futures-core@0.3.31, futures-sink@0.3.31, futures-task@0.3.31, futures-util@0.3.31, getrandom@0.3.2, h2@0.4.10, hashbrown@0.15.3, heck@0.5.0, hex@0.4.3, hostname@0.4.1, http-body-util@0.1.3, http-body@1.0.1, http@1.3.1, httparse@1.10.1, httpdate@1.0.3, hyper-timeout@0.5.2, hyper-util@0.1.11, hyper@1.6.0, iana-time-zone@0.1.63, indexmap@2.9.0, itertools@0.14.0, itoa@1.0.15, lazy_static@1.5.0, libc@0.2.172, libnss@0.9.0, linux-raw-sys@0.4.15, linux-raw-sys@0.9.4, log@0.4.27, matchit@0.8.4, memchr@2.7.4, mime@0.3.17, miniz_oxide@0.8.8, mio@1.0.3, multimap@0.10.0, num-conv@0.1.0, num-traits@0.2.19, num_threads@0.1.7, once_cell@1.21.3, paste@1.0.15, percent-encoding@2.3.1, petgraph@0.7.1, pin-project-internal@1.1.10, pin-project-lite@0.2.16, pin-project@1.1.10, pin-utils@0.1.0, powerfmt@0.2.0, prettyplease@0.2.32, proc-macro2@1.0.95, procfs-core@0.17.0, procfs@0.17.0, prost-build@0.13.5, prost-derive@0.13.5, prost-types@0.13.5, prost@0.13.5, quote@1.0.40, regex-automata@0.4.9, regex-syntax@0.8.5, regex@1.11.1, rustix@0.38.44, rustix@1.0.7, rustversion@1.0.20, serde@1.0.219, shlex@1.3.0, simple_logger@5.0.0, slab@0.4.9, smallvec@1.15.0, socket2@0.5.9, syn@2.0.101, sync_wrapper@1.0.2, syslog@7.0.0, tempfile@3.19.1, time-core@0.1.4, time-macros@0.2.22, time@0.3.41, tokio-macros@2.5.0, tokio-stream@0.1.17, tokio-util@0.7.15, tokio@1.45.0, tonic-build@0.13.1, tonic@0.13.1, tower-layer@0.3.3, tower-service@0.3.3, tower@0.4.13, tower@0.5.2, tracing-attributes@0.1.28, tracing-core@0.1.33, tracing@0.1.41, try-lock@0.2.5, unicode-ident@1.0.18, want@0.3.1 +XS-Vendored-Sources-Rust: adler2@2.0.1, aho-corasick@1.1.3, anyhow@1.0.98, async-trait@0.1.88, atomic-waker@1.1.2, autocfg@1.5.0, axum-core@0.5.2, axum@0.8.4, base64@0.22.1, bitflags@2.9.1, bytes@1.10.1, cc@1.2.32, cfg-if@1.0.1, chrono@0.4.41, colored@2.2.0, crc32fast@1.5.0, ctor-proc-macro@0.0.6, ctor@0.4.3, deranged@0.4.0, dtor-proc-macro@0.0.5, dtor@0.0.6, either@1.15.0, equivalent@1.0.2, errno@0.3.13, fastrand@2.3.0, fixedbitset@0.5.7, flate2@1.1.2, fnv@1.0.7, futures-channel@0.3.31, futures-core@0.3.31, futures-sink@0.3.31, futures-task@0.3.31, futures-util@0.3.31, getrandom@0.3.3, h2@0.4.12, hashbrown@0.15.5, heck@0.5.0, hex@0.4.3, hostname@0.4.1, http-body-util@0.1.3, http-body@1.0.1, http@1.3.1, httparse@1.10.1, httpdate@1.0.3, hyper-timeout@0.5.2, hyper-util@0.1.16, hyper@1.6.0, iana-time-zone@0.1.63, indexmap@2.10.0, itertools@0.14.0, itoa@1.0.15, lazy_static@1.5.0, libc@0.2.175, libnss@0.9.0, linux-raw-sys@0.4.15, linux-raw-sys@0.9.4, log@0.4.27, matchit@0.8.4, memchr@2.7.5, mime@0.3.17, miniz_oxide@0.8.9, mio@1.0.4, multimap@0.10.1, num-conv@0.1.0, num-traits@0.2.19, num_threads@0.1.7, once_cell@1.21.3, paste@1.0.15, percent-encoding@2.3.1, petgraph@0.7.1, pin-project-internal@1.1.10, pin-project-lite@0.2.16, pin-project@1.1.10, pin-utils@0.1.0, powerfmt@0.2.0, prettyplease@0.2.36, proc-macro2@1.0.96, procfs-core@0.17.0, procfs@0.17.0, prost-build@0.13.5, prost-derive@0.13.5, prost-types@0.13.5, prost@0.13.5, quote@1.0.40, regex-automata@0.4.9, regex-syntax@0.8.5, regex@1.11.1, rustix@0.38.44, rustix@1.0.8, rustversion@1.0.22, serde@1.0.219, shlex@1.3.0, simple_logger@5.0.0, slab@0.4.11, smallvec@1.15.1, socket2@0.5.10, socket2@0.6.0, syn@2.0.104, sync_wrapper@1.0.2, syslog@7.0.0, tempfile@3.20.0, time-core@0.1.4, time-macros@0.2.22, time@0.3.41, tokio-macros@2.5.0, tokio-stream@0.1.17, tokio-util@0.7.16, tokio@1.47.1, tonic-build@0.13.1, tonic@0.13.1, tower-layer@0.3.3, tower-service@0.3.3, tower@0.4.13, tower@0.5.2, tracing-attributes@0.1.30, tracing-core@0.1.34, tracing@0.1.41, try-lock@0.2.5, unicode-ident@1.0.18, want@0.3.1 Homepage: https://github.com/ubuntu/authd Vcs-Browser: https://github.com/ubuntu/authd Vcs-Git: https://github.com/ubuntu/authd.git From b3ccaf44d59031c0553e4dd96028033facee2c02 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Fri, 22 Aug 2025 13:11:06 +0200 Subject: [PATCH 0656/1670] Log error when login fails because provider is unreachable UDENG-7721 --- internal/broker/broker.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/broker/broker.go b/internal/broker/broker.go index 91b87dd8fc..7afcff0581 100644 --- a/internal/broker/broker.go +++ b/internal/broker/broker.go @@ -610,6 +610,7 @@ func (b *Broker) handleIsAuthenticated(ctx context.Context, session *session, au } if b.cfg.forceProviderAuthentication && session.isOffline { + log.Error(context.Background(), "Login failed: force_provider_authentication is enabled, but the provider is not reachable") return AuthDenied, errorMessage{Message: "could not refresh token: provider is not reachable"} } From 1c4e662fb5f730870bbb22c76528fb5362fa4dcf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Aug 2025 16:16:43 +0000 Subject: [PATCH 0657/1670] deps(go): bump github.com/go-viper/mapstructure/v2 from 2.3.0 to 2.4.0 Bumps [github.com/go-viper/mapstructure/v2](https://github.com/go-viper/mapstructure) from 2.3.0 to 2.4.0. - [Release notes](https://github.com/go-viper/mapstructure/releases) - [Changelog](https://github.com/go-viper/mapstructure/blob/main/CHANGELOG.md) - [Commits](https://github.com/go-viper/mapstructure/compare/v2.3.0...v2.4.0) --- updated-dependencies: - dependency-name: github.com/go-viper/mapstructure/v2 dependency-version: 2.4.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 446829aa49..6a5aa78dc4 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect - github.com/go-viper/mapstructure/v2 v2.3.0 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect diff --git a/go.sum b/go.sum index 4985a7ef03..69ccdd4e18 100644 --- a/go.sum +++ b/go.sum @@ -35,8 +35,8 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= -github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= From 80cf47d9bc6f68dd2f465eb852ada139a6ba2ac9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Aug 2025 16:17:07 +0000 Subject: [PATCH 0658/1670] deps(go): bump the minor-updates group with 3 updates Bumps the minor-updates group with 3 updates: [github.com/Azure/azure-sdk-for-go/sdk/azcore](https://github.com/Azure/azure-sdk-for-go), [github.com/microsoftgraph/msgraph-sdk-go](https://github.com/microsoftgraph/msgraph-sdk-go) and [github.com/stretchr/testify](https://github.com/stretchr/testify). Updates `github.com/Azure/azure-sdk-for-go/sdk/azcore` from 1.18.2 to 1.19.0 - [Release notes](https://github.com/Azure/azure-sdk-for-go/releases) - [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/go-mgmt-sdk-release-guideline.md) - [Commits](https://github.com/Azure/azure-sdk-for-go/compare/sdk/azcore/v1.18.2...sdk/azcore/v1.19.0) Updates `github.com/microsoftgraph/msgraph-sdk-go` from 1.82.0 to 1.84.0 - [Release notes](https://github.com/microsoftgraph/msgraph-sdk-go/releases) - [Changelog](https://github.com/microsoftgraph/msgraph-sdk-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/microsoftgraph/msgraph-sdk-go/compare/v1.82.0...v1.84.0) Updates `github.com/stretchr/testify` from 1.10.0 to 1.11.0 - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.10.0...v1.11.0) --- updated-dependencies: - dependency-name: github.com/Azure/azure-sdk-for-go/sdk/azcore dependency-version: 1.19.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-updates - dependency-name: github.com/microsoftgraph/msgraph-sdk-go dependency-version: 1.84.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-updates - dependency-name: github.com/stretchr/testify dependency-version: 1.11.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-updates ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 3cba567189..6492035af0 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.24.0 toolchain go1.24.4 require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0 github.com/coreos/go-oidc/v3 v3.15.0 github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/go-jose/go-jose/v4 v4.1.2 @@ -13,13 +13,13 @@ require ( github.com/golang-jwt/jwt/v5 v5.3.0 github.com/google/uuid v1.6.0 github.com/k0kubun/pp v3.0.1+incompatible - github.com/microsoftgraph/msgraph-sdk-go v1.82.0 + github.com/microsoftgraph/msgraph-sdk-go v1.84.0 github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 github.com/otiai10/copy v1.14.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/spf13/cobra v1.9.1 github.com/spf13/viper v1.20.1 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.11.0 github.com/ubuntu/authd v0.5.6 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 diff --git a/go.sum b/go.sum index d36d7a8927..5adf4ad24f 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2 h1:Hr5FTipp7SL07o2FvoVOX9HRiRH3CR3Mj8pxqCcdD5A= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2/go.mod h1:QyVsSSN64v5TGltphKLQ2sQxe4OBQg0J1eKRcVBnfgE= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0 h1:ci6Yd6nysBRLEodoziB6ah1+YOzZbZk+NYneoA6q+6E= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.0/go.mod h1:QyVsSSN64v5TGltphKLQ2sQxe4OBQg0J1eKRcVBnfgE= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= github.com/coreos/go-oidc/v3 v3.15.0 h1:R6Oz8Z4bqWR7VFQ+sPSvZPQv4x8M+sJkDO5ojgwlyAg= @@ -65,8 +65,8 @@ github.com/microsoft/kiota-serialization-multipart-go v1.1.2 h1:1pUyA1QgIeKslQwb github.com/microsoft/kiota-serialization-multipart-go v1.1.2/go.mod h1:j2K7ZyYErloDu7Kuuk993DsvfoP7LPWvAo7rfDpdPio= github.com/microsoft/kiota-serialization-text-go v1.1.2 h1:7OfKFlzdjpPygca/+OtqafkEqCWR7+94efUFGC28cLw= github.com/microsoft/kiota-serialization-text-go v1.1.2/go.mod h1:QNTcswkBPFY3QVBFmzfk00UMNViKQtV0AQKCrRw5ibM= -github.com/microsoftgraph/msgraph-sdk-go v1.82.0 h1:A+MxDbY3TFAjrUdbUH6YJmxSwApuykVToXWLO+L+sGw= -github.com/microsoftgraph/msgraph-sdk-go v1.82.0/go.mod h1:vZjQkQLX2vma7uMxvFHjSmy1edTOMkkWn6DhzVR55m8= +github.com/microsoftgraph/msgraph-sdk-go v1.84.0 h1:XFxBxohWE3pJi6Rqau3nrP1jH4pLwJqIPoL4TjzQEtM= +github.com/microsoftgraph/msgraph-sdk-go v1.84.0/go.mod h1:vZjQkQLX2vma7uMxvFHjSmy1edTOMkkWn6DhzVR55m8= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2 h1:5jCUSosTKaINzPPQXsz7wsHWwknyBmJSu8+ZWxx3kdQ= github.com/microsoftgraph/msgraph-sdk-go-core v1.3.2/go.mod h1:iD75MK3LX8EuwjDYCmh0hkojKXK6VKME33u4daCo3cE= github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8= @@ -101,8 +101,8 @@ github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3 h1:7hth9376EoQEd1hH4lAp3 github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3/go.mod h1:Z5KcoM0YLC7INlNhEezeIZ0TZNYf7WSNO0Lvah4DSeQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8= +github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/ubuntu/authd v0.5.6 h1:2QZuW2+bQEyz31s05fsZw8I8GbE22balR8zVcYssDKc= From 8e39ba3d70484f03955ab0ca02c2f7fe8ec203e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Aug 2025 18:16:55 +0000 Subject: [PATCH 0659/1670] deps(ci): bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/auto-updates.yml | 2 +- .github/workflows/ci.yml | 4 ++-- .github/workflows/git.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/auto-updates.yml b/.github/workflows/auto-updates.yml index 47a545dc20..7d70b95305 100644 --- a/.github/workflows/auto-updates.yml +++ b/.github/workflows/auto-updates.yml @@ -26,7 +26,7 @@ jobs: set -eu sudo apt update sudo apt install -y git - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: ref: main fetch-depth: 0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c909d80c49..733538dc40 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: name: "Go: Code sanity" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Go code sanity check uses: canonical/desktop-engineering/gh-actions/go/code-sanity@main with: @@ -23,7 +23,7 @@ jobs: name: "Go: Tests" runs-on: ubuntu-24.04 # ubuntu-latest-runner steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-go@v5 with: go-version-file: go.mod diff --git a/.github/workflows/git.yml b/.github/workflows/git.yml index e3f3a5cfc2..6416b56897 100644 --- a/.github/workflows/git.yml +++ b/.github/workflows/git.yml @@ -7,6 +7,6 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Block Fixup Commit Merge uses: 13rac1/block-fixup-merge-action@v2.0.0 From e6eeef16fa6ac5a9b0b308fede2c0e4de13756cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 10:26:41 +0000 Subject: [PATCH 0660/1670] deps(go): bump github.com/stretchr/testify in the minor-updates group Bumps the minor-updates group with 1 update: [github.com/stretchr/testify](https://github.com/stretchr/testify). Updates `github.com/stretchr/testify` from 1.10.0 to 1.11.1 - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.10.0...v1.11.1) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-version: 1.11.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-updates ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6a5aa78dc4..3218dd3e6c 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/spf13/cobra v1.9.1 github.com/spf13/viper v1.20.1 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.11.1 github.com/ubuntu/decorate v0.0.0-20230606064312-bc4ac83958d6 go.etcd.io/bbolt v1.4.3 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 diff --git a/go.sum b/go.sum index 69ccdd4e18..b945797126 100644 --- a/go.sum +++ b/go.sum @@ -107,8 +107,8 @@ github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqj github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/ubuntu/decorate v0.0.0-20230606064312-bc4ac83958d6 h1:J0625LLHcZxxnnKCdr2iBbTtYjUiv6KkM6NpGisuQ3Q= From 02f545a34fb740c5c3fe81a1d5f5f447257257f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 11:23:44 +0000 Subject: [PATCH 0661/1670] deps(rust): bump the minor-updates group with 5 updates Bumps the minor-updates group with 5 updates: | Package | From | To | | --- | --- | --- | | [tonic](https://github.com/hyperium/tonic) | `0.13.1` | `0.14.1` | | [prost](https://github.com/tokio-rs/prost) | `0.13.5` | `0.14.1` | | [ctor](https://github.com/mmastrac/rust-ctor) | `0.4.3` | `0.5.0` | | [tonic-build](https://github.com/hyperium/tonic) | `0.13.1` | `0.14.1` | | [cc](https://github.com/rust-lang/cc-rs) | `1.2.32` | `1.2.34` | Updates `tonic` from 0.13.1 to 0.14.1 - [Release notes](https://github.com/hyperium/tonic/releases) - [Changelog](https://github.com/hyperium/tonic/blob/master/CHANGELOG.md) - [Commits](https://github.com/hyperium/tonic/compare/v0.13.1...v0.14.1) Updates `prost` from 0.13.5 to 0.14.1 - [Release notes](https://github.com/tokio-rs/prost/releases) - [Changelog](https://github.com/tokio-rs/prost/blob/master/CHANGELOG.md) - [Commits](https://github.com/tokio-rs/prost/compare/v0.13.5...v0.14.1) Updates `ctor` from 0.4.3 to 0.5.0 - [Commits](https://github.com/mmastrac/rust-ctor/commits) Updates `tonic-build` from 0.13.1 to 0.14.1 - [Release notes](https://github.com/hyperium/tonic/releases) - [Changelog](https://github.com/hyperium/tonic/blob/master/CHANGELOG.md) - [Commits](https://github.com/hyperium/tonic/compare/v0.13.1...v0.14.1) Updates `cc` from 1.2.32 to 1.2.34 - [Release notes](https://github.com/rust-lang/cc-rs/releases) - [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md) - [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.32...cc-v1.2.34) --- updated-dependencies: - dependency-name: tonic dependency-version: 0.14.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-updates - dependency-name: prost dependency-version: 0.14.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-updates - dependency-name: ctor dependency-version: 0.5.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-updates - dependency-name: tonic-build dependency-version: 0.14.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-updates - dependency-name: cc dependency-version: 1.2.34 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-updates ... Signed-off-by: dependabot[bot] --- Cargo.lock | 215 +++++-------------------------------------------- nss/Cargo.toml | 10 +-- 2 files changed, 27 insertions(+), 198 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b43d060a3d..f304664717 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -17,15 +17,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - [[package]] name = "android-tzdata" version = "0.1.1" @@ -156,9 +147,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.32" +version = "1.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e" +checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" dependencies = [ "shlex", ] @@ -208,9 +199,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.4.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec09e802f5081de6157da9a75701d6c713d8dc3ba52571fd4bd25f412644e8a6" +checksum = "67773048316103656a637612c4a62477603b777d91d9c62ff2290f9cde178fdb" dependencies = [ "ctor-proc-macro", "dtor", @@ -233,18 +224,18 @@ dependencies = [ [[package]] name = "dtor" -version = "0.0.6" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97cbdf2ad6846025e8e25df05171abfb30e3ababa12ee0a0e44b9bbe570633a8" +checksum = "e58a0764cddb55ab28955347b45be00ade43d4d6f3ba4bf3dc354e4ec9432934" dependencies = [ "dtor-proc-macro", ] [[package]] name = "dtor-proc-macro" -version = "0.0.5" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7454e41ff9012c00d53cf7f475c5e3afa3b91b7c90568495495e8d9bf47a1055" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" [[package]] name = "either" @@ -268,18 +259,6 @@ dependencies = [ "windows-sys 0.60.2", ] -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "fixedbitset" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" - [[package]] name = "flate2" version = "1.1.2" @@ -335,18 +314,6 @@ dependencies = [ "pin-utils", ] -[[package]] -name = "getrandom" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", -] - [[package]] name = "gimli" version = "0.31.1" @@ -378,12 +345,6 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - [[package]] name = "hex" version = "0.4.3" @@ -496,7 +457,7 @@ dependencies = [ "hyper", "libc", "pin-project-lite", - "socket2 0.6.0", + "socket2", "tokio", "tower-service", "tracing", @@ -647,16 +608,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "windows-sys 0.59.0", ] -[[package]] -name = "multimap" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" - [[package]] name = "nss" version = "0.1.0" @@ -729,16 +684,6 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "petgraph" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" -dependencies = [ - "fixedbitset", - "indexmap", -] - [[package]] name = "pin-project" version = "1.1.10" @@ -823,39 +768,19 @@ dependencies = [ [[package]] name = "prost" -version = "0.13.5" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" dependencies = [ "bytes", "prost-derive", ] -[[package]] -name = "prost-build" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" -dependencies = [ - "heck", - "itertools", - "log", - "multimap", - "once_cell", - "petgraph", - "prettyplease", - "prost", - "prost-types", - "regex", - "syn", - "tempfile", -] - [[package]] name = "prost-derive" -version = "0.13.5" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" dependencies = [ "anyhow", "itertools", @@ -864,15 +789,6 @@ dependencies = [ "syn", ] -[[package]] -name = "prost-types" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" -dependencies = [ - "prost", -] - [[package]] name = "quote" version = "1.0.40" @@ -882,41 +798,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - [[package]] name = "rustc-demangle" version = "0.1.26" @@ -1005,16 +886,6 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "socket2" version = "0.6.0" @@ -1054,19 +925,6 @@ dependencies = [ "time", ] -[[package]] -name = "tempfile" -version = "3.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" -dependencies = [ - "fastrand", - "getrandom", - "once_cell", - "rustix 1.0.8", - "windows-sys 0.59.0", -] - [[package]] name = "time" version = "0.3.41" @@ -1113,7 +971,7 @@ dependencies = [ "mio", "pin-project-lite", "slab", - "socket2 0.6.0", + "socket2", "tokio-macros", "windows-sys 0.59.0", ] @@ -1155,9 +1013,9 @@ dependencies = [ [[package]] name = "tonic" -version = "0.13.1" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e581ba15a835f4d9ea06c55ab1bd4dce26fc53752c69a04aac00703bfb49ba9" +checksum = "67ac5a8627ada0968acec063a4746bf79588aa03ccb66db2f75d7dce26722a40" dependencies = [ "async-trait", "axum", @@ -1172,8 +1030,8 @@ dependencies = [ "hyper-util", "percent-encoding", "pin-project", - "prost", - "socket2 0.5.10", + "socket2", + "sync_wrapper", "tokio", "tokio-stream", "tower 0.5.2", @@ -1184,14 +1042,12 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.13.1" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac6f67be712d12f0b41328db3137e0d0757645d8904b4cb7d51cd9c2279e847" +checksum = "49e323d8bba3be30833707e36d046deabf10a35ae8ad3cae576943ea8933e25d" dependencies = [ "prettyplease", "proc-macro2", - "prost-build", - "prost-types", "quote", "syn", ] @@ -1301,15 +1157,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" -dependencies = [ - "wit-bindgen-rt", -] - [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -1436,15 +1283,6 @@ dependencies = [ "windows-targets 0.48.5", ] -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.59.0" @@ -1648,12 +1486,3 @@ name = "windows_x86_64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] diff --git a/nss/Cargo.toml b/nss/Cargo.toml index ade4f39c34..9b15a19792 100644 --- a/nss/Cargo.toml +++ b/nss/Cargo.toml @@ -20,18 +20,18 @@ should_pre_check_env = [] libnss = "0.9.0" libc = "0.2.174" # TODO: Migrate to tonic 0.14.0 -tonic = "0.13.*" -prost = "0.13.*" +tonic = "0.14.*" +prost = "0.14.*" rustix = { version = "1.0.8", features = ["use-libc"] } tokio = { version = "1.47.0", features = ["macros", "rt-multi-thread"] } tower = { version = "0.4.13", features = ["util"] } log = "0.4.27" simple_logger = {version = "5.0.0", features = ["stderr"]} syslog = "7.0.0" -ctor = "0.4.3" +ctor = "0.5.0" procfs = "0.17.0" hyper-util = "0.1.16" [build-dependencies] -tonic-build = "0.13.*" -cc = "1.2.30" +tonic-build = "0.14.*" +cc = "1.2.34" From b052034cc201b8a8fe4da2992ae6bc8e32b17810 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 11 Aug 2025 14:33:17 +0200 Subject: [PATCH 0662/1670] Pin tonic and prost to 0.13.* for now ... until we migrated our codebase to be compatible with tonic 0.14.0. --- nss/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nss/Cargo.toml b/nss/Cargo.toml index 9b15a19792..886652bb97 100644 --- a/nss/Cargo.toml +++ b/nss/Cargo.toml @@ -20,8 +20,8 @@ should_pre_check_env = [] libnss = "0.9.0" libc = "0.2.174" # TODO: Migrate to tonic 0.14.0 -tonic = "0.14.*" -prost = "0.14.*" +tonic = "0.13.*" +prost = "0.13.*" rustix = { version = "1.0.8", features = ["use-libc"] } tokio = { version = "1.47.0", features = ["macros", "rt-multi-thread"] } tower = { version = "0.4.13", features = ["util"] } @@ -33,5 +33,5 @@ procfs = "0.17.0" hyper-util = "0.1.16" [build-dependencies] -tonic-build = "0.14.*" +tonic-build = "0.13.*" cc = "1.2.34" From 69f1fc4af4f3f213fc6271e6d801e577b33a03a7 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 28 Aug 2025 14:50:53 +0200 Subject: [PATCH 0663/1670] Regenerate Cargo.lock with cargo from noble --- Cargo.lock | 247 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 210 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f304664717..b0de1046a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "addr2line" @@ -17,6 +17,15 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -34,15 +43,15 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", @@ -129,9 +138,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" [[package]] name = "bumpalo" @@ -156,9 +165,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "chrono" @@ -259,6 +268,18 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.1.2" @@ -314,6 +335,18 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + [[package]] name = "gimli" version = "0.31.1" @@ -345,6 +378,12 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hex" version = "0.4.3" @@ -410,13 +449,14 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2", "http", "http-body", @@ -424,6 +464,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -457,7 +498,7 @@ dependencies = [ "hyper", "libc", "pin-project-lite", - "socket2", + "socket2 0.6.0", "tokio", "tower-service", "tracing", @@ -489,9 +530,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" dependencies = [ "equivalent", "hashbrown", @@ -499,9 +540,9 @@ dependencies = [ [[package]] name = "io-uring" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ "bitflags", "cfg-if", @@ -608,10 +649,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.59.0", ] +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + [[package]] name = "nss" version = "0.1.0" @@ -680,9 +727,19 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "petgraph" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] [[package]] name = "pin-project" @@ -724,9 +781,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "prettyplease" -version = "0.2.36" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", "syn", @@ -734,9 +791,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.96" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beef09f85ae72cea1ef96ba6870c51e6382ebfa4f0e85b643459331f3daa5be0" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -768,19 +825,39 @@ dependencies = [ [[package]] name = "prost" -version = "0.14.1" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" dependencies = [ "bytes", "prost-derive", ] +[[package]] +name = "prost-build" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +dependencies = [ + "heck", + "itertools", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", +] + [[package]] name = "prost-derive" -version = "0.14.1" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", "itertools", @@ -789,6 +866,15 @@ dependencies = [ "syn", ] +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + [[package]] name = "quote" version = "1.0.40" @@ -798,6 +884,41 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "regex" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" + [[package]] name = "rustc-demangle" version = "0.1.26" @@ -886,6 +1007,16 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "socket2" version = "0.6.0" @@ -898,9 +1029,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.104" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -925,6 +1056,19 @@ dependencies = [ "time", ] +[[package]] +name = "tempfile" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix 1.0.8", + "windows-sys 0.60.2", +] + [[package]] name = "time" version = "0.3.41" @@ -971,7 +1115,7 @@ dependencies = [ "mio", "pin-project-lite", "slab", - "socket2", + "socket2 0.6.0", "tokio-macros", "windows-sys 0.59.0", ] @@ -1013,9 +1157,9 @@ dependencies = [ [[package]] name = "tonic" -version = "0.14.1" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ac5a8627ada0968acec063a4746bf79588aa03ccb66db2f75d7dce26722a40" +checksum = "7e581ba15a835f4d9ea06c55ab1bd4dce26fc53752c69a04aac00703bfb49ba9" dependencies = [ "async-trait", "axum", @@ -1030,8 +1174,8 @@ dependencies = [ "hyper-util", "percent-encoding", "pin-project", - "socket2", - "sync_wrapper", + "prost", + "socket2 0.5.10", "tokio", "tokio-stream", "tower 0.5.2", @@ -1042,12 +1186,14 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.14.1" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49e323d8bba3be30833707e36d046deabf10a35ae8ad3cae576943ea8933e25d" +checksum = "eac6f67be712d12f0b41328db3137e0d0757645d8904b4cb7d51cd9c2279e847" dependencies = [ "prettyplease", "proc-macro2", + "prost-build", + "prost-types", "quote", "syn", ] @@ -1157,6 +1303,15 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -1283,6 +1438,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -1486,3 +1650,12 @@ name = "windows_x86_64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] From 552b3f9cb96334bff18e1de433ffe1c42027a77b Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 28 Aug 2025 14:51:03 +0200 Subject: [PATCH 0664/1670] debian/control: Update XS-Vendored-Sources-Rust --- debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/control b/debian/control index 137d5aaa0d..398c2424f1 100644 --- a/debian/control +++ b/debian/control @@ -25,7 +25,7 @@ Build-Depends: debhelper-compat (= 13), systemd-dev, Standards-Version: 4.6.2 XS-Go-Import-Path: github.com/ubuntu/authd -XS-Vendored-Sources-Rust: adler2@2.0.1, aho-corasick@1.1.3, anyhow@1.0.98, async-trait@0.1.88, atomic-waker@1.1.2, autocfg@1.5.0, axum-core@0.5.2, axum@0.8.4, base64@0.22.1, bitflags@2.9.1, bytes@1.10.1, cc@1.2.32, cfg-if@1.0.1, chrono@0.4.41, colored@2.2.0, crc32fast@1.5.0, ctor-proc-macro@0.0.6, ctor@0.4.3, deranged@0.4.0, dtor-proc-macro@0.0.5, dtor@0.0.6, either@1.15.0, equivalent@1.0.2, errno@0.3.13, fastrand@2.3.0, fixedbitset@0.5.7, flate2@1.1.2, fnv@1.0.7, futures-channel@0.3.31, futures-core@0.3.31, futures-sink@0.3.31, futures-task@0.3.31, futures-util@0.3.31, getrandom@0.3.3, h2@0.4.12, hashbrown@0.15.5, heck@0.5.0, hex@0.4.3, hostname@0.4.1, http-body-util@0.1.3, http-body@1.0.1, http@1.3.1, httparse@1.10.1, httpdate@1.0.3, hyper-timeout@0.5.2, hyper-util@0.1.16, hyper@1.6.0, iana-time-zone@0.1.63, indexmap@2.10.0, itertools@0.14.0, itoa@1.0.15, lazy_static@1.5.0, libc@0.2.175, libnss@0.9.0, linux-raw-sys@0.4.15, linux-raw-sys@0.9.4, log@0.4.27, matchit@0.8.4, memchr@2.7.5, mime@0.3.17, miniz_oxide@0.8.9, mio@1.0.4, multimap@0.10.1, num-conv@0.1.0, num-traits@0.2.19, num_threads@0.1.7, once_cell@1.21.3, paste@1.0.15, percent-encoding@2.3.1, petgraph@0.7.1, pin-project-internal@1.1.10, pin-project-lite@0.2.16, pin-project@1.1.10, pin-utils@0.1.0, powerfmt@0.2.0, prettyplease@0.2.36, proc-macro2@1.0.96, procfs-core@0.17.0, procfs@0.17.0, prost-build@0.13.5, prost-derive@0.13.5, prost-types@0.13.5, prost@0.13.5, quote@1.0.40, regex-automata@0.4.9, regex-syntax@0.8.5, regex@1.11.1, rustix@0.38.44, rustix@1.0.8, rustversion@1.0.22, serde@1.0.219, shlex@1.3.0, simple_logger@5.0.0, slab@0.4.11, smallvec@1.15.1, socket2@0.5.10, socket2@0.6.0, syn@2.0.104, sync_wrapper@1.0.2, syslog@7.0.0, tempfile@3.20.0, time-core@0.1.4, time-macros@0.2.22, time@0.3.41, tokio-macros@2.5.0, tokio-stream@0.1.17, tokio-util@0.7.16, tokio@1.47.1, tonic-build@0.13.1, tonic@0.13.1, tower-layer@0.3.3, tower-service@0.3.3, tower@0.4.13, tower@0.5.2, tracing-attributes@0.1.30, tracing-core@0.1.34, tracing@0.1.41, try-lock@0.2.5, unicode-ident@1.0.18, want@0.3.1 +XS-Vendored-Sources-Rust: adler2@2.0.1, aho-corasick@1.1.3, anyhow@1.0.99, async-trait@0.1.89, atomic-waker@1.1.2, autocfg@1.5.0, axum-core@0.5.2, axum@0.8.4, base64@0.22.1, bitflags@2.9.3, bytes@1.10.1, cc@1.2.34, cfg-if@1.0.3, chrono@0.4.41, colored@2.2.0, crc32fast@1.5.0, ctor-proc-macro@0.0.6, ctor@0.5.0, deranged@0.4.0, dtor-proc-macro@0.0.6, dtor@0.1.0, either@1.15.0, equivalent@1.0.2, errno@0.3.13, fastrand@2.3.0, fixedbitset@0.5.7, flate2@1.1.2, fnv@1.0.7, futures-channel@0.3.31, futures-core@0.3.31, futures-sink@0.3.31, futures-task@0.3.31, futures-util@0.3.31, getrandom@0.3.3, h2@0.4.12, hashbrown@0.15.5, heck@0.5.0, hex@0.4.3, hostname@0.4.1, http-body-util@0.1.3, http-body@1.0.1, http@1.3.1, httparse@1.10.1, httpdate@1.0.3, hyper-timeout@0.5.2, hyper-util@0.1.16, hyper@1.7.0, iana-time-zone@0.1.63, indexmap@2.11.0, itertools@0.14.0, itoa@1.0.15, lazy_static@1.5.0, libc@0.2.175, libnss@0.9.0, linux-raw-sys@0.4.15, linux-raw-sys@0.9.4, log@0.4.27, matchit@0.8.4, memchr@2.7.5, mime@0.3.17, miniz_oxide@0.8.9, mio@1.0.4, multimap@0.10.1, num-conv@0.1.0, num-traits@0.2.19, num_threads@0.1.7, once_cell@1.21.3, paste@1.0.15, percent-encoding@2.3.2, petgraph@0.7.1, pin-project-internal@1.1.10, pin-project-lite@0.2.16, pin-project@1.1.10, pin-utils@0.1.0, powerfmt@0.2.0, prettyplease@0.2.37, proc-macro2@1.0.101, procfs-core@0.17.0, procfs@0.17.0, prost-build@0.13.5, prost-derive@0.13.5, prost-types@0.13.5, prost@0.13.5, quote@1.0.40, regex-automata@0.4.10, regex-syntax@0.8.6, regex@1.11.2, rustix@0.38.44, rustix@1.0.8, rustversion@1.0.22, serde@1.0.219, shlex@1.3.0, simple_logger@5.0.0, slab@0.4.11, smallvec@1.15.1, socket2@0.5.10, socket2@0.6.0, syn@2.0.106, sync_wrapper@1.0.2, syslog@7.0.0, tempfile@3.21.0, time-core@0.1.4, time-macros@0.2.22, time@0.3.41, tokio-macros@2.5.0, tokio-stream@0.1.17, tokio-util@0.7.16, tokio@1.47.1, tonic-build@0.13.1, tonic@0.13.1, tower-layer@0.3.3, tower-service@0.3.3, tower@0.4.13, tower@0.5.2, tracing-attributes@0.1.30, tracing-core@0.1.34, tracing@0.1.41, try-lock@0.2.5, unicode-ident@1.0.18, want@0.3.1 Homepage: https://github.com/ubuntu/authd Vcs-Browser: https://github.com/ubuntu/authd Vcs-Git: https://github.com/ubuntu/authd.git From 5160c500370e5c564a47204b8e1e272c8dbb566d Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 27 Aug 2025 23:01:12 +0200 Subject: [PATCH 0665/1670] nss: Use actual process name in syslog messages The NSS module is a library that's being used by other processes. Let's not pretend that the process name is "authd" but use the name of the executable of the process. This changes the messages from: authd[274318]: authd: Log output set to syslog to: getent[274416]: authd: Log output set to syslog --- nss/src/logs/mod.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/nss/src/logs/mod.rs b/nss/src/logs/mod.rs index 4f66b8f1cc..61021c591c 100644 --- a/nss/src/logs/mod.rs +++ b/nss/src/logs/mod.rs @@ -36,11 +36,18 @@ pub fn init_logger() { /// init_sys_logger initializes a global log that prints messages to the system logs. fn init_sys_logger(log_level: LevelFilter) { + // Derive the process name from current_exe(), fall back to a sensible default. + let process_name = std::env::current_exe() + .ok() + .and_then(|p| p.file_name().map(|s| s.to_string_lossy().into_owned())) + .filter(|s| !s.is_empty()) + .unwrap_or_else(|| "nss-authd".to_string()); + let formatter = Formatter3164 { facility: Facility::LOG_USER, hostname: None, - process: "authd".into(), - pid: 0, + process: process_name, + pid: std::process::id(), }; let logger = match syslog::unix(formatter) { From 7c66aa123bbb435b33fa15a8be1e8e55c07201cd Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 27 Aug 2025 23:03:02 +0200 Subject: [PATCH 0666/1670] nss: Prefix the syslog log messages with [nss-authd] Prefix messages printed to the syslog with a [nss-authd] tag, so that it's clear that it's the authd NSS module which prints these messages. Also, omit the date from messages printed to stderr. This changes messages printed to stderr from: 2025-08-27T21:17:23.792Z INFO [nss_authd::logs] authd: Log output set to stderr to: 11:44:45 INFO [nss-authd::logs] Log output set to stderr and messages printed to the syslog from: getent[274318]: authd: Log output set to syslog to: getent[274561]: [nss-authd] Log output set to syslog --- Cargo.lock | 2 ++ nss/Cargo.toml | 2 ++ nss/src/logs/mod.rs | 38 ++++++++++++++++++++++++-------------- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b43d060a3d..c54e5d471a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -667,11 +667,13 @@ dependencies = [ "libc", "libnss", "log", + "once_cell", "procfs", "prost", "rustix 1.0.8", "simple_logger", "syslog", + "time", "tokio", "tonic", "tonic-build", diff --git a/nss/Cargo.toml b/nss/Cargo.toml index ade4f39c34..6823c88541 100644 --- a/nss/Cargo.toml +++ b/nss/Cargo.toml @@ -31,6 +31,8 @@ syslog = "7.0.0" ctor = "0.4.3" procfs = "0.17.0" hyper-util = "0.1.16" +time = "0.3.41" +once_cell = "1.21.3" [build-dependencies] tonic-build = "0.13.*" diff --git a/nss/src/logs/mod.rs b/nss/src/logs/mod.rs index 61021c591c..2a228b6b8f 100644 --- a/nss/src/logs/mod.rs +++ b/nss/src/logs/mod.rs @@ -1,13 +1,15 @@ use log::{LevelFilter, Metadata}; +use once_cell::sync::OnceCell; use simple_logger::SimpleLogger; use std::env; use syslog::{BasicLogger, Facility, Formatter3164}; +pub static LOG_PREFIX: OnceCell<&'static str> = OnceCell::new(); + #[macro_export] macro_rules! info { ($($arg:tt)*) => { - let log_prefix = "authd:"; - log::info!("{} {}", log_prefix, format_args!($($arg)*)); + log::info!("{}{}", $crate::logs::LOG_PREFIX.get().copied().unwrap_or(""), format_args!($($arg)*)); } } @@ -50,26 +52,34 @@ fn init_sys_logger(log_level: LevelFilter) { pid: std::process::id(), }; - let logger = match syslog::unix(formatter) { - Err(err) => { - println!("cannot connect to syslog: {err:?}"); - return; - } - Ok(l) => l, + let logger = if let Ok(l) = syslog::unix(formatter) { + l + } else { + eprintln!("failed to create syslog logger"); + return; }; - if let Err(err) = log::set_boxed_logger(Box::new(BasicLogger::new(logger))) - .map(|()| log::set_max_level(log_level)) - { - eprintln!("cannot set log level: {err:?}"); + if let Err(err) = log::set_boxed_logger(Box::new(BasicLogger::new(logger))) { + eprintln!("failed to install global syslog logger: {err:?}"); return; - }; + } + log::set_max_level(log_level); + + LOG_PREFIX.set("[nss-authd] ").unwrap(); info!("Log output set to syslog"); } /// init_stderr_logger initializes a global log that prints the messages to stderr. fn init_stderr_logger(log_level: LevelFilter) { - SimpleLogger::new().with_level(log_level).init().unwrap(); + SimpleLogger::new() + .with_level(log_level) + .with_local_timestamps() + .with_timestamp_format(time::macros::format_description!( + "[hour]:[minute]:[second]" + )) + .init() + .unwrap(); + info!("Log output set to stderr"); } From 850c04727889115a61db60ec7564e80b3694ce7e Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 25 Aug 2025 16:59:16 +0200 Subject: [PATCH 0667/1670] pam/integration-tests: Avoid downloading chromium on each test run The $HOME environment variable wasn't set in the environment which the vhs command runs in, resulting in chromium being downloaded to `.cache/rod` in the current working directory, which is a temporary directory. In effect, chromium was being downloaded each time a test using vhs was executed, which made the test take ~1m longer. --- pam/integration-tests/vhs-helpers_test.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pam/integration-tests/vhs-helpers_test.go b/pam/integration-tests/vhs-helpers_test.go index 7425fa7701..f63c5be997 100644 --- a/pam/integration-tests/vhs-helpers_test.go +++ b/pam/integration-tests/vhs-helpers_test.go @@ -245,8 +245,14 @@ func (td tapeData) RunVhs(t *testing.T, testType vhsTestType, outDir string, cli cmd.Env = append(testutils.AppendCovEnv(cmd.Env), cliEnv...) cmd.Dir = outDir - // If vhs is installed with "go install", we need to add GOPATH to PATH. - cmd.Env = append(cmd.Env, prependBinToPath(t)) + cmd.Env = append(cmd.Env, + // If vhs is installed with "go install", we need to add GOPATH to PATH. + prependBinToPath(t), + // vhs uses rod, which downloads chromium to $HOME/.cache/rod, + // so $HOME needs to be set to avoid that it downloads it every time. + // TODO: Set XDG_CACHE_HOME instead once https://github.com/go-rod/rod/pull/1213 was merged + "HOME="+os.Getenv("HOME"), + ) u, err := user.Current() require.NoError(t, err, "Setup: getting current user") From 7eb1f7d470015d23b832784b45549f649f431594 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 28 Aug 2025 17:38:14 +0200 Subject: [PATCH 0668/1670] vhs: Always set a temporary home directory --- pam/integration-tests/ssh_test.go | 1 - pam/integration-tests/vhs-helpers_test.go | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pam/integration-tests/ssh_test.go b/pam/integration-tests/ssh_test.go index 90d99bd922..f677e7b0c4 100644 --- a/pam/integration-tests/ssh_test.go +++ b/pam/integration-tests/ssh_test.go @@ -461,7 +461,6 @@ Wait@%dms`, sshDefaultFinalWaitTimeout), td := newTapeData(tc.tape, append(defaultTapeSettings, tc.tapeSettings...)...) td.Command = tapeCommand td.Env[pam_test.RunnerEnvSupportsConversation] = "1" - td.Env["HOME"] = t.TempDir() td.Env[pamSSHUserEnv] = user td.Env["AUTHD_PAM_SSH_ARGS"] = strings.Join([]string{ "-p", sshdPort, diff --git a/pam/integration-tests/vhs-helpers_test.go b/pam/integration-tests/vhs-helpers_test.go index f63c5be997..0ed5ffa7d5 100644 --- a/pam/integration-tests/vhs-helpers_test.go +++ b/pam/integration-tests/vhs-helpers_test.go @@ -260,6 +260,9 @@ func (td tapeData) RunVhs(t *testing.T, testType vhsTestType, outDir string, cli cmd.Env = append(cmd.Env, "VHS_NO_SANDBOX=1") } + // Set a temporary HOME directory to avoid interference with the user's home directory. + td.Env["HOME"] = t.TempDir() + // Move some of the environment specific-variables from the tape to the launched process if e, ok := td.Env[pam_test.RunnerEnvLogFile]; ok { delete(td.Env, pam_test.RunnerEnvLogFile) From 8e4885766e1fc7428fd4ffec13e9cdc48ad73193 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 25 Aug 2025 22:07:40 +0200 Subject: [PATCH 0669/1670] tests: Print output of cargo build command Rebuilding the NSS library takes an unexpectedly long time, even if the code hasn't changed. Let's print the output of the cargo build command to get a better picture of what's going on. --- internal/testutils/rust.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/testutils/rust.go b/internal/testutils/rust.go index 7480142f00..fa9831a943 100644 --- a/internal/testutils/rust.go +++ b/internal/testutils/rust.go @@ -95,14 +95,16 @@ func BuildRustNSSLib(t *testing.T, disableCoverage bool, features ...string) (li "--features", strings.Join(features, ","), "--target-dir", target) cmd.Env = append(os.Environ(), rustCovEnv...) cmd.Dir = projectRoot + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr if isNightly && IsAsan() { cmd.Env = append(cmd.Env, "RUSTFLAGS=-Zsanitizer=address") } t.Log("Building NSS library...", cmd.Args) - out, err := cmd.CombinedOutput() - require.NoError(t, err, "Setup: could not build Rust NSS library: %s", out) + err = cmd.Run() + require.NoError(t, err, "Setup: could not build Rust NSS library") // When building the crate with dh-cargo, this env is set to indicate which architecture the code // is being compiled to. When it's set, the compiled is stored under target/$(DEB_HOST_RUST_TYPE)/debug, From 9583d71b4406d248bc3a8b58bbca652cf5c3e855 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 25 Aug 2025 18:17:51 +0200 Subject: [PATCH 0670/1670] Fix cargo always treating nss crate as dirty I couldn't figure out exactly why, but `cargo build` always treated the nss crate as dirty and re-compiled it even when nothing changed in between two runs: cargo build --verbose [...] Dirty nss v0.1.0 (/home/user/projects/authd/nss): the file `nss/..` has changed (1756139002.017575102s, 2s after last build at 1756139000.627582074s) Compiling nss v0.1.0 (/home/user/projects/authd/nss) Using "../internal/proto" instead of "../" as the includes directory in the compile_protos call fixes that. --- nss/build.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nss/build.rs b/nss/build.rs index 927ae589ba..2b9e79539d 100644 --- a/nss/build.rs +++ b/nss/build.rs @@ -2,7 +2,10 @@ fn main() -> Result<(), Box> { tonic_build::configure() .build_server(false) .protoc_arg("--experimental_allow_proto3_optional") - .compile_protos(&["../internal/proto/authd/authd.proto"], &["../"])?; + .compile_protos( + &["../internal/proto/authd/authd.proto"], + &["../internal/proto"], + )?; #[cfg(feature = "integration_tests")] cc::Build::new() From d2545dad3d76006ad3a096242fafa662d5ecf2f3 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 25 Aug 2025 22:00:23 +0200 Subject: [PATCH 0671/1670] tests: Build NSS library without verbose flag Now that the issue that the library was rebuilt each time has been solved, we don't the verbose output anymore. --- internal/testutils/rust.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/testutils/rust.go b/internal/testutils/rust.go index fa9831a943..f45f3ac030 100644 --- a/internal/testutils/rust.go +++ b/internal/testutils/rust.go @@ -91,8 +91,7 @@ func BuildRustNSSLib(t *testing.T, disableCoverage bool, features ...string) (li // Builds the nss library. // #nosec:G204 - we control the command arguments in tests - cmd := exec.Command(cargo, "build", "--verbose", - "--features", strings.Join(features, ","), "--target-dir", target) + cmd := exec.Command(cargo, "build", "--features", strings.Join(features, ","), "--target-dir", target) cmd.Env = append(os.Environ(), rustCovEnv...) cmd.Dir = projectRoot cmd.Stdout = os.Stdout From b990c4d0cee7bc18cd6cf7807eb507cfb16e10a8 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Tue, 26 Aug 2025 10:41:39 +0200 Subject: [PATCH 0672/1670] tests: Re-use Rust build artifacts by default Speeds up the tests if the developer did not set TEST_RUST_TARGET in the test environment. --- internal/testutils/rust.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/internal/testutils/rust.go b/internal/testutils/rust.go index f45f3ac030..ad39cd4915 100644 --- a/internal/testutils/rust.go +++ b/internal/testutils/rust.go @@ -74,13 +74,8 @@ func BuildRustNSSLib(t *testing.T, disableCoverage bool, features ...string) (li cargo, isNightly, err := getCargoPath() require.NoError(t, err, "Setup: looking for cargo") - // Note that for developing purposes and avoiding keeping building the rust program dependencies, - // TEST_RUST_TARGET environment variable can be set to an absolute path to keep iterative - // build artifacts. - target := os.Getenv("TEST_RUST_TARGET") - if target == "" { - target = t.TempDir() - } + // Store the build artifacts in a common temp directory, so that they can be reused between tests. + target := filepath.Join(os.TempDir(), "authd-tests-rust-build-artifacts") rustDir := filepath.Join(projectRoot, "nss") if !disableCoverage { From ff494b8836458f5062c193c2984a3168cc64b584 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 27 Aug 2025 12:12:11 +0200 Subject: [PATCH 0673/1670] fileutils: Add LockDir --- internal/fileutils/fileutils.go | 27 +++++++++++++++++ internal/fileutils/fileutils_test.go | 44 ++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/internal/fileutils/fileutils.go b/internal/fileutils/fileutils.go index d7f5f8815c..0a54facaba 100644 --- a/internal/fileutils/fileutils.go +++ b/internal/fileutils/fileutils.go @@ -7,6 +7,8 @@ import ( "io" "os" "path/filepath" + + "golang.org/x/sys/unix" ) // FileExists checks if a file exists at the given path. @@ -103,3 +105,28 @@ func Lrename(oldPath, newPath string) error { return os.Rename(oldPath, newPath) } + +// LockDir creates a lock file in the specified directory and acquires an exclusive lock on it. +// It blocks until the lock is available and returns an unlock function to release the lock. +func LockDir(dir string) (func() error, error) { + lockPath := filepath.Join(dir, ".lock") + f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0600) + if err != nil { + return nil, err + } + + if err := unix.Flock(int(f.Fd()), unix.LOCK_EX); err != nil { + _ = f.Close() + return nil, err + } + + unlock := func() error { + if err := unix.Flock(int(f.Fd()), unix.LOCK_UN); err != nil { + _ = f.Close() + return err + } + return f.Close() + } + + return unlock, nil +} diff --git a/internal/fileutils/fileutils_test.go b/internal/fileutils/fileutils_test.go index ddfebe7a3c..9195254a54 100644 --- a/internal/fileutils/fileutils_test.go +++ b/internal/fileutils/fileutils_test.go @@ -5,10 +5,12 @@ import ( "os" "path/filepath" "testing" + "time" "github.com/google/uuid" "github.com/stretchr/testify/require" "github.com/ubuntu/authd/internal/fileutils" + "github.com/ubuntu/authd/internal/testutils" ) // errAny represents any error type, for testing purposes. @@ -352,3 +354,45 @@ func TestLrename(t *testing.T) { }) } } + +func TestLockDir(t *testing.T) { + t.Parallel() + + tempDir := t.TempDir() + + // First lock should succeed + t.Log("Acquiring first lock") + unlock, err := fileutils.LockDir(tempDir) + require.NoError(t, err, "LockDir should not return an error") + + // Second lock should block, so we run it in a goroutine and check it doesn't return immediately + unlockCh := make(chan func() error, 1) + go func() { + t.Log("Acquiring second lock (should block)") + unlock2, err := fileutils.LockDir(tempDir) + t.Logf("Second LockDir returned with error: %v", err) + unlockCh <- unlock2 + }() + select { + case <-unlockCh: + require.Fail(t, "LockDir should block when trying to lock an already locked directory") + case <-time.After(testutils.MultipliedSleepDuration(100 * time.Millisecond)): + // Expected behavior, LockDir is blocking + } + + // Unlock the first lock + t.Log("Releasing first lock") + err = unlock() + require.NoError(t, err, "Unlock should not return an error") + + // Now we should be able to acquire the lock again + select { + case unlock = <-unlockCh: + // Expected behavior, LockDir returned after the first lock was released + t.Log("Releasing lock") + err = unlock() + require.NoError(t, err, "Unlock should not return an error") + case <-time.After(testutils.MultipliedSleepDuration(5 * time.Second)): + require.Fail(t, "LockDir should have returned after the first lock was released") + } +} From 415633740e4198cc10425b8cf2296152e1fbd1b2 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Wed, 27 Aug 2025 12:18:32 +0200 Subject: [PATCH 0674/1670] tests: Avoid races when building/using the NSS library * Lock the target directory to avoid multiple tests running in parallel trying to build the library at the same time (possibly with different set of features or other options). * Copy the resulting library to a temporary directory only used by the current test, to allow other tests to reuse the target directory (so that they don't have to rebuild it from scratch). --- internal/testutils/rust.go | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/internal/testutils/rust.go b/internal/testutils/rust.go index ad39cd4915..6480bc5b26 100644 --- a/internal/testutils/rust.go +++ b/internal/testutils/rust.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/ubuntu/authd/internal/fileutils" ) func getCargoPath() (path string, isNightly bool, err error) { @@ -75,7 +76,13 @@ func BuildRustNSSLib(t *testing.T, disableCoverage bool, features ...string) (li require.NoError(t, err, "Setup: looking for cargo") // Store the build artifacts in a common temp directory, so that they can be reused between tests. - target := filepath.Join(os.TempDir(), "authd-tests-rust-build-artifacts") + target := os.Getenv("TEST_RUST_TARGET") + if target == "" { + target = filepath.Join(os.TempDir(), "authd-tests-rust-build-artifacts") + } + + err = os.MkdirAll(target, 0700) + require.NoError(t, err, "Setup: could not create Rust target dir") rustDir := filepath.Join(projectRoot, "nss") if !disableCoverage { @@ -84,6 +91,12 @@ func BuildRustNSSLib(t *testing.T, disableCoverage bool, features ...string) (li features = append([]string{"integration_tests", "custom_socket"}, features...) + unlock, err := fileutils.LockDir(target) + require.NoError(t, err, "Setup: could not lock Rust target dir") + defer func() { + require.NoError(t, unlock(), "Setup: could not unlock Rust target dir") + }() + // Builds the nss library. // #nosec:G204 - we control the command arguments in tests cmd := exec.Command(cargo, "build", "--features", strings.Join(features, ","), "--target-dir", target) @@ -107,9 +120,10 @@ func BuildRustNSSLib(t *testing.T, disableCoverage bool, features ...string) (li // If the env is not set, the target stays the same. target = filepath.Join(target, os.Getenv("DEB_HOST_RUST_TYPE")) - // Creates a symlink for the compiled library with the expected versioned name. - libPath = filepath.Join(target, "libnss_authd.so.2") - if err = os.Symlink(filepath.Join(target, "debug", "libnss_authd.so"), libPath); err != nil { + // Copy the library with the expected versioned name to a temporary directory, so that we can safely use + // it from there after unlocking the target directory, which allows other tests to rebuild the library. + libPath = filepath.Join(t.TempDir(), "libnss_authd.so.2") + if err = fileutils.CopyFile(filepath.Join(target, "debug", "libnss_authd.so"), libPath); err != nil { require.ErrorIs(t, err, os.ErrExist, "Setup: failed to create versioned link to the library") } From 3d2b7887cad04c39d4e3fc9958ce9ebad81d5741 Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Thu, 28 Aug 2025 00:11:22 +0200 Subject: [PATCH 0675/1670] tests: Run cargo with --verbose flag if AUTHD_TEST_VERBOSITY is set --- internal/testutils/args.go | 16 ++++++++++++++++ internal/testutils/rust.go | 3 +++ 2 files changed, 19 insertions(+) diff --git a/internal/testutils/args.go b/internal/testutils/args.go index 949b314353..659e8a9dd8 100644 --- a/internal/testutils/args.go +++ b/internal/testutils/args.go @@ -16,8 +16,24 @@ var ( isRaceOnce sync.Once sleepMultiplier float64 sleepMultiplierOnce sync.Once + testVerbosity int + testVerbosityOnce sync.Once ) +// TestVerbosity returns the verbosity level that should be used in tests. +func TestVerbosity() int { + testVerbosityOnce.Do(func() { + if v := os.Getenv("AUTHD_TEST_VERBOSITY"); v != "" { + var err error + testVerbosity, err = strconv.Atoi(v) + if err != nil { + panic(err) + } + } + }) + return testVerbosity +} + func haveBuildFlag(flag string) bool { b, ok := debug.ReadBuildInfo() if !ok { diff --git a/internal/testutils/rust.go b/internal/testutils/rust.go index 6480bc5b26..9845034e1a 100644 --- a/internal/testutils/rust.go +++ b/internal/testutils/rust.go @@ -100,6 +100,9 @@ func BuildRustNSSLib(t *testing.T, disableCoverage bool, features ...string) (li // Builds the nss library. // #nosec:G204 - we control the command arguments in tests cmd := exec.Command(cargo, "build", "--features", strings.Join(features, ","), "--target-dir", target) + if TestVerbosity() > 0 { + cmd.Args = append(cmd.Args, "--verbose") + } cmd.Env = append(os.Environ(), rustCovEnv...) cmd.Dir = projectRoot cmd.Stdout = os.Stdout From 86743abec2daaa07d4ca04eaa9e41aefac804913 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Sep 2025 14:52:47 +0000 Subject: [PATCH 0676/1670] deps(go): bump github.com/stretchr/testify in the minor-updates group Bumps the minor-updates group with 1 update: [github.com/stretchr/testify](https://github.com/stretchr/testify). Updates `github.com/stretchr/testify` from 1.11.0 to 1.11.1 - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.11.0...v1.11.1) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-version: 1.11.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-updates ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6492035af0..67f30561fe 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/spf13/cobra v1.9.1 github.com/spf13/viper v1.20.1 - github.com/stretchr/testify v1.11.0 + github.com/stretchr/testify v1.11.1 github.com/ubuntu/authd v0.5.6 github.com/ubuntu/decorate v0.0.0-20240301153420-5015d6dbc8e5 github.com/ubuntu/go-i18n v0.0.0-20231113092927-594c1754ca47 diff --git a/go.sum b/go.sum index 5adf4ad24f..29f524f62d 100644 --- a/go.sum +++ b/go.sum @@ -101,8 +101,8 @@ github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3 h1:7hth9376EoQEd1hH4lAp3 github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3/go.mod h1:Z5KcoM0YLC7INlNhEezeIZ0TZNYf7WSNO0Lvah4DSeQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8= -github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/ubuntu/authd v0.5.6 h1:2QZuW2+bQEyz31s05fsZw8I8GbE22balR8zVcYssDKc= From 0a507dda84ba6864c111fed91e02faa50f95b12e Mon Sep 17 00:00:00 2001 From: Adrian Dombeck Date: Mon, 1 Sep 2025 18:23:52 +0200 Subject: [PATCH 0677/1670] ci: Use go/code-sanity@v1 We're about to merge a breaking change in the go/code-sanity GitHub Action (use of golangci-lint v2 instead of v1), so to avoid breaking the CI, we need to switch to go/code-sanity@v1 instead of go/code-sanity@main. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 733538dc40..41fa7ef652 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Go code sanity check - uses: canonical/desktop-engineering/gh-actions/go/code-sanity@main + uses: canonical/desktop-engineering/gh-actions/go/code-sanity@v1 with: golangci-lint-configfile: ".golangci.yaml" tools-directory: "tools" From 20a1686917d9073d7d51b6996cabbf98b4490a8e Mon Sep 17 00:00:00 2001 From: shanecrowley Date: Wed, 3 Sep 2025 11:31:16 +0100 Subject: [PATCH 0678/1670] add Google analytics for documentation - adds Google analytics tracker into HTML header - adds JS, HTML, and CSS for consent banner - updates config paths in conf.py --- docs/.sphinx/_static/cookie-banner.css | 82 +++++++++++++++++++++++ docs/.sphinx/_static/js/bundle.js | 1 + docs/.sphinx/_templates/footer.html | 92 ++++++++++++++++++++++++++ docs/.sphinx/_templates/header.html | 20 ++++++ docs/conf.py | 8 +-- 5 files changed, 198 insertions(+), 5 deletions(-) create mode 100644 docs/.sphinx/_static/cookie-banner.css create mode 100644 docs/.sphinx/_static/js/bundle.js create mode 100644 docs/.sphinx/_templates/footer.html diff --git a/docs/.sphinx/_static/cookie-banner.css b/docs/.sphinx/_static/cookie-banner.css new file mode 100644 index 0000000000..afcf7922a5 --- /dev/null +++ b/docs/.sphinx/_static/cookie-banner.css @@ -0,0 +1,82 @@ +/* Cookie policy styling WILL BE REMOVED when implementation of new theme with vanilla is implemented */ +.cookie-policy { + overflow: auto; + top: 35%; + z-index: 50; + position: fixed; +} + +dialog.cookie-policy { + background-color: var(--color-code-background); + color: var(--color-code-foreground); + height: auto; + max-height: 60vh; + max-width: 40rem; + padding: 0 1rem 0 1rem; + width: auto; +} + +header.p-modal__header { + margin-bottom: .5rem; +} + +header.p-modal__header::after { + background-color: #d9d9d9; + content: ""; + height: 1px; + left: 0; + margin-left: 1rem; + margin-right: 1rem; + position: absolute; + right: 0; +} + +h2#cookie-policy-title.p-modal__title { + align-self: flex-end; + font-size: 1.5rem; + font-style: normal; + font-weight: 275; + line-height: 2rem; + margin: 0 0 1.05rem 0; + padding: 0.45rem 0 0 0; +} + +.cookie-policy p { + font-size: 1rem; + line-height: 1.5rem; + margin-top: 0; + padding-top: .4rem; +} + +.cookie-policy p a { + text-decoration: none; + color: var(--color-link); +} +.cookie-policy button { + border-style: solid; + border-width: 1.5px; + cursor: pointer; + display: inline-block; + font-size: 1rem; + font-weight: 400; + justify-content: center; + line-height: 1.5rem; + padding: calc(.4rem - 1px) 1rem; + text-align: center; + text-decoration: none; + transition-duration: .1s; + transition-property: background-color,border-color; + transition-timing-function: cubic-bezier(0.55,0.055,0.675,0.19); +} + +.cookie-policy button { + background-color: #fff; + border-color: rgba(0,0,0,0.56); + color: #000; +} + +.cookie-policy .p-button--positive { + background-color: #0e8420; + border-color: #0e8420; + color: #fff; +} diff --git a/docs/.sphinx/_static/js/bundle.js b/docs/.sphinx/_static/js/bundle.js new file mode 100644 index 0000000000..a79e20ca87 --- /dev/null +++ b/docs/.sphinx/_static/js/bundle.js @@ -0,0 +1 @@ +(()=>{"use strict";function e(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,o)}return n}function t(t){for(var n=1;ncookie policy.',buttonAccept:"Accept all and visit site",buttonManage:"Manage your tracker settings"},manager:{title:"Tracking choices",body1:"We use cookies to recognise visitors and remember your preferences.",body2:"They enhance user experience, personalise content and ads, provide social media features, measure campaign effectiveness, and analyse site traffic.",body3:"Select the types of trackers you consent to, both by us, and third parties.",body4:'Learn more at data privacy: cookie policy - you can change your choices at any time from the footer of the site.',acceptAll:"Accept all",acceptAllHelp:'This will switch all toggles "ON".',SavePreferences:"Save preferences"}},zh:{notification:{title:"您的追踪器设置",body1:"我们使用cookie和相似的方法来识别访问者和记住偏好设置。我们也用它们来衡量活动的效果和网站流量分析。",body2:"选择”接受“,您同意我们和受信的第三方来使用这些方式。",body3:'更多内容或者随时地变更您的同意选择,请点击我们的 cookie策略.',buttonAccept:"接受全部和访问网站",buttonManage:"管理您的追踪器设置"},manager:{title:"追踪选项",body1:"我们使用cookie来识别访问者和记住您的偏好设置",body2:"它们增强用户体验,使内容和广告个性化,提供社交媒体功能,衡量活动效果和网站流量分析。",body3:"选择您同意授予我们和受信的第三方的追踪类型。",body4:'点击数据隐私:cookie策略了解更多,您可以在网站底部随时更改您的选择。',acceptAll:"接受全部",acceptAllHelp:"这将把全部开关变为”开启“。",SavePreferences:"保存偏好设置"}},ja:{notification:{title:"トラッキング機能の設定",body1:"当社は、当社のウェブサイトを訪問された方の識別や傾向の記録を行うために、クッキーおよび類似の手法を利用します。また、キャンペーンの効果の測定やサイトのトラフィックの分析にもクッキーを利用します。",body2:"「同意」を選択すると、当社および信頼できる第三者による上記の手法の利用に同意したものとみなされます。",body3:'詳細または同意の変更については、いつでも当社のクッキーに関するポリシーをご覧になることができます。',buttonAccept:"すべて同意してサイトにアクセス",buttonManage:"トラッキング機能の設定の管理"},manager:{title:"トラッキング機能の選択",body1:"当社は、当社のウェブサイトを訪問された方の識別や傾向の記録を行うために、クッキーを利用します。",body2:"クッキーは、お客様の利便性の向上、お客様に合わせたコンテンツや広告の表示、ソーシャルメディア機能の提供、キャンペーンの効果の測定、サイトのトラフィックの分析に役立ちます。",body3:"当社および第三者によるトラッキング機能のタイプから、お客様が同意されるものをお選びください。",body4:'詳細は、データプライバシー:クッキーに関するポリシーをご覧ください。お客様が選んだ設定は、本サイトの下部からいつでも変更できます。',acceptAll:"すべて同意",acceptAllHelp:"同意されるとすべての設定が「ON」に切り替わります。",SavePreferences:"設定を保存"}}},d={ad_storage:"denied",ad_user_data:"denied",ad_personalization:"denied",analytics_storage:"denied",functionality_storage:"denied",personalization_storage:"denied",security_storage:"denied"},u=["security_storage"],p=["ad_storage","ad_user_data","ad_personalization","analytics_storage"],f=["functionality_storage","personalization_storage"],h=["ad_storage","ad_user_data","ad_personalization","analytics_storage","functionality_storage","personalization_storage"],y=function(e){var t=new Date;t.setTime(t.getTime()+31536e6);var n="expires="+t.toUTCString();document.cookie="_cookies_accepted="+e+"; "+n+"; samesite=lax;path=/;",S(e)&&_()},b=function(){for(var e=document.cookie.split(";"),t="",n="",o=0;o\n ")}},{key:"render",value:function(e){this.container.innerHTML=this.getNotificationMarkup(e),this.initaliseListeners()}},{key:"initaliseListeners",value:function(){var e=this;this.container.querySelector(".js-close").addEventListener("click",(function(t){y("all"),v("all"),e.destroyComponent()})),this.container.querySelector(".js-manage").addEventListener("click",(function(t){e.renderManager()}))}}]),e}(),L=function(){function e(t,n,i){o(this,e),this.language=i,this.id=t.id,this.title=m(t,i).title,this.description=m(t,i).description,this.enableSwitcher=t.enableSwitcher,this.container=n,this.element,this.render()}return a(e,[{key:"render",value:function(){var e=this.cookieIsTrue(),t=document.createElement("div");t.classList.add("u-sv3"),t.innerHTML="\n ".concat(''),"\n

",this.title,"

\n

").concat(this.description,"

"),this.container.appendChild(t),this.element=t.querySelector(".js-".concat(this.id,"-switch"))}},{key:"cookieIsTrue",value:function(){var e=b();return!(!e||e!==this.id&&"all"!==e)||e&&e===this.id}},{key:"isChecked",value:function(){return!!this.element&&this.element.checked}},{key:"getId",value:function(){return this.id}}]),e}(),E=function(){function e(t,n){o(this,e),this.container=t,this.controlsStore=[],this.destroyComponent=n}return a(e,[{key:"getManagerMarkup",value:function(e){var t=g(e).manager;return'\n ")}},{key:"render",value:function(e){var t=this;this.container.innerHTML=this.getManagerMarkup(e);var n=this.container.querySelector(".controls");s.forEach((function(o){var i=new L(o,n,e);t.controlsStore.push(i)})),this.initaliseListeners()}},{key:"initaliseListeners",value:function(){var e=this;this.container.querySelector(".js-close").addEventListener("click",(function(){y("all"),v("all"),e.destroyComponent()})),this.container.querySelector(".js-save-preferences").addEventListener("click",(function(){e.savePreferences(),e.destroyComponent()}))}},{key:"savePreferences",value:function(){var e=this.controlsStore.filter((function(e){return e.isChecked()}));this.controlsStore.length===e.length?y("all"):this.controlsStore.forEach((function(e){e.isChecked()&&y(e.getId())})),function(e){var n=e.filter((function(e){return e.isChecked()})),o=t({},d);n.forEach((function(e){o=k(o,e.id)})),w(o)}(this.controlsStore)}}]),e}();window.gtag||(window.dataLayer=window.dataLayer||[],window.gtag=function(){dataLayer.push(arguments)},window.gtag("consent","default",d));var O=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,t=null,n=document.documentElement.lang,o=!1,i=function(e){e&&e.preventDefault(),null===t&&((t=document.createElement("dialog")).classList.add("cookie-policy"),t.setAttribute("open",!0),document.body.appendChild(t),new j(t,a,r).render(n),document.getElementById("cookie-policy-button-accept").focus())},a=function(){new E(t,r).render(n)},r=function(){"function"==typeof e&&e(),document.body.removeChild(t),t=null},c=function(){if(!o){var e;o=!0,(e=b())&&v(e);var t=document.querySelector(".js-revoke-cookie-manager");t&&t.addEventListener("click",i),function(){var e=b();return!e||"true"==e}()&&"hide"!==new URLSearchParams(window.location.search).get("cp")&&i()}};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",c,!1):c()},P=function(e){document.cookie.match(new RegExp("(^| )"+e+"=([^;]+)"))},A=P("_cookies_accepted");function M(){var e,t;if(("all"===(null===(e=A=P("_cookies_accepted"))||void 0===e?void 0:e[2])||"performance"===(null===(t=A)||void 0===t?void 0:t[2]))&&!P("user_id")){var n=([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,(function(e){return(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16)}));document.cookie="user_id="+n+";max-age=31536000;",dataLayer.push({user_id:n})}}A?(M(),O()):O(M)})(); diff --git a/docs/.sphinx/_templates/footer.html b/docs/.sphinx/_templates/footer.html new file mode 100644 index 0000000000..0143bf3c83 --- /dev/null +++ b/docs/.sphinx/_templates/footer.html @@ -0,0 +1,92 @@ + +
+
+ {%- if show_copyright %} + + {%- endif %} + + {# mod: removed "Made with" #} + + {%- if last_updated -%} +
+ {% trans last_updated=last_updated|e -%} + Last updated on {{ last_updated }} + {%- endtrans -%} +
+ {%- endif %} + + {%- if show_source and has_source and sourcename %} + + {%- endif %} +
+
+ {% if has_contributor_listing and display_contributors and pagename and page_source_suffix %} + {% set contributors = get_contributors_for_file(pagename, page_source_suffix) %} + {% if contributors %} + {% if contributors | length > 1 %} + Thanks to the {{ contributors |length }} contributors! + {% else %} + Thanks to our contributor! + {% endif %} +
+ + {% endif %} + {% endif %} +
+ +
diff --git a/docs/.sphinx/_templates/header.html b/docs/.sphinx/_templates/header.html index 30c52c5c8b..1c4e18b2a6 100644 --- a/docs/.sphinx/_templates/header.html +++ b/docs/.sphinx/_templates/header.html @@ -1,5 +1,25 @@