diff --git a/lib/config.go b/lib/config.go index 7e77630..6919ce1 100644 --- a/lib/config.go +++ b/lib/config.go @@ -142,15 +142,33 @@ func ParseConfig(filename string, flags *pflag.FlagSet) (*Config, error) { return nil, fmt.Errorf("invalid config: user %q: %w", cfg.Users[i].Username, err) } - if !v.IsSet(fmt.Sprintf("Users.%d.Permissions", i)) { + // Viper does not apply environment variables to fields of slice elements + // when unmarshaling, so explicitly set values are read back with Get. + userPermissionsKey := fmt.Sprintf("Users.%d.Permissions", i) + if isExplicitlySet(v, flags, userPermissionsKey) { + permissions := Permissions{} + err := permissions.UnmarshalText([]byte(v.GetString(userPermissionsKey))) + if err != nil { + return nil, fmt.Errorf("invalid config: user %q: %w", cfg.Users[i].Username, err) + } + cfg.Users[i].Permissions = permissions + } else { cfg.Users[i].Permissions = cfg.Permissions } - if !v.IsSet(fmt.Sprintf("Users.%d.RulesBehavior", i)) { + userRulesBehaviorKey := fmt.Sprintf("Users.%d.RulesBehavior", i) + if isExplicitlySet(v, flags, userRulesBehaviorKey) { + cfg.Users[i].RulesBehavior = RulesBehavior(v.GetString(userRulesBehaviorKey)) + } else { cfg.Users[i].RulesBehavior = cfg.RulesBehavior } - if v.IsSet(fmt.Sprintf("Users.%d.Rules", i)) { + userRulesKey := fmt.Sprintf("Users.%d.Rules", i) + if os.Getenv(envKey(userRulesKey)) != "" { + return nil, fmt.Errorf("invalid config: user %q: rules cannot be set via environment variables", cfg.Users[i].Username) + } + + if isExplicitlySet(v, flags, userRulesKey) { switch cfg.Users[i].RulesBehavior { case RulesOverwrite: // Do nothing @@ -208,11 +226,14 @@ func isExplicitlySet(v *viper.Viper, flags *pflag.FlagSet, key string) bool { return true } - envKey := "WD_" + strings.ToUpper(strings.ReplaceAll(key, ".", "_")) - value, ok := os.LookupEnv(envKey) + value, ok := os.LookupEnv(envKey(key)) return ok && value != "" } +func envKey(key string) string { + return "WD_" + strings.ToUpper(strings.ReplaceAll(key, ".", "_")) +} + func (c *Config) Validate() error { var err error diff --git a/lib/config_test.go b/lib/config_test.go index 4e94186..4c21529 100644 --- a/lib/config_test.go +++ b/lib/config_test.go @@ -387,6 +387,130 @@ users: require.Equal(t, DirectoryMounts{{Name: filepath.Base(userMulti), Path: userMulti}}, cfg.Users[2].Directories) } +func TestConfigUserEnvOverrides(t *testing.T) { + t.Setenv("WD_USERS_0_PERMISSIONS", "R") + t.Setenv("WD_USERS_1_PERMISSIONS", "none") + t.Setenv("WD_USERS_1_RULESBEHAVIOR", "overwrite") + + cfg := writeAndParseConfig(t, ` +directory: / +permissions: CRUD +rulesBehavior: append +rules: + - path: /secret + permissions: none +users: + - username: inherited + password: inherited + - username: own + password: own + permissions: CRUD + rules: + - path: /mine`, ".yml") + require.NoError(t, cfg.Validate()) + + require.Equal(t, Permissions{Read: true}, cfg.Users[0].Permissions) + require.Equal(t, RulesAppend, cfg.Users[0].RulesBehavior) + require.Len(t, cfg.Users[0].Rules, 1) + require.Equal(t, "/secret", cfg.Users[0].Rules[0].Path) + + require.Equal(t, Permissions{}, cfg.Users[1].Permissions) + require.Equal(t, RulesOverwrite, cfg.Users[1].RulesBehavior) + require.Len(t, cfg.Users[1].Rules, 1) + require.Equal(t, "/mine", cfg.Users[1].Rules[0].Path) +} + +func TestConfigUserRulesEnvRejected(t *testing.T) { + t.Setenv("WD_USERS_0_RULES", "placeholder") + + writeAndParseConfigWithError(t, ` +directory: / +rules: + - path: /secret + permissions: none +users: + - username: foo + password: bar`, ".yml", "rules cannot be set via environment variables") +} + +func TestConfigUserEnvPermissionsNone(t *testing.T) { + t.Setenv("WD_USERS_0_PERMISSIONS", "none") + + cfg := writeAndParseConfig(t, ` +directory: / +permissions: CRUD +rules: + - path: /secret + permissions: none +users: + - username: foo + password: bar`, ".yml") + require.NoError(t, cfg.Validate()) + + require.Equal(t, Permissions{}, cfg.Users[0].Permissions) + require.Len(t, cfg.Users[0].Rules, 1) + require.Equal(t, "/secret", cfg.Users[0].Rules[0].Path) +} + +func TestConfigUserEnvEmptyInherits(t *testing.T) { + t.Setenv("WD_USERS_0_PERMISSIONS", "") + t.Setenv("WD_USERS_0_RULESBEHAVIOR", "") + t.Setenv("WD_USERS_0_RULES", "") + + cfg := writeAndParseConfig(t, ` +directory: / +permissions: CRUD +rulesBehavior: append +rules: + - path: /secret + permissions: none +users: + - username: foo + password: bar`, ".yml") + require.NoError(t, cfg.Validate()) + + require.Equal(t, cfg.Permissions, cfg.Users[0].Permissions) + require.Equal(t, RulesAppend, cfg.Users[0].RulesBehavior) + require.Len(t, cfg.Users[0].Rules, 1) + require.Equal(t, "/secret", cfg.Users[0].Rules[0].Path) +} + +func TestConfigUserFileOverrides(t *testing.T) { + t.Parallel() + + cfg := writeAndParseConfig(t, ` +directory: / +permissions: CRUD +rulesBehavior: append +rules: + - path: /secret + permissions: none +users: + - username: none + password: none + permissions: none + - username: overwrite + password: overwrite + rulesBehavior: overwrite + rules: + - path: /mine + - username: empty + password: empty + rules:`, ".yml") + require.NoError(t, cfg.Validate()) + + require.Equal(t, Permissions{}, cfg.Users[0].Permissions) + require.Len(t, cfg.Users[0].Rules, 1) + + require.Equal(t, RulesOverwrite, cfg.Users[1].RulesBehavior) + require.Len(t, cfg.Users[1].Rules, 1) + require.Equal(t, "/mine", cfg.Users[1].Rules[0].Path) + + require.Equal(t, cfg.Permissions, cfg.Users[2].Permissions) + require.Len(t, cfg.Users[2].Rules, 1) + require.Equal(t, "/secret", cfg.Users[2].Rules[0].Path) +} + func TestConfigKeys(t *testing.T) { t.Parallel()