package service import ( "context" "os" "path/filepath" "regexp" "testing" "time" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/authelia/authelia/v4/internal/authentication" "github.com/authelia/authelia/v4/internal/configuration/schema" "github.com/authelia/authelia/v4/internal/logging" "github.com/authelia/authelia/v4/internal/middlewares" "github.com/authelia/authelia/v4/internal/templates" ) func TestProvisionUsersFileWatcher(t *testing.T) { dir := t.TempDir() f, err := os.Create(filepath.Join(dir, "users.yml")) require.NoError(t, err) require.NoError(t, f.Close()) tx, err := templates.New(templates.Config{}) require.NoError(t, err) address, err := schema.NewAddress("tcp://:9091") require.NoError(t, err) config := &schema.Configuration{ Server: schema.Server{ Address: &schema.AddressTCP{Address: *address}, }, } provision := ProvisionUsersFileWatcher ctx := &testCtx{ Context: context.Background(), Configuration: config, Providers: middlewares.Providers{ Templates: tx, }, Logger: logrus.NewEntry(logging.Logger()), } watcher, err := provision(ctx) assert.NoError(t, err) assert.Nil(t, watcher) watcher, err = provision(ctx) assert.NoError(t, err) assert.Nil(t, watcher) config.AuthenticationBackend.File = &schema.AuthenticationBackendFile{ Path: filepath.Join(dir, "users.yml"), Watch: true, } watcher, err = provision(ctx) assert.EqualError(t, err, "error occurred asserting user provider") assert.Nil(t, watcher) ctx.Providers.UserProvider = authentication.NewFileUserProvider(config.AuthenticationBackend.File) config.AuthenticationBackend.File = &schema.AuthenticationBackendFile{ Watch: true, } watcher, err = provision(ctx) assert.EqualError(t, err, "error initializing file watcher: path must be specified") assert.Nil(t, watcher) config.AuthenticationBackend.File = &schema.AuthenticationBackendFile{ Path: filepath.Join(dir, "users.yml"), Watch: true, } watcher, err = provision(ctx) assert.NoError(t, err) assert.NotNil(t, watcher) assert.NotNil(t, watcher.Log()) assert.Equal(t, "users", watcher.ServiceName()) assert.Equal(t, "watcher", watcher.ServiceType()) watcher.Shutdown() } func TestNewFileWatcher(t *testing.T) { dir := t.TempDir() reloader := &testReloader{reload: true} f, err := os.Create(filepath.Join(dir, "test.log")) require.NoError(t, err) service, err := NewFileWatcher("example", filepath.Join(dir, "test.log"), reloader, logrus.NewEntry(logging.Logger())) assert.NoError(t, err) go func() { require.NoError(t, service.Run()) }() // Give the service a moment to start. time.Sleep(100 * time.Millisecond) _, err = f.Write([]byte("test")) require.NoError(t, err) require.NoError(t, f.Close()) time.Sleep(time.Second) assert.Equal(t, 1, reloader.count) assert.NoError(t, os.WriteFile(filepath.Join(dir, "test2.log"), []byte("test"), 0600)) assert.Equal(t, 1, reloader.count) assert.NoError(t, os.Remove(filepath.Join(dir, "test2.log"))) assert.Equal(t, 1, reloader.count) service.Shutdown() } func TestNewFileWatcherDirectory(t *testing.T) { dir := t.TempDir() reloader := &testReloader{reload: true} service, err := NewFileWatcher("example", dir, reloader, logrus.NewEntry(logging.Logger())) assert.NoError(t, err) go func() { require.NoError(t, service.Run()) }() // Give the service a moment to start. time.Sleep(100 * time.Millisecond) f, err := os.Create(filepath.Join(dir, "test.log")) require.NoError(t, err) _, err = f.Write([]byte("test")) require.NoError(t, err) require.NoError(t, f.Close()) time.Sleep(time.Second) assert.Equal(t, 2, reloader.count) service.Shutdown() } func TestNewFileWatcherBadPath(t *testing.T) { dir := t.TempDir() reloader := &testReloader{reload: true} service, err := NewFileWatcher("example", filepath.Join(dir, "test.log"), reloader, logrus.NewEntry(logging.Logger())) require.Error(t, err) assert.Regexp(t, regexp.MustCompile(`^error initializing file watcher: error stating file '/tmp/[^/]+/\d+/test.log': file does not exist$`), err.Error()) assert.Nil(t, service) } func TestNewFileWatcherBadPermission(t *testing.T) { dir := t.TempDir() reloader := &testReloader{reload: true} require.NoError(t, os.Mkdir(filepath.Join(dir, "tmp"), 0700)) f, err := os.Create(filepath.Join(dir, "tmp", "test.log")) require.NoError(t, err) require.NoError(t, f.Close()) require.NoError(t, os.Chmod(filepath.Join(dir, "tmp"), 0o000)) service, err := NewFileWatcher("example", filepath.Join(dir, "tmp", "test.log"), reloader, logrus.NewEntry(logging.Logger())) require.Error(t, err) assert.Regexp(t, regexp.MustCompile(`^error initializing file watcher: error stating file '/tmp/[^/]+/\d+/tmp/test.log': permission denied trying to read the file$`), err.Error()) require.NoError(t, os.Chmod(filepath.Join(dir, "tmp"), 0o700)) assert.Nil(t, service) } type testReloader struct { count int reload bool err error } func (r *testReloader) Reload() (bool, error) { r.count++ return r.reload, r.err }