diff --git a/internal/auth/roles.go b/internal/auth/roles.go index 6ca7c37a..bf6e3ca7 100644 --- a/internal/auth/roles.go +++ b/internal/auth/roles.go @@ -1,6 +1,7 @@ package auth import ( + "encoding/json" "strings" "github.com/rs/zerolog/log" @@ -18,9 +19,28 @@ const ( const All = Shell | Actions | Download // ParseRole parses a comma-separated string of roles and returns the corresponding Role. -func ParseRole(commaValues string) Role { +func ParseRole(input string) Role { var roles Role - for r := range strings.SplitSeq(commaValues, ",") { + var parts []string + + // Check if input is valid JSON + trimmed := strings.TrimSpace(input) + if json.Valid([]byte(trimmed)) { + var jsonRoles []string + if err := json.Unmarshal([]byte(trimmed), &jsonRoles); err == nil { + parts = jsonRoles + } else { + log.Warn().Str("input", input).Msg("failed to parse JSON roles") + return None + } + } else { + // Split by both commas and pipes + parts = strings.FieldsFunc(input, func(c rune) bool { + return c == ',' || c == '|' + }) + } + + for _, r := range parts { role := strings.TrimSpace(strings.ToLower(r)) switch role { case "shell": @@ -34,7 +54,7 @@ func ParseRole(commaValues string) Role { case "all": return All default: - log.Warn().Str("role", role).Msg("invalid role") + log.Debug().Str("role", role).Msg("invalid role") } } return roles diff --git a/internal/auth/roles_test.go b/internal/auth/roles_test.go new file mode 100644 index 00000000..9c971021 --- /dev/null +++ b/internal/auth/roles_test.go @@ -0,0 +1,62 @@ +package auth + +import ( + "testing" +) + +func TestParseRole(t *testing.T) { + testCases := []struct { + name string + input string + expected Role + }{ + // Single role tests + {"Single shell role", "shell", Shell}, + {"Single actions role", "actions", Actions}, + {"Single download role", "download", Download}, + {"None role", "none", None}, + {"All role", "all", All}, + + // Case insensitive tests + {"Shell uppercase", "SHELL", Shell}, + {"Actions mixed case", "AcTiOnS", Actions}, + {"Download with spaces", " download ", Download}, + + // Multiple roles with comma separator + {"Shell and actions", "shell,actions", Shell | Actions}, + {"All three roles", "shell,actions,download", Shell | Actions | Download}, + {"Roles with spaces", "shell , actions , download", Shell | Actions | Download}, + + // Multiple roles with pipe separator + {"Shell and actions with pipe", "shell|actions", Shell | Actions}, + {"All three with pipe", "shell|actions|download", Shell | Actions | Download}, + {"Mixed separators", "shell,actions|download", Shell | Actions | Download}, + + // JSON format tests + {"JSON single role", `["shell"]`, Shell}, + {"JSON multiple roles", `["shell", "actions"]`, Shell | Actions}, + {"JSON all roles", `["shell", "actions", "download"]`, Shell | Actions | Download}, + {"JSON with spaces", ` ["shell", "actions"] `, Shell | Actions}, + + // Edge cases + {"Empty string", "", None}, + {"Whitespace only", " ", None}, + {"Invalid role", "invalid", None}, + {"Mixed valid and invalid", "shell,invalid,actions", Shell | Actions}, + {"None overrides others", "shell,none,actions", None}, + {"All overrides others", "shell,all,actions", All}, + + // Invalid JSON + {"Invalid JSON format", `["shell"`, None}, + {"Malformed JSON", `{shell: "test"}`, None}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := ParseRole(tc.input) + if result != tc.expected { + t.Errorf("ParseRole(%q) = %d, expected %d", tc.input, int(result), int(tc.expected)) + } + }) + } +}