diff --git a/pkg/config/control_plane.go b/pkg/config/control_plane.go index 4475ec83b8..ccd4a6d136 100644 --- a/pkg/config/control_plane.go +++ b/pkg/config/control_plane.go @@ -77,7 +77,10 @@ func (s *SharedSSOConfig) UnmarshalJSON(data []byte) error { return err } - provider := m["provider"].(string) + provider, ok := m["provider"].(string) + if !ok { + return fmt.Errorf("provider field in SharedSSOConfig must be a string, got %T", m["provider"]) + } v, ok := model.ProjectSSOConfig_Provider_value[provider] if !ok { return fmt.Errorf("unsupported provider %s", provider) @@ -88,7 +91,11 @@ func (s *SharedSSOConfig) UnmarshalJSON(data []byte) error { if !ok { return fmt.Errorf("name field in SharedSSOConfig is required") } - s.Name = name.(string) + nameStr, ok := name.(string) + if !ok { + return fmt.Errorf("name field in SharedSSOConfig must be a string, got %T", name) + } + s.Name = nameStr delete(m, "name") data, err := json.Marshal(m) diff --git a/pkg/config/shared_sso_config_test.go b/pkg/config/shared_sso_config_test.go new file mode 100644 index 0000000000..d086248eec --- /dev/null +++ b/pkg/config/shared_sso_config_test.go @@ -0,0 +1,42 @@ +// Copyright 2024 The PipeCD Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "encoding/json" + "testing" +) + +// Each body previously panicked in UnmarshalJSON, which decodes into +// map[string]interface{} and asserts on the lookups. +func TestSharedSSOConfigMalformed(t *testing.T) { + for name, body := range map[string]string{ + "provider missing": `{"name":"github"}`, + "provider a number": `{"name":"github","provider":42}`, + "name a number": `{"name":42,"provider":"GITHUB"}`, + } { + t.Run(name, func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Fatalf("panicked on user config: %v", r) + } + }() + var c SharedSSOConfig + if err := json.Unmarshal([]byte(body), &c); err == nil { + t.Fatal("want an error, got nil") + } + }) + } +} diff --git a/pkg/configv1/control_plane.go b/pkg/configv1/control_plane.go index 4475ec83b8..ccd4a6d136 100644 --- a/pkg/configv1/control_plane.go +++ b/pkg/configv1/control_plane.go @@ -77,7 +77,10 @@ func (s *SharedSSOConfig) UnmarshalJSON(data []byte) error { return err } - provider := m["provider"].(string) + provider, ok := m["provider"].(string) + if !ok { + return fmt.Errorf("provider field in SharedSSOConfig must be a string, got %T", m["provider"]) + } v, ok := model.ProjectSSOConfig_Provider_value[provider] if !ok { return fmt.Errorf("unsupported provider %s", provider) @@ -88,7 +91,11 @@ func (s *SharedSSOConfig) UnmarshalJSON(data []byte) error { if !ok { return fmt.Errorf("name field in SharedSSOConfig is required") } - s.Name = name.(string) + nameStr, ok := name.(string) + if !ok { + return fmt.Errorf("name field in SharedSSOConfig must be a string, got %T", name) + } + s.Name = nameStr delete(m, "name") data, err := json.Marshal(m) diff --git a/pkg/configv1/shared_sso_config_test.go b/pkg/configv1/shared_sso_config_test.go new file mode 100644 index 0000000000..d086248eec --- /dev/null +++ b/pkg/configv1/shared_sso_config_test.go @@ -0,0 +1,42 @@ +// Copyright 2024 The PipeCD Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "encoding/json" + "testing" +) + +// Each body previously panicked in UnmarshalJSON, which decodes into +// map[string]interface{} and asserts on the lookups. +func TestSharedSSOConfigMalformed(t *testing.T) { + for name, body := range map[string]string{ + "provider missing": `{"name":"github"}`, + "provider a number": `{"name":"github","provider":42}`, + "name a number": `{"name":42,"provider":"GITHUB"}`, + } { + t.Run(name, func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Fatalf("panicked on user config: %v", r) + } + }() + var c SharedSSOConfig + if err := json.Unmarshal([]byte(body), &c); err == nil { + t.Fatal("want an error, got nil") + } + }) + } +}