diff --git a/.gitignore b/.gitignore index 9c01e90..322ad7a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ tmp coverage.html coverage.out +.vscode diff --git a/internal/server/handlerCategory.go b/internal/server/handlerCategory.go index 9d0ee88..2c5688f 100644 --- a/internal/server/handlerCategory.go +++ b/internal/server/handlerCategory.go @@ -46,14 +46,16 @@ func _categoryHandler(s *shared, req *http.Request, res *response) (*response, * var config k8s.KubeConfig if data.ProjectName != "" && data.WorkspaceName != "" && data.McpName != "" { - config, err = openmcp.GetControlPlaneKubeconfig(s.crateKube, data.ProjectName, data.WorkspaceName, data.McpName, data.Authorization, crateKubeconfig) + config, err = openmcp.GetControlPlaneKubeconfig(s.crateKube, data.ProjectName, data.WorkspaceName, data.McpName, data.CrateAuthorizationToken, crateKubeconfig) if err != nil { slog.Error("failed to get control plane api config", "err", err) return nil, NewInternalServerError("failed to get control plane api config") } - if data.Authorization != "" { - config.SetUserToken(data.Authorization) + if data.McpAuthorizationToken == "" { + slog.Error("MCP authorization token not provided") + return nil, NewBadRequestError("MCP authorization token not provided") } + config.SetUserToken(data.McpAuthorizationToken) } else { slog.Error("either use %s: true or provide %s, %s and %s headers", useCrateClusterHeader, projectNameHeader, workspaceNameHeader, mcpName) return nil, NewBadRequestError( diff --git a/internal/server/handlerMain.go b/internal/server/handlerMain.go index 675b7be..80c7e40 100644 --- a/internal/server/handlerMain.go +++ b/internal/server/handlerMain.go @@ -54,7 +54,8 @@ type ExtractedRequestData struct { McpName string ContextName string UseCrateCluster bool - Authorization string + CrateAuthorizationToken string + McpAuthorizationToken string Headers map[string][]string JQ string Category string @@ -87,16 +88,18 @@ func mainHandler(s *shared, req *http.Request, res *response) (*response, *HttpE var config k8s.KubeConfig if data.UseCrateCluster { config = crateKubeconfig - config.SetUserToken(data.Authorization) + config.SetUserToken(data.CrateAuthorizationToken) } else if data.ProjectName != "" && data.WorkspaceName != "" && data.McpName != "" { - config, err = openmcp.GetControlPlaneKubeconfig(s.crateKube, data.ProjectName, data.WorkspaceName, data.McpName, data.Authorization, crateKubeconfig) + config, err = openmcp.GetControlPlaneKubeconfig(s.crateKube, data.ProjectName, data.WorkspaceName, data.McpName, data.CrateAuthorizationToken, crateKubeconfig) if err != nil { slog.Error("failed to get control plane api config", "err", err) return nil, NewInternalServerError("failed to get control plane api config") } - if data.Authorization != "" { - config.SetUserToken(data.Authorization) + if data.McpAuthorizationToken == "" { + slog.Error("MCP authorization token not provided") + return nil, NewBadRequestError("MCP authorization token not provided") } + config.SetUserToken(data.McpAuthorizationToken) } else { slog.Error("either use %s: true or provide %s, %s and %s headers", useCrateClusterHeader, projectNameHeader, workspaceNameHeader, mcpName) return nil, NewBadRequestError( @@ -138,6 +141,15 @@ func mainHandler(s *shared, req *http.Request, res *response) (*response, *HttpE } func extractRequestData(r *http.Request) (ExtractedRequestData, error) { + if r.Header.Get(authorizationHeader) == "" { + return ExtractedRequestData{}, fmt.Errorf("%s header is required", authorizationHeader) + } + + crateToken, mcpToken, err := parseAuthorizationHeaderWithDoubleTokens(r.Header.Get(authorizationHeader)) + if err != nil { + return ExtractedRequestData{}, fmt.Errorf("invalid %s header: %w", authorizationHeader, err) + } + rd := ExtractedRequestData{ Path: r.URL.Path, Query: r.URL.Query(), @@ -150,7 +162,8 @@ func extractRequestData(r *http.Request) (ExtractedRequestData, error) { WorkspaceName: r.Header.Get(workspaceNameHeader), ContextName: r.Header.Get(contextHeader), McpName: r.Header.Get(mcpName), - Authorization: r.Header.Get(authorizationHeader), + CrateAuthorizationToken: crateToken, + McpAuthorizationToken: mcpToken, JQ: r.Header.Get(jqHeader), Category: r.Header.Get(categoryHeader), } @@ -166,10 +179,6 @@ func extractRequestData(r *http.Request) (ExtractedRequestData, error) { rd.UseCrateCluster = useCrateCluster } - if rd.Authorization == "" { - return ExtractedRequestData{}, fmt.Errorf("%s header is required", authorizationHeader) - } - return rd, nil } diff --git a/internal/server/utils.go b/internal/server/utils.go index fba6f84..968e0f9 100644 --- a/internal/server/utils.go +++ b/internal/server/utils.go @@ -3,6 +3,7 @@ package server import ( "bytes" "encoding/json" + "fmt" "io" "net/http" "strings" @@ -103,3 +104,21 @@ func ParseJQ(inputJson []byte, inputJQ string) (string, error) { return strings.Join(result[:], "\n"), nil } + +// parseAuthorizationHeaderWithDoubleTokens parses an authorization header that may contain two tokens separated by a comma. +// It returns the first token and the second token (if present). If the second token is absent, it returns an empty string for it. +// If the header is empty or contains more than two tokens, it returns an error. +func parseAuthorizationHeaderWithDoubleTokens(authHeader string) (string, string, error) { + if authHeader == "" { + return "", "", fmt.Errorf("authorization header is empty") + } + + tokens := strings.Split(authHeader, ",") + if len(tokens) > 2 { + return "", "", fmt.Errorf("authorization header must contain two or less tokens separated by a space") + } + if len(tokens) == 1 { + return tokens[0], "", nil + } + return tokens[0], tokens[1], nil +} diff --git a/internal/server/utils_test.go b/internal/server/utils_test.go new file mode 100644 index 0000000..2f10963 --- /dev/null +++ b/internal/server/utils_test.go @@ -0,0 +1,41 @@ +package server + +import ( + "testing" +) + +func TestParseAuthorizationHeaderWithDoubleTokens(t *testing.T) { + tests := []struct { + authHeader string + token1 string + token2 string + expectErr bool + }{ + {"token1,token2", "token1", "token2", false}, + {"token1", "token1", "", false}, + {"", "", "", true}, + {"token1,token2,token3", "", "", true}, + } + + for _, test := range tests { + t.Run(test.authHeader, func(t *testing.T) { + token1, token2, err := parseAuthorizationHeaderWithDoubleTokens(test.authHeader) + + if test.expectErr { + if err == nil { + t.Errorf("expected an error but got none") + } + } else { + if err != nil { + t.Errorf("expected no error but got: %v", err) + } + if token1 != test.token1 { + t.Errorf("expected token1 to be %q but got %q", test.token1, token1) + } + if token2 != test.token2 { + t.Errorf("expected token2 to be %q but got %q", test.token2, token2) + } + } + }) + } +}