From 96dd8173fa61ff8e8f508171ff57913c0d2c347e Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Thu, 23 Jul 2026 11:44:03 -0700 Subject: [PATCH 01/13] fix(v24): accept day-name strings in SystemDayOfWeek unmarshal Keyfactor Command serializes WeeklyModel.Days as day-name strings (e.g. "Monday") in some API responses, but SystemDayOfWeek.UnmarshalJSON only accepted JSON integers, causing Weekly-scheduled resources to fail to deserialize. Try the integer form first, preserving existing enum validation, and fall back to the existing Parse() day-name mapping when the payload is a JSON string. Malformed strings and out-of-range ints still error clearly. Applies to both v1 and v2 API packages in this module. --- .../keyfactor/v1/model_system_day_of_week.go | 31 +++-- .../v1/model_system_day_of_week_test.go | 117 ++++++++++++++++++ .../keyfactor/v2/model_system_day_of_week.go | 31 +++-- .../v2/model_system_day_of_week_test.go | 117 ++++++++++++++++++ 4 files changed, 276 insertions(+), 20 deletions(-) create mode 100644 v24/api/keyfactor/v1/model_system_day_of_week_test.go create mode 100644 v24/api/keyfactor/v2/model_system_day_of_week_test.go diff --git a/v24/api/keyfactor/v1/model_system_day_of_week.go b/v24/api/keyfactor/v1/model_system_day_of_week.go index c137e17..be750a0 100644 --- a/v24/api/keyfactor/v1/model_system_day_of_week.go +++ b/v24/api/keyfactor/v1/model_system_day_of_week.go @@ -79,20 +79,31 @@ var AllowedSystemDayOfWeekEnumValues = []SystemDayOfWeek{ } func (v *SystemDayOfWeek) UnmarshalJSON(src []byte) error { + // Keyfactor Command has, across API versions, serialized this enum both + // as a JSON integer (e.g. 1) and as a day-name string (e.g. "Monday"). + // Try the integer form first to preserve the original generated + // validation against AllowedSystemDayOfWeekEnumValues. var value int32 - err := json.Unmarshal(src, &value) - if err != nil { - return err - } - enumTypeValue := SystemDayOfWeek(value) - for _, existing := range AllowedSystemDayOfWeekEnumValues { - if existing == enumTypeValue { - *v = enumTypeValue - return nil + if err := json.Unmarshal(src, &value); err == nil { + enumTypeValue := SystemDayOfWeek(value) + for _, existing := range AllowedSystemDayOfWeekEnumValues { + if existing == enumTypeValue { + *v = enumTypeValue + return nil + } } + + return fmt.Errorf("%+v is not a valid SystemDayOfWeek", value) + } + + // Fall back to the string form (e.g. "Monday") using the existing Parse + // helper, which maps day names to their enum values. + var strValue string + if err := json.Unmarshal(src, &strValue); err != nil { + return fmt.Errorf("SystemDayOfWeek must be a JSON integer or day-name string, got %s", string(src)) } - return fmt.Errorf("%+v is not a valid SystemDayOfWeek", value) + return v.Parse(strValue) } // NewSystemDayOfWeekFromValue returns a pointer to a valid SystemDayOfWeek diff --git a/v24/api/keyfactor/v1/model_system_day_of_week_test.go b/v24/api/keyfactor/v1/model_system_day_of_week_test.go new file mode 100644 index 0000000..2201cd8 --- /dev/null +++ b/v24/api/keyfactor/v1/model_system_day_of_week_test.go @@ -0,0 +1,117 @@ +/* +Copyright 2025 Keyfactor +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 v1 + +import ( + "encoding/json" + "testing" + "time" +) + +// TestSystemDayOfWeek_UnmarshalJSON_IntForm verifies that the original +// generated wire form (a JSON integer) still deserializes correctly. +func TestSystemDayOfWeek_UnmarshalJSON_IntForm(t *testing.T) { + var got SystemDayOfWeek + if err := json.Unmarshal([]byte(`1`), &got); err != nil { + t.Fatalf("unexpected error unmarshaling int form: %v", err) + } + if got != SYSTEMDAYOFWEEK_Monday { + t.Errorf("expected %v, got %v", SYSTEMDAYOFWEEK_Monday, got) + } +} + +// TestSystemDayOfWeek_UnmarshalJSON_StringForm covers the regression from +// GitHub issue #185: Keyfactor Command v25.5 serializes WeeklyModel.Days as +// day-name strings (e.g. "Monday") rather than integers. +func TestSystemDayOfWeek_UnmarshalJSON_StringForm(t *testing.T) { + var got SystemDayOfWeek + if err := json.Unmarshal([]byte(`"Monday"`), &got); err != nil { + t.Fatalf("unexpected error unmarshaling string form: %v", err) + } + if got != SYSTEMDAYOFWEEK_Monday { + t.Errorf("expected %v, got %v", SYSTEMDAYOFWEEK_Monday, got) + } +} + +// TestSystemDayOfWeek_UnmarshalJSON_InvalidString verifies a malformed day +// name still produces a clear error rather than silently defaulting. +func TestSystemDayOfWeek_UnmarshalJSON_InvalidString(t *testing.T) { + var got SystemDayOfWeek + err := json.Unmarshal([]byte(`"Funday"`), &got) + if err == nil { + t.Fatalf("expected error for invalid day-name string, got nil (value=%v)", got) + } +} + +// TestSystemDayOfWeek_UnmarshalJSON_OutOfRangeInt verifies an out-of-range +// integer still produces a clear error rather than silently accepting it. +func TestSystemDayOfWeek_UnmarshalJSON_OutOfRangeInt(t *testing.T) { + var got SystemDayOfWeek + err := json.Unmarshal([]byte(`42`), &got) + if err == nil { + t.Fatalf("expected error for out-of-range int, got nil (value=%v)", got) + } +} + +// TestWeeklyModel_UnmarshalJSON_DayNameStrings is the full round-trip +// regression test for issue #185: a Weekly-shaped schedule payload as +// returned by GET /CertificateAuthority on a v25.5 Command instance must +// deserialize into KeyfactorCommonSchedulingModelsWeeklyModel without error. +func TestWeeklyModel_UnmarshalJSON_DayNameStrings(t *testing.T) { + payload := `{"Days":["Monday","Friday"],"Time":"2000-01-01T07:00:00Z"}` + + var model KeyfactorCommonSchedulingModelsWeeklyModel + if err := json.Unmarshal([]byte(payload), &model); err != nil { + t.Fatalf("unexpected error unmarshaling WeeklyModel with day-name strings: %v", err) + } + + wantDays := []SystemDayOfWeek{SYSTEMDAYOFWEEK_Monday, SYSTEMDAYOFWEEK_Friday} + if len(model.Days) != len(wantDays) { + t.Fatalf("expected %d days, got %d (%v)", len(wantDays), len(model.Days), model.Days) + } + for i, want := range wantDays { + if model.Days[i] != want { + t.Errorf("Days[%d]: expected %v, got %v", i, want, model.Days[i]) + } + } + + wantTime, err := time.Parse(time.RFC3339, "2000-01-01T07:00:00Z") + if err != nil { + t.Fatalf("failed to parse expected time: %v", err) + } + if model.Time == nil || !model.Time.Equal(wantTime) { + t.Errorf("Time: expected %v, got %v", wantTime, model.Time) + } +} + +// TestWeeklyModel_UnmarshalJSON_DayIndexInts verifies the pre-v25.5 integer +// wire form of WeeklyModel.Days still round-trips correctly, guarding +// against a regression in the other direction. +func TestWeeklyModel_UnmarshalJSON_DayIndexInts(t *testing.T) { + payload := `{"Days":[1,5],"Time":"2000-01-01T07:00:00Z"}` + + var model KeyfactorCommonSchedulingModelsWeeklyModel + if err := json.Unmarshal([]byte(payload), &model); err != nil { + t.Fatalf("unexpected error unmarshaling WeeklyModel with int days: %v", err) + } + + wantDays := []SystemDayOfWeek{SYSTEMDAYOFWEEK_Monday, SYSTEMDAYOFWEEK_Friday} + if len(model.Days) != len(wantDays) { + t.Fatalf("expected %d days, got %d (%v)", len(wantDays), len(model.Days), model.Days) + } + for i, want := range wantDays { + if model.Days[i] != want { + t.Errorf("Days[%d]: expected %v, got %v", i, want, model.Days[i]) + } + } +} diff --git a/v24/api/keyfactor/v2/model_system_day_of_week.go b/v24/api/keyfactor/v2/model_system_day_of_week.go index bf9ae58..d2a4988 100644 --- a/v24/api/keyfactor/v2/model_system_day_of_week.go +++ b/v24/api/keyfactor/v2/model_system_day_of_week.go @@ -79,20 +79,31 @@ var AllowedSystemDayOfWeekEnumValues = []SystemDayOfWeek{ } func (v *SystemDayOfWeek) UnmarshalJSON(src []byte) error { + // Keyfactor Command has, across API versions, serialized this enum both + // as a JSON integer (e.g. 1) and as a day-name string (e.g. "Monday"). + // Try the integer form first to preserve the original generated + // validation against AllowedSystemDayOfWeekEnumValues. var value int32 - err := json.Unmarshal(src, &value) - if err != nil { - return err - } - enumTypeValue := SystemDayOfWeek(value) - for _, existing := range AllowedSystemDayOfWeekEnumValues { - if existing == enumTypeValue { - *v = enumTypeValue - return nil + if err := json.Unmarshal(src, &value); err == nil { + enumTypeValue := SystemDayOfWeek(value) + for _, existing := range AllowedSystemDayOfWeekEnumValues { + if existing == enumTypeValue { + *v = enumTypeValue + return nil + } } + + return fmt.Errorf("%+v is not a valid SystemDayOfWeek", value) + } + + // Fall back to the string form (e.g. "Monday") using the existing Parse + // helper, which maps day names to their enum values. + var strValue string + if err := json.Unmarshal(src, &strValue); err != nil { + return fmt.Errorf("SystemDayOfWeek must be a JSON integer or day-name string, got %s", string(src)) } - return fmt.Errorf("%+v is not a valid SystemDayOfWeek", value) + return v.Parse(strValue) } // NewSystemDayOfWeekFromValue returns a pointer to a valid SystemDayOfWeek diff --git a/v24/api/keyfactor/v2/model_system_day_of_week_test.go b/v24/api/keyfactor/v2/model_system_day_of_week_test.go new file mode 100644 index 0000000..eb2a288 --- /dev/null +++ b/v24/api/keyfactor/v2/model_system_day_of_week_test.go @@ -0,0 +1,117 @@ +/* +Copyright 2025 Keyfactor +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 v2 + +import ( + "encoding/json" + "testing" + "time" +) + +// TestSystemDayOfWeek_UnmarshalJSON_IntForm verifies that the original +// generated wire form (a JSON integer) still deserializes correctly. +func TestSystemDayOfWeek_UnmarshalJSON_IntForm(t *testing.T) { + var got SystemDayOfWeek + if err := json.Unmarshal([]byte(`1`), &got); err != nil { + t.Fatalf("unexpected error unmarshaling int form: %v", err) + } + if got != SYSTEMDAYOFWEEK_Monday { + t.Errorf("expected %v, got %v", SYSTEMDAYOFWEEK_Monday, got) + } +} + +// TestSystemDayOfWeek_UnmarshalJSON_StringForm covers the regression from +// GitHub issue #185: Keyfactor Command v25.5 serializes WeeklyModel.Days as +// day-name strings (e.g. "Monday") rather than integers. +func TestSystemDayOfWeek_UnmarshalJSON_StringForm(t *testing.T) { + var got SystemDayOfWeek + if err := json.Unmarshal([]byte(`"Monday"`), &got); err != nil { + t.Fatalf("unexpected error unmarshaling string form: %v", err) + } + if got != SYSTEMDAYOFWEEK_Monday { + t.Errorf("expected %v, got %v", SYSTEMDAYOFWEEK_Monday, got) + } +} + +// TestSystemDayOfWeek_UnmarshalJSON_InvalidString verifies a malformed day +// name still produces a clear error rather than silently defaulting. +func TestSystemDayOfWeek_UnmarshalJSON_InvalidString(t *testing.T) { + var got SystemDayOfWeek + err := json.Unmarshal([]byte(`"Funday"`), &got) + if err == nil { + t.Fatalf("expected error for invalid day-name string, got nil (value=%v)", got) + } +} + +// TestSystemDayOfWeek_UnmarshalJSON_OutOfRangeInt verifies an out-of-range +// integer still produces a clear error rather than silently accepting it. +func TestSystemDayOfWeek_UnmarshalJSON_OutOfRangeInt(t *testing.T) { + var got SystemDayOfWeek + err := json.Unmarshal([]byte(`42`), &got) + if err == nil { + t.Fatalf("expected error for out-of-range int, got nil (value=%v)", got) + } +} + +// TestWeeklyModel_UnmarshalJSON_DayNameStrings is the full round-trip +// regression test for issue #185: a Weekly-shaped schedule payload as +// returned by GET /CertificateAuthority on a v25.5 Command instance must +// deserialize into KeyfactorCommonSchedulingModelsWeeklyModel without error. +func TestWeeklyModel_UnmarshalJSON_DayNameStrings(t *testing.T) { + payload := `{"Days":["Monday","Friday"],"Time":"2000-01-01T07:00:00Z"}` + + var model KeyfactorCommonSchedulingModelsWeeklyModel + if err := json.Unmarshal([]byte(payload), &model); err != nil { + t.Fatalf("unexpected error unmarshaling WeeklyModel with day-name strings: %v", err) + } + + wantDays := []SystemDayOfWeek{SYSTEMDAYOFWEEK_Monday, SYSTEMDAYOFWEEK_Friday} + if len(model.Days) != len(wantDays) { + t.Fatalf("expected %d days, got %d (%v)", len(wantDays), len(model.Days), model.Days) + } + for i, want := range wantDays { + if model.Days[i] != want { + t.Errorf("Days[%d]: expected %v, got %v", i, want, model.Days[i]) + } + } + + wantTime, err := time.Parse(time.RFC3339, "2000-01-01T07:00:00Z") + if err != nil { + t.Fatalf("failed to parse expected time: %v", err) + } + if model.Time == nil || !model.Time.Equal(wantTime) { + t.Errorf("Time: expected %v, got %v", wantTime, model.Time) + } +} + +// TestWeeklyModel_UnmarshalJSON_DayIndexInts verifies the pre-v25.5 integer +// wire form of WeeklyModel.Days still round-trips correctly, guarding +// against a regression in the other direction. +func TestWeeklyModel_UnmarshalJSON_DayIndexInts(t *testing.T) { + payload := `{"Days":[1,5],"Time":"2000-01-01T07:00:00Z"}` + + var model KeyfactorCommonSchedulingModelsWeeklyModel + if err := json.Unmarshal([]byte(payload), &model); err != nil { + t.Fatalf("unexpected error unmarshaling WeeklyModel with int days: %v", err) + } + + wantDays := []SystemDayOfWeek{SYSTEMDAYOFWEEK_Monday, SYSTEMDAYOFWEEK_Friday} + if len(model.Days) != len(wantDays) { + t.Fatalf("expected %d days, got %d (%v)", len(wantDays), len(model.Days), model.Days) + } + for i, want := range wantDays { + if model.Days[i] != want { + t.Errorf("Days[%d]: expected %v, got %v", i, want, model.Days[i]) + } + } +} From e2633f6f62564aea1d0f79df29bd380b4105b9fb Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Thu, 23 Jul 2026 11:44:16 -0700 Subject: [PATCH 02/13] fix(v25): accept day-name strings in SystemDayOfWeek unmarshal Fixes keyfactor-pub/terraform-provider-keyfactor#185: Keyfactor Command v25.5 serializes WeeklyModel.Days as day-name strings (e.g. "Monday") in GET /CertificateAuthority responses, but SystemDayOfWeek.UnmarshalJSON only accepted JSON integers, causing any Weekly-scheduled CA to fail to deserialize. Try the integer form first, preserving existing enum validation, and fall back to the existing Parse() day-name mapping when the payload is a JSON string. Malformed strings and out-of-range ints still error clearly. Applies to both v1 and v2 API packages in this module. --- .../keyfactor/v1/model_system_day_of_week.go | 31 +++-- .../v1/model_system_day_of_week_test.go | 117 ++++++++++++++++++ .../keyfactor/v2/model_system_day_of_week.go | 31 +++-- .../v2/model_system_day_of_week_test.go | 117 ++++++++++++++++++ 4 files changed, 276 insertions(+), 20 deletions(-) create mode 100644 v25/api/keyfactor/v1/model_system_day_of_week_test.go create mode 100644 v25/api/keyfactor/v2/model_system_day_of_week_test.go diff --git a/v25/api/keyfactor/v1/model_system_day_of_week.go b/v25/api/keyfactor/v1/model_system_day_of_week.go index d8b8dc7..f29e86c 100644 --- a/v25/api/keyfactor/v1/model_system_day_of_week.go +++ b/v25/api/keyfactor/v1/model_system_day_of_week.go @@ -79,20 +79,31 @@ var AllowedSystemDayOfWeekEnumValues = []SystemDayOfWeek{ } func (v *SystemDayOfWeek) UnmarshalJSON(src []byte) error { + // Keyfactor Command has, across API versions, serialized this enum both + // as a JSON integer (e.g. 1) and as a day-name string (e.g. "Monday"). + // Try the integer form first to preserve the original generated + // validation against AllowedSystemDayOfWeekEnumValues. var value int32 - err := json.Unmarshal(src, &value) - if err != nil { - return err - } - enumTypeValue := SystemDayOfWeek(value) - for _, existing := range AllowedSystemDayOfWeekEnumValues { - if existing == enumTypeValue { - *v = enumTypeValue - return nil + if err := json.Unmarshal(src, &value); err == nil { + enumTypeValue := SystemDayOfWeek(value) + for _, existing := range AllowedSystemDayOfWeekEnumValues { + if existing == enumTypeValue { + *v = enumTypeValue + return nil + } } + + return fmt.Errorf("%+v is not a valid SystemDayOfWeek", value) + } + + // Fall back to the string form (e.g. "Monday") using the existing Parse + // helper, which maps day names to their enum values. + var strValue string + if err := json.Unmarshal(src, &strValue); err != nil { + return fmt.Errorf("SystemDayOfWeek must be a JSON integer or day-name string, got %s", string(src)) } - return fmt.Errorf("%+v is not a valid SystemDayOfWeek", value) + return v.Parse(strValue) } // NewSystemDayOfWeekFromValue returns a pointer to a valid SystemDayOfWeek diff --git a/v25/api/keyfactor/v1/model_system_day_of_week_test.go b/v25/api/keyfactor/v1/model_system_day_of_week_test.go new file mode 100644 index 0000000..2201cd8 --- /dev/null +++ b/v25/api/keyfactor/v1/model_system_day_of_week_test.go @@ -0,0 +1,117 @@ +/* +Copyright 2025 Keyfactor +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 v1 + +import ( + "encoding/json" + "testing" + "time" +) + +// TestSystemDayOfWeek_UnmarshalJSON_IntForm verifies that the original +// generated wire form (a JSON integer) still deserializes correctly. +func TestSystemDayOfWeek_UnmarshalJSON_IntForm(t *testing.T) { + var got SystemDayOfWeek + if err := json.Unmarshal([]byte(`1`), &got); err != nil { + t.Fatalf("unexpected error unmarshaling int form: %v", err) + } + if got != SYSTEMDAYOFWEEK_Monday { + t.Errorf("expected %v, got %v", SYSTEMDAYOFWEEK_Monday, got) + } +} + +// TestSystemDayOfWeek_UnmarshalJSON_StringForm covers the regression from +// GitHub issue #185: Keyfactor Command v25.5 serializes WeeklyModel.Days as +// day-name strings (e.g. "Monday") rather than integers. +func TestSystemDayOfWeek_UnmarshalJSON_StringForm(t *testing.T) { + var got SystemDayOfWeek + if err := json.Unmarshal([]byte(`"Monday"`), &got); err != nil { + t.Fatalf("unexpected error unmarshaling string form: %v", err) + } + if got != SYSTEMDAYOFWEEK_Monday { + t.Errorf("expected %v, got %v", SYSTEMDAYOFWEEK_Monday, got) + } +} + +// TestSystemDayOfWeek_UnmarshalJSON_InvalidString verifies a malformed day +// name still produces a clear error rather than silently defaulting. +func TestSystemDayOfWeek_UnmarshalJSON_InvalidString(t *testing.T) { + var got SystemDayOfWeek + err := json.Unmarshal([]byte(`"Funday"`), &got) + if err == nil { + t.Fatalf("expected error for invalid day-name string, got nil (value=%v)", got) + } +} + +// TestSystemDayOfWeek_UnmarshalJSON_OutOfRangeInt verifies an out-of-range +// integer still produces a clear error rather than silently accepting it. +func TestSystemDayOfWeek_UnmarshalJSON_OutOfRangeInt(t *testing.T) { + var got SystemDayOfWeek + err := json.Unmarshal([]byte(`42`), &got) + if err == nil { + t.Fatalf("expected error for out-of-range int, got nil (value=%v)", got) + } +} + +// TestWeeklyModel_UnmarshalJSON_DayNameStrings is the full round-trip +// regression test for issue #185: a Weekly-shaped schedule payload as +// returned by GET /CertificateAuthority on a v25.5 Command instance must +// deserialize into KeyfactorCommonSchedulingModelsWeeklyModel without error. +func TestWeeklyModel_UnmarshalJSON_DayNameStrings(t *testing.T) { + payload := `{"Days":["Monday","Friday"],"Time":"2000-01-01T07:00:00Z"}` + + var model KeyfactorCommonSchedulingModelsWeeklyModel + if err := json.Unmarshal([]byte(payload), &model); err != nil { + t.Fatalf("unexpected error unmarshaling WeeklyModel with day-name strings: %v", err) + } + + wantDays := []SystemDayOfWeek{SYSTEMDAYOFWEEK_Monday, SYSTEMDAYOFWEEK_Friday} + if len(model.Days) != len(wantDays) { + t.Fatalf("expected %d days, got %d (%v)", len(wantDays), len(model.Days), model.Days) + } + for i, want := range wantDays { + if model.Days[i] != want { + t.Errorf("Days[%d]: expected %v, got %v", i, want, model.Days[i]) + } + } + + wantTime, err := time.Parse(time.RFC3339, "2000-01-01T07:00:00Z") + if err != nil { + t.Fatalf("failed to parse expected time: %v", err) + } + if model.Time == nil || !model.Time.Equal(wantTime) { + t.Errorf("Time: expected %v, got %v", wantTime, model.Time) + } +} + +// TestWeeklyModel_UnmarshalJSON_DayIndexInts verifies the pre-v25.5 integer +// wire form of WeeklyModel.Days still round-trips correctly, guarding +// against a regression in the other direction. +func TestWeeklyModel_UnmarshalJSON_DayIndexInts(t *testing.T) { + payload := `{"Days":[1,5],"Time":"2000-01-01T07:00:00Z"}` + + var model KeyfactorCommonSchedulingModelsWeeklyModel + if err := json.Unmarshal([]byte(payload), &model); err != nil { + t.Fatalf("unexpected error unmarshaling WeeklyModel with int days: %v", err) + } + + wantDays := []SystemDayOfWeek{SYSTEMDAYOFWEEK_Monday, SYSTEMDAYOFWEEK_Friday} + if len(model.Days) != len(wantDays) { + t.Fatalf("expected %d days, got %d (%v)", len(wantDays), len(model.Days), model.Days) + } + for i, want := range wantDays { + if model.Days[i] != want { + t.Errorf("Days[%d]: expected %v, got %v", i, want, model.Days[i]) + } + } +} diff --git a/v25/api/keyfactor/v2/model_system_day_of_week.go b/v25/api/keyfactor/v2/model_system_day_of_week.go index 65de2b2..5b07ab4 100644 --- a/v25/api/keyfactor/v2/model_system_day_of_week.go +++ b/v25/api/keyfactor/v2/model_system_day_of_week.go @@ -79,20 +79,31 @@ var AllowedSystemDayOfWeekEnumValues = []SystemDayOfWeek{ } func (v *SystemDayOfWeek) UnmarshalJSON(src []byte) error { + // Keyfactor Command has, across API versions, serialized this enum both + // as a JSON integer (e.g. 1) and as a day-name string (e.g. "Monday"). + // Try the integer form first to preserve the original generated + // validation against AllowedSystemDayOfWeekEnumValues. var value int32 - err := json.Unmarshal(src, &value) - if err != nil { - return err - } - enumTypeValue := SystemDayOfWeek(value) - for _, existing := range AllowedSystemDayOfWeekEnumValues { - if existing == enumTypeValue { - *v = enumTypeValue - return nil + if err := json.Unmarshal(src, &value); err == nil { + enumTypeValue := SystemDayOfWeek(value) + for _, existing := range AllowedSystemDayOfWeekEnumValues { + if existing == enumTypeValue { + *v = enumTypeValue + return nil + } } + + return fmt.Errorf("%+v is not a valid SystemDayOfWeek", value) + } + + // Fall back to the string form (e.g. "Monday") using the existing Parse + // helper, which maps day names to their enum values. + var strValue string + if err := json.Unmarshal(src, &strValue); err != nil { + return fmt.Errorf("SystemDayOfWeek must be a JSON integer or day-name string, got %s", string(src)) } - return fmt.Errorf("%+v is not a valid SystemDayOfWeek", value) + return v.Parse(strValue) } // NewSystemDayOfWeekFromValue returns a pointer to a valid SystemDayOfWeek diff --git a/v25/api/keyfactor/v2/model_system_day_of_week_test.go b/v25/api/keyfactor/v2/model_system_day_of_week_test.go new file mode 100644 index 0000000..eb2a288 --- /dev/null +++ b/v25/api/keyfactor/v2/model_system_day_of_week_test.go @@ -0,0 +1,117 @@ +/* +Copyright 2025 Keyfactor +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 v2 + +import ( + "encoding/json" + "testing" + "time" +) + +// TestSystemDayOfWeek_UnmarshalJSON_IntForm verifies that the original +// generated wire form (a JSON integer) still deserializes correctly. +func TestSystemDayOfWeek_UnmarshalJSON_IntForm(t *testing.T) { + var got SystemDayOfWeek + if err := json.Unmarshal([]byte(`1`), &got); err != nil { + t.Fatalf("unexpected error unmarshaling int form: %v", err) + } + if got != SYSTEMDAYOFWEEK_Monday { + t.Errorf("expected %v, got %v", SYSTEMDAYOFWEEK_Monday, got) + } +} + +// TestSystemDayOfWeek_UnmarshalJSON_StringForm covers the regression from +// GitHub issue #185: Keyfactor Command v25.5 serializes WeeklyModel.Days as +// day-name strings (e.g. "Monday") rather than integers. +func TestSystemDayOfWeek_UnmarshalJSON_StringForm(t *testing.T) { + var got SystemDayOfWeek + if err := json.Unmarshal([]byte(`"Monday"`), &got); err != nil { + t.Fatalf("unexpected error unmarshaling string form: %v", err) + } + if got != SYSTEMDAYOFWEEK_Monday { + t.Errorf("expected %v, got %v", SYSTEMDAYOFWEEK_Monday, got) + } +} + +// TestSystemDayOfWeek_UnmarshalJSON_InvalidString verifies a malformed day +// name still produces a clear error rather than silently defaulting. +func TestSystemDayOfWeek_UnmarshalJSON_InvalidString(t *testing.T) { + var got SystemDayOfWeek + err := json.Unmarshal([]byte(`"Funday"`), &got) + if err == nil { + t.Fatalf("expected error for invalid day-name string, got nil (value=%v)", got) + } +} + +// TestSystemDayOfWeek_UnmarshalJSON_OutOfRangeInt verifies an out-of-range +// integer still produces a clear error rather than silently accepting it. +func TestSystemDayOfWeek_UnmarshalJSON_OutOfRangeInt(t *testing.T) { + var got SystemDayOfWeek + err := json.Unmarshal([]byte(`42`), &got) + if err == nil { + t.Fatalf("expected error for out-of-range int, got nil (value=%v)", got) + } +} + +// TestWeeklyModel_UnmarshalJSON_DayNameStrings is the full round-trip +// regression test for issue #185: a Weekly-shaped schedule payload as +// returned by GET /CertificateAuthority on a v25.5 Command instance must +// deserialize into KeyfactorCommonSchedulingModelsWeeklyModel without error. +func TestWeeklyModel_UnmarshalJSON_DayNameStrings(t *testing.T) { + payload := `{"Days":["Monday","Friday"],"Time":"2000-01-01T07:00:00Z"}` + + var model KeyfactorCommonSchedulingModelsWeeklyModel + if err := json.Unmarshal([]byte(payload), &model); err != nil { + t.Fatalf("unexpected error unmarshaling WeeklyModel with day-name strings: %v", err) + } + + wantDays := []SystemDayOfWeek{SYSTEMDAYOFWEEK_Monday, SYSTEMDAYOFWEEK_Friday} + if len(model.Days) != len(wantDays) { + t.Fatalf("expected %d days, got %d (%v)", len(wantDays), len(model.Days), model.Days) + } + for i, want := range wantDays { + if model.Days[i] != want { + t.Errorf("Days[%d]: expected %v, got %v", i, want, model.Days[i]) + } + } + + wantTime, err := time.Parse(time.RFC3339, "2000-01-01T07:00:00Z") + if err != nil { + t.Fatalf("failed to parse expected time: %v", err) + } + if model.Time == nil || !model.Time.Equal(wantTime) { + t.Errorf("Time: expected %v, got %v", wantTime, model.Time) + } +} + +// TestWeeklyModel_UnmarshalJSON_DayIndexInts verifies the pre-v25.5 integer +// wire form of WeeklyModel.Days still round-trips correctly, guarding +// against a regression in the other direction. +func TestWeeklyModel_UnmarshalJSON_DayIndexInts(t *testing.T) { + payload := `{"Days":[1,5],"Time":"2000-01-01T07:00:00Z"}` + + var model KeyfactorCommonSchedulingModelsWeeklyModel + if err := json.Unmarshal([]byte(payload), &model); err != nil { + t.Fatalf("unexpected error unmarshaling WeeklyModel with int days: %v", err) + } + + wantDays := []SystemDayOfWeek{SYSTEMDAYOFWEEK_Monday, SYSTEMDAYOFWEEK_Friday} + if len(model.Days) != len(wantDays) { + t.Fatalf("expected %d days, got %d (%v)", len(wantDays), len(model.Days), model.Days) + } + for i, want := range wantDays { + if model.Days[i] != want { + t.Errorf("Days[%d]: expected %v, got %v", i, want, model.Days[i]) + } + } +} From 2a6c5b4a62b26d1d4dc8950d469f928084121c9f Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Thu, 13 Aug 2026 18:49:59 -0700 Subject: [PATCH 03/13] fix(v24): plumb Server.ClientTimeout into rebuilt auth config buildHttpClientV2 in both v24/api/keyfactor/v1/client.go and v24/api/keyfactor/v2/client.go rebuilds a fresh CommandAuthConfig from the caller's *auth_providers.Server but never carried over ClientTimeout. Every consumer ended up authenticating and issuing requests with DefaultClientTimeout (60s) regardless of what was configured upstream (e.g. the Terraform provider's request_timeout), producing "net/http: timeout awaiting response headers" on long-running calls like PFX enrollment. Set HttpClientTimeout: cfg.ClientTimeout in both baseConfig literals. This is a hand-edit to generator output (see HAND_EDITS.md); do not drop it on regen. Depends on github.com/Keyfactor/keyfactor-auth-client-go#51 being fixed upstream (Server.ClientTimeout field). go.mod is bumped to the not-yet-tagged v1.6.0-rc.1 and pinned locally via a `replace` directive at /tmp/kf-worktrees/kfc-auth for testing; once that tag is cut, drop the replace and re-run `go mod tidy`. --- v24/api/keyfactor/v1/client.go | 11 +-- v24/api/keyfactor/v1/client_test.go | 70 +++++++++++++++ v24/api/keyfactor/v2/client.go | 11 +-- v24/api/keyfactor/v2/client_test.go | 70 +++++++++++++++ v24/go.mod | 43 ++++++---- v24/go.sum | 129 +++++++++++++++++++--------- 6 files changed, 268 insertions(+), 66 deletions(-) diff --git a/v24/api/keyfactor/v1/client.go b/v24/api/keyfactor/v1/client.go index ba208f4..667586d 100644 --- a/v24/api/keyfactor/v1/client.go +++ b/v24/api/keyfactor/v1/client.go @@ -317,11 +317,12 @@ func buildHttpClientV2(cfg *auth_providers.Server) (AuthConfig, error) { clientAuthType := cfg.GetAuthType() baseConfig := auth_providers.CommandAuthConfig{ - CommandHostName: cfg.Host, - CommandPort: cfg.Port, - CommandAPIPath: cfg.APIPath, - CommandCACert: cfg.CACertPath, - SkipVerify: cfg.SkipTLSVerify, + CommandHostName: cfg.Host, + CommandPort: cfg.Port, + CommandAPIPath: cfg.APIPath, + CommandCACert: cfg.CACertPath, + SkipVerify: cfg.SkipTLSVerify, + HttpClientTimeout: cfg.ClientTimeout, } if clientAuthType == "basic" { diff --git a/v24/api/keyfactor/v1/client_test.go b/v24/api/keyfactor/v1/client_test.go index fadf205..f91d1e9 100644 --- a/v24/api/keyfactor/v1/client_test.go +++ b/v24/api/keyfactor/v1/client_test.go @@ -1,8 +1,12 @@ package v1 import ( + "net/http" + "net/http/httptest" + "net/url" "reflect" "testing" + "time" "github.com/Keyfactor/keyfactor-auth-client-go/auth_providers" ) @@ -97,3 +101,69 @@ func TestCommandConfigOauth_AccessTokenOnlyNoClientCreds(t *testing.T) { t.Errorf("ClientSecret = %q, want empty", oauthCfg.ClientSecret) } } + +// newFakeCommandServer stands in for a Keyfactor Command instance for +// CommandAuthConfigBasic.Authenticate(), which performs a real GET against +// {host}/{apiPath}/Status/Endpoints as part of authentication. It always +// returns 200 with a valid JSON string array, regardless of credentials. +func newFakeCommandServer(t *testing.T) *httptest.Server { + t.Helper() + server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`["endpoint1"]`)) + })) + t.Cleanup(server.Close) + return server +} + +// TestBuildHttpClientV2_ClientTimeoutPropagation is a regression test for the +// bug where Server.ClientTimeout was silently dropped when buildHttpClientV2 +// rebuilt its own CommandAuthConfig, causing every caller (including the +// Terraform provider's request_timeout setting) to fall back to +// auth_providers.DefaultClientTimeout (60s) regardless of what was +// configured -- surfacing as "net/http: timeout awaiting response headers" on +// long-running calls such as PFX enrollment. Unlike +// TestCommandConfigOauth_AccessTokenFieldPropagation above (which mirrors +// buildHttpClientV2's lines to avoid the network call inside Authenticate()), +// this test exercises buildHttpClientV2 itself against a fake Command server. +func TestBuildHttpClientV2_ClientTimeoutPropagation(t *testing.T) { + server := newFakeCommandServer(t) + u, uErr := url.Parse(server.URL) + if uErr != nil { + t.Fatalf("failed to parse test server URL: %v", uErr) + } + + srv := &auth_providers.Server{ + Host: u.Host, + Username: "user", + Password: "pass", + APIPath: "api", + SkipTLSVerify: true, + ClientTimeout: 300, + } + + authCfg, err := buildHttpClientV2(srv) + if err != nil { + t.Fatalf("buildHttpClientV2() returned unexpected error: %v", err) + } + + basicCfg, ok := authCfg.(*auth_providers.CommandAuthConfigBasic) + if !ok { + t.Fatalf("expected AuthConfig to be *auth_providers.CommandAuthConfigBasic, got %T", authCfg) + } + + if basicCfg.HttpClientTimeout != 300 { + t.Errorf("CommandAuthConfigBasic.HttpClientTimeout = %d, want %d", basicCfg.HttpClientTimeout, 300) + } + + transport, tErr := basicCfg.CommandAuthConfig.BuildTransport() + if tErr != nil { + t.Fatalf("BuildTransport() returned unexpected error: %v", tErr) + } + + expected := 300 * time.Second + if transport.ResponseHeaderTimeout != expected { + t.Errorf("ResponseHeaderTimeout = %v, want %v", transport.ResponseHeaderTimeout, expected) + } +} diff --git a/v24/api/keyfactor/v2/client.go b/v24/api/keyfactor/v2/client.go index 70f0a56..a65a230 100644 --- a/v24/api/keyfactor/v2/client.go +++ b/v24/api/keyfactor/v2/client.go @@ -129,11 +129,12 @@ func buildHttpClientV2(cfg *auth_providers.Server) (AuthConfig, error) { clientAuthType := cfg.GetAuthType() baseConfig := auth_providers.CommandAuthConfig{ - CommandHostName: cfg.Host, - CommandPort: cfg.Port, - CommandAPIPath: cfg.APIPath, - CommandCACert: cfg.CACertPath, - SkipVerify: cfg.SkipTLSVerify, + CommandHostName: cfg.Host, + CommandPort: cfg.Port, + CommandAPIPath: cfg.APIPath, + CommandCACert: cfg.CACertPath, + SkipVerify: cfg.SkipTLSVerify, + HttpClientTimeout: cfg.ClientTimeout, } if clientAuthType == "basic" { diff --git a/v24/api/keyfactor/v2/client_test.go b/v24/api/keyfactor/v2/client_test.go index 6a58a66..f8803c8 100644 --- a/v24/api/keyfactor/v2/client_test.go +++ b/v24/api/keyfactor/v2/client_test.go @@ -1,8 +1,12 @@ package v2 import ( + "net/http" + "net/http/httptest" + "net/url" "reflect" "testing" + "time" "github.com/Keyfactor/keyfactor-auth-client-go/auth_providers" ) @@ -97,3 +101,69 @@ func TestCommandConfigOauth_AccessTokenOnlyNoClientCreds(t *testing.T) { t.Errorf("ClientSecret = %q, want empty", oauthCfg.ClientSecret) } } + +// newFakeCommandServer stands in for a Keyfactor Command instance for +// CommandAuthConfigBasic.Authenticate(), which performs a real GET against +// {host}/{apiPath}/Status/Endpoints as part of authentication. It always +// returns 200 with a valid JSON string array, regardless of credentials. +func newFakeCommandServer(t *testing.T) *httptest.Server { + t.Helper() + server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`["endpoint1"]`)) + })) + t.Cleanup(server.Close) + return server +} + +// TestBuildHttpClientV2_ClientTimeoutPropagation is a regression test for the +// bug where Server.ClientTimeout was silently dropped when buildHttpClientV2 +// rebuilt its own CommandAuthConfig, causing every caller (including the +// Terraform provider's request_timeout setting) to fall back to +// auth_providers.DefaultClientTimeout (60s) regardless of what was +// configured -- surfacing as "net/http: timeout awaiting response headers" on +// long-running calls such as PFX enrollment. Unlike +// TestCommandConfigOauth_AccessTokenFieldPropagation above (which mirrors +// buildHttpClientV2's lines to avoid the network call inside Authenticate()), +// this test exercises buildHttpClientV2 itself against a fake Command server. +func TestBuildHttpClientV2_ClientTimeoutPropagation(t *testing.T) { + server := newFakeCommandServer(t) + u, uErr := url.Parse(server.URL) + if uErr != nil { + t.Fatalf("failed to parse test server URL: %v", uErr) + } + + srv := &auth_providers.Server{ + Host: u.Host, + Username: "user", + Password: "pass", + APIPath: "api", + SkipTLSVerify: true, + ClientTimeout: 300, + } + + authCfg, err := buildHttpClientV2(srv) + if err != nil { + t.Fatalf("buildHttpClientV2() returned unexpected error: %v", err) + } + + basicCfg, ok := authCfg.(*auth_providers.CommandAuthConfigBasic) + if !ok { + t.Fatalf("expected AuthConfig to be *auth_providers.CommandAuthConfigBasic, got %T", authCfg) + } + + if basicCfg.HttpClientTimeout != 300 { + t.Errorf("CommandAuthConfigBasic.HttpClientTimeout = %d, want %d", basicCfg.HttpClientTimeout, 300) + } + + transport, tErr := basicCfg.CommandAuthConfig.BuildTransport() + if tErr != nil { + t.Fatalf("BuildTransport() returned unexpected error: %v", tErr) + } + + expected := 300 * time.Second + if transport.ResponseHeaderTimeout != expected { + t.Errorf("ResponseHeaderTimeout = %v, want %v", transport.ResponseHeaderTimeout, expected) + } +} diff --git a/v24/go.mod b/v24/go.mod index 216dadf..cd07473 100644 --- a/v24/go.mod +++ b/v24/go.mod @@ -1,26 +1,37 @@ module github.com/Keyfactor/keyfactor-go-client-sdk/v24 -go 1.22 +go 1.24.0 -toolchain go1.24.0 - -require github.com/Keyfactor/keyfactor-auth-client-go v1.1.0-rc.8 +// TODO(fix/server-client-timeout): bump to v1.6.0-rc.1 once that tag is cut +// upstream (fixes Server.ClientTimeout plumbing, see +// https://github.com/Keyfactor/keyfactor-auth-client-go/issues/51), then +// remove the local `replace` below and re-run `go mod tidy`. +require github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.1 require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.3.1 // indirect - github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/jcmturner/aescts/v2 v2.0.0 // indirect + github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect + github.com/jcmturner/gofork v1.7.6 // indirect + github.com/jcmturner/goidentity/v6 v6.0.1 // indirect + github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect + github.com/jcmturner/rpc/v2 v2.0.3 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect - golang.org/x/crypto v0.28.0 // indirect - golang.org/x/net v0.30.0 // indirect - golang.org/x/oauth2 v0.24.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) + +replace github.com/Keyfactor/keyfactor-auth-client-go => /tmp/kf-worktrees/kfc-auth diff --git a/v24/go.sum b/v24/go.sum index ba2f2cd..5cf8578 100644 --- a/v24/go.sum +++ b/v24/go.sum @@ -1,35 +1,47 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI= -github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw= -github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.0 h1:WLUIpeyv04H0RCcQHaA4TNoyrQ39Ox7V+re+iaqzTe0= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.0/go.mod h1:hd8hTTIY3VmUVPRHNH7GVCHO3SHgXkJKZHReby/bnUQ= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0 h1:eXnN9kaS8TiDwXjoie3hMRLuwdUBUMW9KRgOqB3mCaw= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.0/go.mod h1:XIpam8wumeZ5rVMuhdDQLMfIPDf1WO3IzrCRO3e3e3o= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 h1:/g8S6wk65vfC6m3FIxJ+i5QDyN9JWwXI8Hb0Img10hU= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0/go.mod h1:gpl+q95AzZlKVI3xSoseF9QPrypk0hQqBiJYeB/cR/I= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.3.1 h1:gUDtaZk8heteyfdmv+pcfHvhR9llnh7c7GMwZ8RVG04= -github.com/AzureAD/microsoft-authentication-library-for-go v1.3.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= -github.com/Keyfactor/keyfactor-auth-client-go v1.1.0-rc.8 h1:JtA7UwqCSsqBUc0shlEBk+g2xNMorxwQHtknCY7hcUg= -github.com/Keyfactor/keyfactor-auth-client-go v1.1.0-rc.8/go.mod h1:dUIVnqWpPiYkGqKMYQi6Z98fqzQdkZ1KHvJpCoSLQ2s= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= -github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -40,27 +52,64 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmd github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= -github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= -golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= -golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 3952fca4d44c7433b83d045c252858fb9bc69fa2 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Thu, 13 Aug 2026 18:50:40 -0700 Subject: [PATCH 04/13] docs(sdk): catalog v24 client.go ClientTimeout hand-edit Establishes HAND_EDITS.md for this branch, scoped to v24 (no v24 swagger exists yet, so it isn't covered by any regen pipeline). Documents commit 2a6c5b4 so a future regeneration effort doesn't silently drop the Server.ClientTimeout plumbing fix. --- HAND_EDITS.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 HAND_EDITS.md diff --git a/HAND_EDITS.md b/HAND_EDITS.md new file mode 100644 index 0000000..c279965 --- /dev/null +++ b/HAND_EDITS.md @@ -0,0 +1,24 @@ +# Hand-Edits to Generated SDK Code + +This file catalogs commits that modified files inside `v24/api/keyfactor/v{1,2}/` (and, in future, other version directories) after their initial generation. These files carry generator-output "DO NOT EDIT" headers, but the project's `.openapi-generator-ignore` files are empty — no protection mechanism is in place. **Without the right templates and swagger patches, naive regeneration would silently drop every hand-edit listed below.** + +## Conventions + +For each file, hand-edits are listed in commit order (oldest first). Each entry notes: + +- **Commit SHA + subject** — recover the full diff with `git show -- `. +- **What it changed** — brief description. +- **Reproduced by upstream swagger?** — Yes if the swagger definition already implies the same shape; No if it does not (i.e. this is pure Go logic with no swagger counterpart). +- **Regression test pins this?** — Yes if a `*_test.go` test would fail without the hand-edit. +- **Action on regen** — `preserve` (must be re-applied post-regen), `verify` (re-check whether reproduced), `obsolete` (intentional removal), `docs-only` (no behavioral impact). + +--- + +## v24 (out of scope for any current regen — no v24 swagger has been supplied) + +### `v24/api/keyfactor/v1/client.go` + `v24/api/keyfactor/v2/client.go` + +1. **`2a6c5b4`** — *fix(v24): plumb Server.ClientTimeout into rebuilt auth config* — inside `buildHttpClientV2()`, adds `HttpClientTimeout: cfg.ClientTimeout` to the `baseConfig := auth_providers.CommandAuthConfig{...}` struct literal in both files. Without it, `Server.ClientTimeout` (added upstream by `keyfactor-auth-client-go` to fix [issue #51](https://github.com/Keyfactor/keyfactor-auth-client-go/issues/51)) was silently dropped when this SDK rebuilt its own `CommandAuthConfig`, so every caller — including the Terraform provider's `request_timeout` setting — fell back to `auth_providers.DefaultClientTimeout` (60s) regardless of what was configured. This surfaced as `net/http: timeout awaiting response headers` on long-running calls such as PFX enrollment. + - Reproduced by upstream swagger: **No** — pure Go logic, no swagger counterpart. + - Pinned by test: **Yes** — `TestBuildHttpClientV2_ClientTimeoutPropagation` in `v24/api/keyfactor/v1/client_test.go` and `v24/api/keyfactor/v2/client_test.go` calls `buildHttpClientV2()` against a fake Command server and asserts the resulting `CommandAuthConfigBasic.HttpClientTimeout` and derived `BuildTransport().ResponseHeaderTimeout` reflect the configured value. + - Action: **preserve**. From a1f639e3ee48feb312de5fff9cb097716f3abecc Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Fri, 14 Aug 2026 08:08:47 -0700 Subject: [PATCH 05/13] chore(deps): bump keyfactor-auth-client-go to v1.6.0-rc.2 Removes the local replace directive and TODO now that the ClientTimeout fix is published, and validates against the published dependency. Vet disabled for the test run due to a pre-existing non-constant format string in generated configuration.go (unrelated to this change). --- v24/go.mod | 8 +------- v24/go.sum | 2 ++ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/v24/go.mod b/v24/go.mod index cd07473..3ed3a82 100644 --- a/v24/go.mod +++ b/v24/go.mod @@ -2,11 +2,7 @@ module github.com/Keyfactor/keyfactor-go-client-sdk/v24 go 1.24.0 -// TODO(fix/server-client-timeout): bump to v1.6.0-rc.1 once that tag is cut -// upstream (fixes Server.ClientTimeout plumbing, see -// https://github.com/Keyfactor/keyfactor-auth-client-go/issues/51), then -// remove the local `replace` below and re-run `go mod tidy`. -require github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.1 +require github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.2 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect @@ -33,5 +29,3 @@ require ( golang.org/x/text v0.33.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) - -replace github.com/Keyfactor/keyfactor-auth-client-go => /tmp/kf-worktrees/kfc-auth diff --git a/v24/go.sum b/v24/go.sum index 5cf8578..af2a2f3 100644 --- a/v24/go.sum +++ b/v24/go.sum @@ -14,6 +14,8 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= +github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.2 h1:wp7LBuNSpHZYPlzEuipNeuWwwBow8lgLj8lD2gMivhM= +github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.2/go.mod h1:rFBZPMSHWwWuUwE1kXhLsDaOxjGiHMbXTTEni8Dmufo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= From bf733a2c743debfce95658d865a41c18fc276bcf Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Fri, 14 Aug 2026 09:14:11 -0700 Subject: [PATCH 06/13] test(v24): make ClientTimeout regression test hermetic against ambient env ValidateAuthConfig (keyfactor-auth-client-go) unconditionally overwrites SkipVerify from KEYFACTOR_SKIP_VERIFY whenever the variable is merely present, regardless of value. Anything other than exactly "true"/"1" (e.g. "TRUE", "false", "0", or an empty string sourced from a lab env file) flips SkipTLSVerify back to false, so TestBuildHttpClientV2_ClientTimeoutPropagation failed against its own self-signed httptest server with "x509: certificate signed by unknown authority" on any machine with such a value exported. Pin KEYFACTOR_SKIP_VERIFY to "true" for the duration of the test and neutralize KEYFACTOR_CA_CERT (a stale path would make BuildTransport fail) and KEYFACTOR_CLIENT_TIMEOUT (defense-in-depth) via a proper unset-and-restore helper, since t.Setenv(key, "") does not unset a variable for os.LookupEnv purposes. Verified green with KEYFACTOR_SKIP_VERIFY set to false, TRUE, 0, and empty string, in both v1 and v2 packages. --- v24/api/keyfactor/v1/client_test.go | 46 +++++++++++++++++++++++++++++ v24/api/keyfactor/v2/client_test.go | 46 +++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/v24/api/keyfactor/v1/client_test.go b/v24/api/keyfactor/v1/client_test.go index f91d1e9..f4c549a 100644 --- a/v24/api/keyfactor/v1/client_test.go +++ b/v24/api/keyfactor/v1/client_test.go @@ -4,6 +4,7 @@ import ( "net/http" "net/http/httptest" "net/url" + "os" "reflect" "testing" "time" @@ -11,6 +12,25 @@ import ( "github.com/Keyfactor/keyfactor-auth-client-go/auth_providers" ) +// unsetEnvForTest removes an environment variable for the duration of the +// test and restores its original value (or absence) on cleanup. Unlike +// t.Setenv(key, ""), which leaves the variable "present" with an empty +// value (still visible to os.LookupEnv), this genuinely unsets it so code +// that branches on presence -- e.g. ValidateAuthConfig's +// KEYFACTOR_CLIENT_TIMEOUT handling, which treats a present-but-unparseable +// value as "leave HttpClientTimeout at its current value" rather than +// falling through to the 60s default -- behaves as if the caller's shell +// never exported it at all. +func unsetEnvForTest(t *testing.T, key string) { + t.Helper() + if orig, ok := os.LookupEnv(key); ok { + t.Cleanup(func() { + _ = os.Setenv(key, orig) + }) + _ = os.Unsetenv(key) + } +} + // TestCommandConfigOauth_AccessTokenFieldPropagation is a compilation + correctness // regression test for the v2.8.0 bug where AccessToken, Audience, and Scopes were // silently dropped when constructing CommandConfigOauth from auth_providers.Server @@ -128,6 +148,32 @@ func newFakeCommandServer(t *testing.T) *httptest.Server { // buildHttpClientV2's lines to avoid the network call inside Authenticate()), // this test exercises buildHttpClientV2 itself against a fake Command server. func TestBuildHttpClientV2_ClientTimeoutPropagation(t *testing.T) { + // Hermetic against the ambient environment: ValidateAuthConfig (in + // keyfactor-auth-client-go's auth_core.go) unconditionally overwrites + // SkipVerify from KEYFACTOR_SKIP_VERIFY whenever the variable is merely + // *present*, regardless of its value -- so anything other than exactly + // "true"/"1" (e.g. "TRUE", "false", "0", or an empty string sourced from + // a lab env file) flips SkipTLSVerify back to false and this test fails + // against its own self-signed httptest server with "x509: certificate + // signed by unknown authority". Pin it explicitly rather than relying on + // it being unset in whatever shell runs `go test`. (The upstream + // unconditional-overwrite behavior itself is tracked and fixed + // separately in keyfactor-auth-client-go; this only needs to make our + // test hermetic against it.) + t.Setenv(auth_providers.EnvKeyfactorSkipVerify, "true") + // A stale/bad KEYFACTOR_CA_CERT path in the ambient environment would + // make BuildTransport() below treat the value as literal PEM bytes and + // fail with "failed to append custom CA cert to pool". Neutralize it. + unsetEnvForTest(t, auth_providers.EnvKeyfactorCACert) + // KEYFACTOR_CLIENT_TIMEOUT only matters when HttpClientTimeout is <= 0 + // going in (it isn't here -- srv.ClientTimeout is 300 below), but pin it + // too for defense-in-depth. t.Setenv(..., "") would NOT achieve this: an + // empty value is still "present" to os.LookupEnv, so ValidateAuthConfig + // would see ok=true, fail to strconv.Atoi(""), and leave + // HttpClientTimeout at whatever it currently is instead of falling + // through to the 60s default -- the "zero-timeout case". + unsetEnvForTest(t, auth_providers.EnvKeyfactorClientTimeout) + server := newFakeCommandServer(t) u, uErr := url.Parse(server.URL) if uErr != nil { diff --git a/v24/api/keyfactor/v2/client_test.go b/v24/api/keyfactor/v2/client_test.go index f8803c8..5b074bd 100644 --- a/v24/api/keyfactor/v2/client_test.go +++ b/v24/api/keyfactor/v2/client_test.go @@ -4,6 +4,7 @@ import ( "net/http" "net/http/httptest" "net/url" + "os" "reflect" "testing" "time" @@ -11,6 +12,25 @@ import ( "github.com/Keyfactor/keyfactor-auth-client-go/auth_providers" ) +// unsetEnvForTest removes an environment variable for the duration of the +// test and restores its original value (or absence) on cleanup. Unlike +// t.Setenv(key, ""), which leaves the variable "present" with an empty +// value (still visible to os.LookupEnv), this genuinely unsets it so code +// that branches on presence -- e.g. ValidateAuthConfig's +// KEYFACTOR_CLIENT_TIMEOUT handling, which treats a present-but-unparseable +// value as "leave HttpClientTimeout at its current value" rather than +// falling through to the 60s default -- behaves as if the caller's shell +// never exported it at all. +func unsetEnvForTest(t *testing.T, key string) { + t.Helper() + if orig, ok := os.LookupEnv(key); ok { + t.Cleanup(func() { + _ = os.Setenv(key, orig) + }) + _ = os.Unsetenv(key) + } +} + // TestCommandConfigOauth_AccessTokenFieldPropagation is a compilation + correctness // regression test for the v2.8.0 bug where AccessToken, Audience, and Scopes were // silently dropped when constructing CommandConfigOauth from auth_providers.Server @@ -128,6 +148,32 @@ func newFakeCommandServer(t *testing.T) *httptest.Server { // buildHttpClientV2's lines to avoid the network call inside Authenticate()), // this test exercises buildHttpClientV2 itself against a fake Command server. func TestBuildHttpClientV2_ClientTimeoutPropagation(t *testing.T) { + // Hermetic against the ambient environment: ValidateAuthConfig (in + // keyfactor-auth-client-go's auth_core.go) unconditionally overwrites + // SkipVerify from KEYFACTOR_SKIP_VERIFY whenever the variable is merely + // *present*, regardless of its value -- so anything other than exactly + // "true"/"1" (e.g. "TRUE", "false", "0", or an empty string sourced from + // a lab env file) flips SkipTLSVerify back to false and this test fails + // against its own self-signed httptest server with "x509: certificate + // signed by unknown authority". Pin it explicitly rather than relying on + // it being unset in whatever shell runs `go test`. (The upstream + // unconditional-overwrite behavior itself is tracked and fixed + // separately in keyfactor-auth-client-go; this only needs to make our + // test hermetic against it.) + t.Setenv(auth_providers.EnvKeyfactorSkipVerify, "true") + // A stale/bad KEYFACTOR_CA_CERT path in the ambient environment would + // make BuildTransport() below treat the value as literal PEM bytes and + // fail with "failed to append custom CA cert to pool". Neutralize it. + unsetEnvForTest(t, auth_providers.EnvKeyfactorCACert) + // KEYFACTOR_CLIENT_TIMEOUT only matters when HttpClientTimeout is <= 0 + // going in (it isn't here -- srv.ClientTimeout is 300 below), but pin it + // too for defense-in-depth. t.Setenv(..., "") would NOT achieve this: an + // empty value is still "present" to os.LookupEnv, so ValidateAuthConfig + // would see ok=true, fail to strconv.Atoi(""), and leave + // HttpClientTimeout at whatever it currently is instead of falling + // through to the 60s default -- the "zero-timeout case". + unsetEnvForTest(t, auth_providers.EnvKeyfactorClientTimeout) + server := newFakeCommandServer(t) u, uErr := url.Parse(server.URL) if uErr != nil { From db2be750adce5a9283f316ea82217147b3c4e8a7 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Fri, 14 Aug 2026 09:14:30 -0700 Subject: [PATCH 07/13] fix(v25): plumb Server.ClientTimeout into rebuilt auth config buildHttpClientV2 in v25/api/keyfactor/v1/client.go and v2/client.go rebuilds its own auth_providers.CommandAuthConfig from the caller's Server but never copied ClientTimeout into HttpClientTimeout, so every v25 caller silently fell back to the 60s default regardless of what was configured -- the same bug already fixed for v24 in 2a6c5b4. Server.ClientTimeout only exists starting at keyfactor-auth-client-go v1.6.0-rc.2 (added for issue #51), so bump v25's dependency from v1.3.0 to match. `go mod tidy` and `go build ./...` are clean with no API compatibility breaks between those versions. Port TestBuildHttpClientV2_ClientTimeoutPropagation (and its unsetEnvForTest hermeticity helper) from v24 to v25's v1 and v2 packages verbatim. Verified green, including with KEYFACTOR_SKIP_VERIFY set to false, TRUE, 0, and empty string. --- v25/api/keyfactor/v1/client.go | 17 ++-- v25/api/keyfactor/v1/client_test.go | 116 ++++++++++++++++++++++++++ v25/api/keyfactor/v2/client.go | 17 ++-- v25/api/keyfactor/v2/client_test.go | 116 ++++++++++++++++++++++++++ v25/go.mod | 37 +++++---- v25/go.sum | 121 ++++++++++++++++++++-------- 6 files changed, 358 insertions(+), 66 deletions(-) diff --git a/v25/api/keyfactor/v1/client.go b/v25/api/keyfactor/v1/client.go index 51c7350..fe885f2 100644 --- a/v25/api/keyfactor/v1/client.go +++ b/v25/api/keyfactor/v1/client.go @@ -257,11 +257,12 @@ func buildHttpClientV2(cfg *auth_providers.Server) (AuthConfig, error) { clientAuthType := cfg.GetAuthType() baseConfig := auth_providers.CommandAuthConfig{ - CommandHostName: cfg.Host, - CommandPort: cfg.Port, - CommandAPIPath: cfg.APIPath, - CommandCACert: cfg.CACertPath, - SkipVerify: cfg.SkipTLSVerify, + CommandHostName: cfg.Host, + CommandPort: cfg.Port, + CommandAPIPath: cfg.APIPath, + CommandCACert: cfg.CACertPath, + SkipVerify: cfg.SkipTLSVerify, + HttpClientTimeout: cfg.ClientTimeout, } if clientAuthType == "basic" { @@ -286,9 +287,9 @@ func buildHttpClientV2(cfg *auth_providers.Server) (AuthConfig, error) { ClientID: cfg.ClientID, ClientSecret: cfg.ClientSecret, TokenURL: cfg.OAuthTokenUrl, - Audience: cfg.Audience, - Scopes: cfg.Scopes, - AccessToken: cfg.AccessToken, + Audience: cfg.Audience, + Scopes: cfg.Scopes, + AccessToken: cfg.AccessToken, } aErr := oauthCfg.Authenticate() if aErr != nil { diff --git a/v25/api/keyfactor/v1/client_test.go b/v25/api/keyfactor/v1/client_test.go index fadf205..f4c549a 100644 --- a/v25/api/keyfactor/v1/client_test.go +++ b/v25/api/keyfactor/v1/client_test.go @@ -1,12 +1,36 @@ package v1 import ( + "net/http" + "net/http/httptest" + "net/url" + "os" "reflect" "testing" + "time" "github.com/Keyfactor/keyfactor-auth-client-go/auth_providers" ) +// unsetEnvForTest removes an environment variable for the duration of the +// test and restores its original value (or absence) on cleanup. Unlike +// t.Setenv(key, ""), which leaves the variable "present" with an empty +// value (still visible to os.LookupEnv), this genuinely unsets it so code +// that branches on presence -- e.g. ValidateAuthConfig's +// KEYFACTOR_CLIENT_TIMEOUT handling, which treats a present-but-unparseable +// value as "leave HttpClientTimeout at its current value" rather than +// falling through to the 60s default -- behaves as if the caller's shell +// never exported it at all. +func unsetEnvForTest(t *testing.T, key string) { + t.Helper() + if orig, ok := os.LookupEnv(key); ok { + t.Cleanup(func() { + _ = os.Setenv(key, orig) + }) + _ = os.Unsetenv(key) + } +} + // TestCommandConfigOauth_AccessTokenFieldPropagation is a compilation + correctness // regression test for the v2.8.0 bug where AccessToken, Audience, and Scopes were // silently dropped when constructing CommandConfigOauth from auth_providers.Server @@ -97,3 +121,95 @@ func TestCommandConfigOauth_AccessTokenOnlyNoClientCreds(t *testing.T) { t.Errorf("ClientSecret = %q, want empty", oauthCfg.ClientSecret) } } + +// newFakeCommandServer stands in for a Keyfactor Command instance for +// CommandAuthConfigBasic.Authenticate(), which performs a real GET against +// {host}/{apiPath}/Status/Endpoints as part of authentication. It always +// returns 200 with a valid JSON string array, regardless of credentials. +func newFakeCommandServer(t *testing.T) *httptest.Server { + t.Helper() + server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`["endpoint1"]`)) + })) + t.Cleanup(server.Close) + return server +} + +// TestBuildHttpClientV2_ClientTimeoutPropagation is a regression test for the +// bug where Server.ClientTimeout was silently dropped when buildHttpClientV2 +// rebuilt its own CommandAuthConfig, causing every caller (including the +// Terraform provider's request_timeout setting) to fall back to +// auth_providers.DefaultClientTimeout (60s) regardless of what was +// configured -- surfacing as "net/http: timeout awaiting response headers" on +// long-running calls such as PFX enrollment. Unlike +// TestCommandConfigOauth_AccessTokenFieldPropagation above (which mirrors +// buildHttpClientV2's lines to avoid the network call inside Authenticate()), +// this test exercises buildHttpClientV2 itself against a fake Command server. +func TestBuildHttpClientV2_ClientTimeoutPropagation(t *testing.T) { + // Hermetic against the ambient environment: ValidateAuthConfig (in + // keyfactor-auth-client-go's auth_core.go) unconditionally overwrites + // SkipVerify from KEYFACTOR_SKIP_VERIFY whenever the variable is merely + // *present*, regardless of its value -- so anything other than exactly + // "true"/"1" (e.g. "TRUE", "false", "0", or an empty string sourced from + // a lab env file) flips SkipTLSVerify back to false and this test fails + // against its own self-signed httptest server with "x509: certificate + // signed by unknown authority". Pin it explicitly rather than relying on + // it being unset in whatever shell runs `go test`. (The upstream + // unconditional-overwrite behavior itself is tracked and fixed + // separately in keyfactor-auth-client-go; this only needs to make our + // test hermetic against it.) + t.Setenv(auth_providers.EnvKeyfactorSkipVerify, "true") + // A stale/bad KEYFACTOR_CA_CERT path in the ambient environment would + // make BuildTransport() below treat the value as literal PEM bytes and + // fail with "failed to append custom CA cert to pool". Neutralize it. + unsetEnvForTest(t, auth_providers.EnvKeyfactorCACert) + // KEYFACTOR_CLIENT_TIMEOUT only matters when HttpClientTimeout is <= 0 + // going in (it isn't here -- srv.ClientTimeout is 300 below), but pin it + // too for defense-in-depth. t.Setenv(..., "") would NOT achieve this: an + // empty value is still "present" to os.LookupEnv, so ValidateAuthConfig + // would see ok=true, fail to strconv.Atoi(""), and leave + // HttpClientTimeout at whatever it currently is instead of falling + // through to the 60s default -- the "zero-timeout case". + unsetEnvForTest(t, auth_providers.EnvKeyfactorClientTimeout) + + server := newFakeCommandServer(t) + u, uErr := url.Parse(server.URL) + if uErr != nil { + t.Fatalf("failed to parse test server URL: %v", uErr) + } + + srv := &auth_providers.Server{ + Host: u.Host, + Username: "user", + Password: "pass", + APIPath: "api", + SkipTLSVerify: true, + ClientTimeout: 300, + } + + authCfg, err := buildHttpClientV2(srv) + if err != nil { + t.Fatalf("buildHttpClientV2() returned unexpected error: %v", err) + } + + basicCfg, ok := authCfg.(*auth_providers.CommandAuthConfigBasic) + if !ok { + t.Fatalf("expected AuthConfig to be *auth_providers.CommandAuthConfigBasic, got %T", authCfg) + } + + if basicCfg.HttpClientTimeout != 300 { + t.Errorf("CommandAuthConfigBasic.HttpClientTimeout = %d, want %d", basicCfg.HttpClientTimeout, 300) + } + + transport, tErr := basicCfg.CommandAuthConfig.BuildTransport() + if tErr != nil { + t.Fatalf("BuildTransport() returned unexpected error: %v", tErr) + } + + expected := 300 * time.Second + if transport.ResponseHeaderTimeout != expected { + t.Errorf("ResponseHeaderTimeout = %v, want %v", transport.ResponseHeaderTimeout, expected) + } +} diff --git a/v25/api/keyfactor/v2/client.go b/v25/api/keyfactor/v2/client.go index 656c4a7..8390026 100644 --- a/v25/api/keyfactor/v2/client.go +++ b/v25/api/keyfactor/v2/client.go @@ -116,11 +116,12 @@ func buildHttpClientV2(cfg *auth_providers.Server) (AuthConfig, error) { clientAuthType := cfg.GetAuthType() baseConfig := auth_providers.CommandAuthConfig{ - CommandHostName: cfg.Host, - CommandPort: cfg.Port, - CommandAPIPath: cfg.APIPath, - CommandCACert: cfg.CACertPath, - SkipVerify: cfg.SkipTLSVerify, + CommandHostName: cfg.Host, + CommandPort: cfg.Port, + CommandAPIPath: cfg.APIPath, + CommandCACert: cfg.CACertPath, + SkipVerify: cfg.SkipTLSVerify, + HttpClientTimeout: cfg.ClientTimeout, } if clientAuthType == "basic" { @@ -145,9 +146,9 @@ func buildHttpClientV2(cfg *auth_providers.Server) (AuthConfig, error) { ClientID: cfg.ClientID, ClientSecret: cfg.ClientSecret, TokenURL: cfg.OAuthTokenUrl, - Audience: cfg.Audience, - Scopes: cfg.Scopes, - AccessToken: cfg.AccessToken, + Audience: cfg.Audience, + Scopes: cfg.Scopes, + AccessToken: cfg.AccessToken, } aErr := oauthCfg.Authenticate() if aErr != nil { diff --git a/v25/api/keyfactor/v2/client_test.go b/v25/api/keyfactor/v2/client_test.go index 6a58a66..5b074bd 100644 --- a/v25/api/keyfactor/v2/client_test.go +++ b/v25/api/keyfactor/v2/client_test.go @@ -1,12 +1,36 @@ package v2 import ( + "net/http" + "net/http/httptest" + "net/url" + "os" "reflect" "testing" + "time" "github.com/Keyfactor/keyfactor-auth-client-go/auth_providers" ) +// unsetEnvForTest removes an environment variable for the duration of the +// test and restores its original value (or absence) on cleanup. Unlike +// t.Setenv(key, ""), which leaves the variable "present" with an empty +// value (still visible to os.LookupEnv), this genuinely unsets it so code +// that branches on presence -- e.g. ValidateAuthConfig's +// KEYFACTOR_CLIENT_TIMEOUT handling, which treats a present-but-unparseable +// value as "leave HttpClientTimeout at its current value" rather than +// falling through to the 60s default -- behaves as if the caller's shell +// never exported it at all. +func unsetEnvForTest(t *testing.T, key string) { + t.Helper() + if orig, ok := os.LookupEnv(key); ok { + t.Cleanup(func() { + _ = os.Setenv(key, orig) + }) + _ = os.Unsetenv(key) + } +} + // TestCommandConfigOauth_AccessTokenFieldPropagation is a compilation + correctness // regression test for the v2.8.0 bug where AccessToken, Audience, and Scopes were // silently dropped when constructing CommandConfigOauth from auth_providers.Server @@ -97,3 +121,95 @@ func TestCommandConfigOauth_AccessTokenOnlyNoClientCreds(t *testing.T) { t.Errorf("ClientSecret = %q, want empty", oauthCfg.ClientSecret) } } + +// newFakeCommandServer stands in for a Keyfactor Command instance for +// CommandAuthConfigBasic.Authenticate(), which performs a real GET against +// {host}/{apiPath}/Status/Endpoints as part of authentication. It always +// returns 200 with a valid JSON string array, regardless of credentials. +func newFakeCommandServer(t *testing.T) *httptest.Server { + t.Helper() + server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`["endpoint1"]`)) + })) + t.Cleanup(server.Close) + return server +} + +// TestBuildHttpClientV2_ClientTimeoutPropagation is a regression test for the +// bug where Server.ClientTimeout was silently dropped when buildHttpClientV2 +// rebuilt its own CommandAuthConfig, causing every caller (including the +// Terraform provider's request_timeout setting) to fall back to +// auth_providers.DefaultClientTimeout (60s) regardless of what was +// configured -- surfacing as "net/http: timeout awaiting response headers" on +// long-running calls such as PFX enrollment. Unlike +// TestCommandConfigOauth_AccessTokenFieldPropagation above (which mirrors +// buildHttpClientV2's lines to avoid the network call inside Authenticate()), +// this test exercises buildHttpClientV2 itself against a fake Command server. +func TestBuildHttpClientV2_ClientTimeoutPropagation(t *testing.T) { + // Hermetic against the ambient environment: ValidateAuthConfig (in + // keyfactor-auth-client-go's auth_core.go) unconditionally overwrites + // SkipVerify from KEYFACTOR_SKIP_VERIFY whenever the variable is merely + // *present*, regardless of its value -- so anything other than exactly + // "true"/"1" (e.g. "TRUE", "false", "0", or an empty string sourced from + // a lab env file) flips SkipTLSVerify back to false and this test fails + // against its own self-signed httptest server with "x509: certificate + // signed by unknown authority". Pin it explicitly rather than relying on + // it being unset in whatever shell runs `go test`. (The upstream + // unconditional-overwrite behavior itself is tracked and fixed + // separately in keyfactor-auth-client-go; this only needs to make our + // test hermetic against it.) + t.Setenv(auth_providers.EnvKeyfactorSkipVerify, "true") + // A stale/bad KEYFACTOR_CA_CERT path in the ambient environment would + // make BuildTransport() below treat the value as literal PEM bytes and + // fail with "failed to append custom CA cert to pool". Neutralize it. + unsetEnvForTest(t, auth_providers.EnvKeyfactorCACert) + // KEYFACTOR_CLIENT_TIMEOUT only matters when HttpClientTimeout is <= 0 + // going in (it isn't here -- srv.ClientTimeout is 300 below), but pin it + // too for defense-in-depth. t.Setenv(..., "") would NOT achieve this: an + // empty value is still "present" to os.LookupEnv, so ValidateAuthConfig + // would see ok=true, fail to strconv.Atoi(""), and leave + // HttpClientTimeout at whatever it currently is instead of falling + // through to the 60s default -- the "zero-timeout case". + unsetEnvForTest(t, auth_providers.EnvKeyfactorClientTimeout) + + server := newFakeCommandServer(t) + u, uErr := url.Parse(server.URL) + if uErr != nil { + t.Fatalf("failed to parse test server URL: %v", uErr) + } + + srv := &auth_providers.Server{ + Host: u.Host, + Username: "user", + Password: "pass", + APIPath: "api", + SkipTLSVerify: true, + ClientTimeout: 300, + } + + authCfg, err := buildHttpClientV2(srv) + if err != nil { + t.Fatalf("buildHttpClientV2() returned unexpected error: %v", err) + } + + basicCfg, ok := authCfg.(*auth_providers.CommandAuthConfigBasic) + if !ok { + t.Fatalf("expected AuthConfig to be *auth_providers.CommandAuthConfigBasic, got %T", authCfg) + } + + if basicCfg.HttpClientTimeout != 300 { + t.Errorf("CommandAuthConfigBasic.HttpClientTimeout = %d, want %d", basicCfg.HttpClientTimeout, 300) + } + + transport, tErr := basicCfg.CommandAuthConfig.BuildTransport() + if tErr != nil { + t.Fatalf("BuildTransport() returned unexpected error: %v", tErr) + } + + expected := 300 * time.Second + if transport.ResponseHeaderTimeout != expected { + t.Errorf("ResponseHeaderTimeout = %v, want %v", transport.ResponseHeaderTimeout, expected) + } +} diff --git a/v25/go.mod b/v25/go.mod index b0ecb0f..21fe280 100644 --- a/v25/go.mod +++ b/v25/go.mod @@ -1,26 +1,31 @@ module github.com/Keyfactor/keyfactor-go-client-sdk/v25 -go 1.24 +go 1.24.0 -toolchain go1.24.0 - -require github.com/Keyfactor/keyfactor-auth-client-go v1.3.0 +require github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.2 require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect - github.com/golang-jwt/jwt/v5 v5.2.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/jcmturner/aescts/v2 v2.0.0 // indirect + github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect + github.com/jcmturner/gofork v1.7.6 // indirect + github.com/jcmturner/goidentity/v6 v6.0.1 // indirect + github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect + github.com/jcmturner/rpc/v2 v2.0.3 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect - golang.org/x/crypto v0.39.0 // indirect - golang.org/x/net v0.41.0 // indirect - golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.26.0 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/v25/go.sum b/v25/go.sum index 2d1793f..af2a2f3 100644 --- a/v25/go.sum +++ b/v25/go.sum @@ -1,31 +1,47 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.1 h1:mrkDCdkMsD4l9wjFGhofFHFrV43Y3c53RSLKOCJ5+Ow= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.1/go.mod h1:hPv41DbqMmnxcGralanA/kVlfdH5jv3T4LxGku2E1BY= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 h1:/g8S6wk65vfC6m3FIxJ+i5QDyN9JWwXI8Hb0Img10hU= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0/go.mod h1:gpl+q95AzZlKVI3xSoseF9QPrypk0hQqBiJYeB/cR/I= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= -github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= -github.com/Keyfactor/keyfactor-auth-client-go v1.3.0 h1:otC213b6CYzqeN9b3CRlH1Qj1hTFIN5nqPA8gTlHdLg= -github.com/Keyfactor/keyfactor-auth-client-go v1.3.0/go.mod h1:97vCisBNkdCK0l2TuvOSdjlpvQa4+GHsMut1UTyv1jo= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= +github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.2 h1:wp7LBuNSpHZYPlzEuipNeuWwwBow8lgLj8lD2gMivhM= +github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.2/go.mod h1:rFBZPMSHWwWuUwE1kXhLsDaOxjGiHMbXTTEni8Dmufo= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= -github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= -github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -38,27 +54,64 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmd github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= -github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From cacd7c591887dea78f2659c686162522e138de2e Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Fri, 14 Aug 2026 09:21:28 -0700 Subject: [PATCH 08/13] test(client): add regression test for prepareRequest port-443 guard Commit 229db7d added "&& serverConfig.Port != 443" to prepareRequest's port guard in both v1 and v2 client.go (skip appending port 443 to the request host to avoid duplicate/explicit-port HTTPS URLs), but shipped with no test. Reverting that guard would fail nothing in CI despite being an auth/URL-critical hand-edit. Add TestPrepareRequest_Port443Guard to v24 and v25's v1 and v2 packages, covering both the omitted-port-443 case and a non-443 port to confirm the guard doesn't over-broadly strip other ports. Constructs the APIClient directly via its exported AuthClient field rather than NewAPIClientWithAuth, so the test doesn't depend on an unrelated hand-edit (and works uniformly in v25, which never got that helper ported from v24). Verified red against the pre-229db7d guard (asserts URL.Host == "command.example.com:443" instead of without the port) and green against the current guard, in all four packages. --- v24/api/keyfactor/v1/client_test.go | 59 +++++++++++++++++++++++++++++ v24/api/keyfactor/v2/client_test.go | 59 +++++++++++++++++++++++++++++ v25/api/keyfactor/v1/client_test.go | 59 +++++++++++++++++++++++++++++ v25/api/keyfactor/v2/client_test.go | 59 +++++++++++++++++++++++++++++ 4 files changed, 236 insertions(+) diff --git a/v24/api/keyfactor/v1/client_test.go b/v24/api/keyfactor/v1/client_test.go index f4c549a..3bf0088 100644 --- a/v24/api/keyfactor/v1/client_test.go +++ b/v24/api/keyfactor/v1/client_test.go @@ -1,6 +1,7 @@ package v1 import ( + "context" "net/http" "net/http/httptest" "net/url" @@ -213,3 +214,61 @@ func TestBuildHttpClientV2_ClientTimeoutPropagation(t *testing.T) { t.Errorf("ResponseHeaderTimeout = %v, want %v", transport.ResponseHeaderTimeout, expected) } } + +// TestPrepareRequest_Port443Guard is a regression test for the hand-edit in +// commit 229db7d that added "&& serverConfig.Port != 443" to prepareRequest's +// port guard. Without it, a Server configured with Port: 443 (the default +// HTTPS port, and what many callers -- including the Terraform provider -- +// set explicitly) produces request URLs like "https://host:443/..." instead +// of "https://host/...". Both are technically valid HTTPS URLs, but the +// explicit ":443" broke servers/proxies that match on Host header exactly +// (no port suffix) and was reported as a functional regression. This test +// was previously unprotected: reverting the guard would fail nothing in CI. +func TestPrepareRequest_Port443Guard(t *testing.T) { + // Constructed directly against the exported AuthClient field (rather + // than via NewAPIClientWithAuth) so this test exercises prepareRequest + // in isolation without depending on an unrelated hand-edit. + newClientWithPort := func(t *testing.T, port int) *APIClient { + t.Helper() + return &APIClient{ + AuthClient: &auth_providers.CommandAuthConfigBasic{ + CommandAuthConfig: auth_providers.CommandAuthConfig{ + CommandHostName: "command.example.com", + CommandPort: port, + }, + }, + } + } + + prepare := func(t *testing.T, c *APIClient) *http.Request { + t.Helper() + req, err := c.prepareRequest( + context.Background(), + "https://placeholder.invalid/api/Status/Endpoints", + "GET", + nil, + map[string]string{}, + nil, + nil, + nil, + ) + if err != nil { + t.Fatalf("prepareRequest() returned unexpected error: %v", err) + } + return req + } + + t.Run("port 443 is omitted from the request host", func(t *testing.T) { + req := prepare(t, newClientWithPort(t, 443)) + if got, want := req.URL.Host, "command.example.com"; got != want { + t.Errorf("URL.Host = %q, want %q", got, want) + } + }) + + t.Run("non-443 port is still appended to the request host", func(t *testing.T) { + req := prepare(t, newClientWithPort(t, 8443)) + if got, want := req.URL.Host, "command.example.com:8443"; got != want { + t.Errorf("URL.Host = %q, want %q", got, want) + } + }) +} diff --git a/v24/api/keyfactor/v2/client_test.go b/v24/api/keyfactor/v2/client_test.go index 5b074bd..e0ea389 100644 --- a/v24/api/keyfactor/v2/client_test.go +++ b/v24/api/keyfactor/v2/client_test.go @@ -1,6 +1,7 @@ package v2 import ( + "context" "net/http" "net/http/httptest" "net/url" @@ -213,3 +214,61 @@ func TestBuildHttpClientV2_ClientTimeoutPropagation(t *testing.T) { t.Errorf("ResponseHeaderTimeout = %v, want %v", transport.ResponseHeaderTimeout, expected) } } + +// TestPrepareRequest_Port443Guard is a regression test for the hand-edit in +// commit 229db7d that added "&& serverConfig.Port != 443" to prepareRequest's +// port guard. Without it, a Server configured with Port: 443 (the default +// HTTPS port, and what many callers -- including the Terraform provider -- +// set explicitly) produces request URLs like "https://host:443/..." instead +// of "https://host/...". Both are technically valid HTTPS URLs, but the +// explicit ":443" broke servers/proxies that match on Host header exactly +// (no port suffix) and was reported as a functional regression. This test +// was previously unprotected: reverting the guard would fail nothing in CI. +func TestPrepareRequest_Port443Guard(t *testing.T) { + // Constructed directly against the exported AuthClient field (rather + // than via NewAPIClientWithAuth) so this test exercises prepareRequest + // in isolation without depending on an unrelated hand-edit. + newClientWithPort := func(t *testing.T, port int) *APIClient { + t.Helper() + return &APIClient{ + AuthClient: &auth_providers.CommandAuthConfigBasic{ + CommandAuthConfig: auth_providers.CommandAuthConfig{ + CommandHostName: "command.example.com", + CommandPort: port, + }, + }, + } + } + + prepare := func(t *testing.T, c *APIClient) *http.Request { + t.Helper() + req, err := c.prepareRequest( + context.Background(), + "https://placeholder.invalid/api/Status/Endpoints", + "GET", + nil, + map[string]string{}, + nil, + nil, + nil, + ) + if err != nil { + t.Fatalf("prepareRequest() returned unexpected error: %v", err) + } + return req + } + + t.Run("port 443 is omitted from the request host", func(t *testing.T) { + req := prepare(t, newClientWithPort(t, 443)) + if got, want := req.URL.Host, "command.example.com"; got != want { + t.Errorf("URL.Host = %q, want %q", got, want) + } + }) + + t.Run("non-443 port is still appended to the request host", func(t *testing.T) { + req := prepare(t, newClientWithPort(t, 8443)) + if got, want := req.URL.Host, "command.example.com:8443"; got != want { + t.Errorf("URL.Host = %q, want %q", got, want) + } + }) +} diff --git a/v25/api/keyfactor/v1/client_test.go b/v25/api/keyfactor/v1/client_test.go index f4c549a..3bf0088 100644 --- a/v25/api/keyfactor/v1/client_test.go +++ b/v25/api/keyfactor/v1/client_test.go @@ -1,6 +1,7 @@ package v1 import ( + "context" "net/http" "net/http/httptest" "net/url" @@ -213,3 +214,61 @@ func TestBuildHttpClientV2_ClientTimeoutPropagation(t *testing.T) { t.Errorf("ResponseHeaderTimeout = %v, want %v", transport.ResponseHeaderTimeout, expected) } } + +// TestPrepareRequest_Port443Guard is a regression test for the hand-edit in +// commit 229db7d that added "&& serverConfig.Port != 443" to prepareRequest's +// port guard. Without it, a Server configured with Port: 443 (the default +// HTTPS port, and what many callers -- including the Terraform provider -- +// set explicitly) produces request URLs like "https://host:443/..." instead +// of "https://host/...". Both are technically valid HTTPS URLs, but the +// explicit ":443" broke servers/proxies that match on Host header exactly +// (no port suffix) and was reported as a functional regression. This test +// was previously unprotected: reverting the guard would fail nothing in CI. +func TestPrepareRequest_Port443Guard(t *testing.T) { + // Constructed directly against the exported AuthClient field (rather + // than via NewAPIClientWithAuth) so this test exercises prepareRequest + // in isolation without depending on an unrelated hand-edit. + newClientWithPort := func(t *testing.T, port int) *APIClient { + t.Helper() + return &APIClient{ + AuthClient: &auth_providers.CommandAuthConfigBasic{ + CommandAuthConfig: auth_providers.CommandAuthConfig{ + CommandHostName: "command.example.com", + CommandPort: port, + }, + }, + } + } + + prepare := func(t *testing.T, c *APIClient) *http.Request { + t.Helper() + req, err := c.prepareRequest( + context.Background(), + "https://placeholder.invalid/api/Status/Endpoints", + "GET", + nil, + map[string]string{}, + nil, + nil, + nil, + ) + if err != nil { + t.Fatalf("prepareRequest() returned unexpected error: %v", err) + } + return req + } + + t.Run("port 443 is omitted from the request host", func(t *testing.T) { + req := prepare(t, newClientWithPort(t, 443)) + if got, want := req.URL.Host, "command.example.com"; got != want { + t.Errorf("URL.Host = %q, want %q", got, want) + } + }) + + t.Run("non-443 port is still appended to the request host", func(t *testing.T) { + req := prepare(t, newClientWithPort(t, 8443)) + if got, want := req.URL.Host, "command.example.com:8443"; got != want { + t.Errorf("URL.Host = %q, want %q", got, want) + } + }) +} diff --git a/v25/api/keyfactor/v2/client_test.go b/v25/api/keyfactor/v2/client_test.go index 5b074bd..e0ea389 100644 --- a/v25/api/keyfactor/v2/client_test.go +++ b/v25/api/keyfactor/v2/client_test.go @@ -1,6 +1,7 @@ package v2 import ( + "context" "net/http" "net/http/httptest" "net/url" @@ -213,3 +214,61 @@ func TestBuildHttpClientV2_ClientTimeoutPropagation(t *testing.T) { t.Errorf("ResponseHeaderTimeout = %v, want %v", transport.ResponseHeaderTimeout, expected) } } + +// TestPrepareRequest_Port443Guard is a regression test for the hand-edit in +// commit 229db7d that added "&& serverConfig.Port != 443" to prepareRequest's +// port guard. Without it, a Server configured with Port: 443 (the default +// HTTPS port, and what many callers -- including the Terraform provider -- +// set explicitly) produces request URLs like "https://host:443/..." instead +// of "https://host/...". Both are technically valid HTTPS URLs, but the +// explicit ":443" broke servers/proxies that match on Host header exactly +// (no port suffix) and was reported as a functional regression. This test +// was previously unprotected: reverting the guard would fail nothing in CI. +func TestPrepareRequest_Port443Guard(t *testing.T) { + // Constructed directly against the exported AuthClient field (rather + // than via NewAPIClientWithAuth) so this test exercises prepareRequest + // in isolation without depending on an unrelated hand-edit. + newClientWithPort := func(t *testing.T, port int) *APIClient { + t.Helper() + return &APIClient{ + AuthClient: &auth_providers.CommandAuthConfigBasic{ + CommandAuthConfig: auth_providers.CommandAuthConfig{ + CommandHostName: "command.example.com", + CommandPort: port, + }, + }, + } + } + + prepare := func(t *testing.T, c *APIClient) *http.Request { + t.Helper() + req, err := c.prepareRequest( + context.Background(), + "https://placeholder.invalid/api/Status/Endpoints", + "GET", + nil, + map[string]string{}, + nil, + nil, + nil, + ) + if err != nil { + t.Fatalf("prepareRequest() returned unexpected error: %v", err) + } + return req + } + + t.Run("port 443 is omitted from the request host", func(t *testing.T) { + req := prepare(t, newClientWithPort(t, 443)) + if got, want := req.URL.Host, "command.example.com"; got != want { + t.Errorf("URL.Host = %q, want %q", got, want) + } + }) + + t.Run("non-443 port is still appended to the request host", func(t *testing.T) { + req := prepare(t, newClientWithPort(t, 8443)) + if got, want := req.URL.Host, "command.example.com:8443"; got != want { + t.Errorf("URL.Host = %q, want %q", got, want) + } + }) +} From 15e6049c9d038cc3c0d49f6bfa6694bff3ad31e9 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Fri, 14 Aug 2026 09:25:41 -0700 Subject: [PATCH 09/13] docs(sdk): complete v24 hand-edit catalog, correct ignore-file claim, scope v25 The previous version of this document claimed to catalog every post-generation hand-edit under v24/api/keyfactor/v{1,2}/ but only listed the ClientTimeout edit -- 11 non-test files differ from the v24 generation baseline (2ed41db), 10 of them uncataloged. Enumerate all of them: NewAPIClientWithAuth, the OAuth AccessToken/Audience/Scopes restoration, the prepareRequest port-443 guard (previously untested -- see the companion test commit), the CA cleanup/enrollment fields, the EnrollmentType bitmask values, KeyRetentionPolicy's string-form UnmarshalJSON, the new CertificateCleanupTimeUnits enum, SystemDayOfWeek's day-name fallback, and the template cleanup/Manageability fields. Each entry states plainly whether a regression test protects it. Also corrects a factual error: the .openapi-generator-ignore files are 1040 bytes of generator boilerplate comments, not empty. Narrow the document's stated scope to v24 only, since v25's independent hand-edit history (from its own generation baseline) has not been audited to the same standard -- document only the two v25 edits made as part of this change set (ClientTimeout plumbing, port-443 test) and say so explicitly, rather than letting the file imply broader v25 coverage it doesn't have. Note the root and v2 modules carry the identical ClientTimeout bug and were verified fixable (dependency bump builds clean), but are left unfixed here because they have no existing test scaffolding and appear to have no active consumers -- documented as a deliberate scope decision, not a silent gap. --- HAND_EDITS.md | 215 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 207 insertions(+), 8 deletions(-) diff --git a/HAND_EDITS.md b/HAND_EDITS.md index c279965..65db980 100644 --- a/HAND_EDITS.md +++ b/HAND_EDITS.md @@ -1,6 +1,22 @@ # Hand-Edits to Generated SDK Code -This file catalogs commits that modified files inside `v24/api/keyfactor/v{1,2}/` (and, in future, other version directories) after their initial generation. These files carry generator-output "DO NOT EDIT" headers, but the project's `.openapi-generator-ignore` files are empty — no protection mechanism is in place. **Without the right templates and swagger patches, naive regeneration would silently drop every hand-edit listed below.** +## Scope + +This file catalogs commits that modified files inside **`v24/api/keyfactor/v{1,2}/`** after +their initial generation (baseline: `2ed41db`, "Generate V24 client"). These files carry +generator-output "DO NOT EDIT" headers, but the project's `.openapi-generator-ignore` files +are 1040 bytes of generator boilerplate comments with no active ignore patterns (verified by +byte count and content inspection) — no protection mechanism is in place. **Without the right +templates and swagger patches, naive regeneration of v24 would silently drop every hand-edit +listed below.** + +**This document does NOT currently cover `v25/api/keyfactor/v{1,2}/`, `v2/api/keyfactor/`, or +the repo-root `api/keyfactor/`.** `v25` has its own hand-edit history starting from its own +generation baseline (`536f3e2`, "feat(api): Add support for Keyfactor API Command up to +25.1.1") that has not been fully audited against this convention — see the "v25 (partial)" +section below for the one edit ported there as part of this change set. Treat any claim of +completeness in this file as scoped to v24 only; do not assume v25/v2/root are safe to +regenerate just because they aren't listed here. ## Conventions @@ -8,17 +24,200 @@ For each file, hand-edits are listed in commit order (oldest first). Each entry - **Commit SHA + subject** — recover the full diff with `git show -- `. - **What it changed** — brief description. -- **Reproduced by upstream swagger?** — Yes if the swagger definition already implies the same shape; No if it does not (i.e. this is pure Go logic with no swagger counterpart). -- **Regression test pins this?** — Yes if a `*_test.go` test would fail without the hand-edit. -- **Action on regen** — `preserve` (must be re-applied post-regen), `verify` (re-check whether reproduced), `obsolete` (intentional removal), `docs-only` (no behavioral impact). +- **Reproduced by upstream swagger?** — this repo has no swagger/OpenAPI spec file checked in + at any commit in its history (`find . -iname '*swagger*' -o -iname '*openapi*'` under + `v24/` returns nothing but generated output and `.openapi-generator-ignore`), so the answer + is **No** for every entry below: none of these edits can be verified against a source spec + we can regenerate from. "No" here does not mean "wrong," only "pure Go logic (or a + hand-authored file matching generator conventions) with no committed spec counterpart." +- **Regression test pins this?** — Yes if a `*_test.go` test would fail (or fail to compile) + without the hand-edit; "compile-only" if removal would only break a build elsewhere in this + repo without a test asserting behavior. +- **Action on regen** — `preserve` (must be re-applied post-regen), `verify` (re-check whether + reproduced), `obsolete` (intentional removal), `docs-only` (no behavioral impact). --- -## v24 (out of scope for any current regen — no v24 swagger has been supplied) +## v24 ### `v24/api/keyfactor/v1/client.go` + `v24/api/keyfactor/v2/client.go` -1. **`2a6c5b4`** — *fix(v24): plumb Server.ClientTimeout into rebuilt auth config* — inside `buildHttpClientV2()`, adds `HttpClientTimeout: cfg.ClientTimeout` to the `baseConfig := auth_providers.CommandAuthConfig{...}` struct literal in both files. Without it, `Server.ClientTimeout` (added upstream by `keyfactor-auth-client-go` to fix [issue #51](https://github.com/Keyfactor/keyfactor-auth-client-go/issues/51)) was silently dropped when this SDK rebuilt its own `CommandAuthConfig`, so every caller — including the Terraform provider's `request_timeout` setting — fell back to `auth_providers.DefaultClientTimeout` (60s) regardless of what was configured. This surfaced as `net/http: timeout awaiting response headers` on long-running calls such as PFX enrollment. - - Reproduced by upstream swagger: **No** — pure Go logic, no swagger counterpart. - - Pinned by test: **Yes** — `TestBuildHttpClientV2_ClientTimeoutPropagation` in `v24/api/keyfactor/v1/client_test.go` and `v24/api/keyfactor/v2/client_test.go` calls `buildHttpClientV2()` against a fake Command server and asserts the resulting `CommandAuthConfigBasic.HttpClientTimeout` and derived `BuildTransport().ResponseHeaderTimeout` reflect the configured value. +1. **`af6340b`** — *Ab#82568 (#30)* — adds `NewAPIClientWithAuth(auth AuthConfig) *APIClient` + to both files: constructs an `APIClient` with a pre-built `AuthConfig`, bypassing the + network call inside `Authenticate()`. This is the entry point the VCR-cassette-based unit + test harness (in this repo and in consumers such as the Terraform provider) uses to inject + a fake or replay-mode `AuthConfig` without hitting a real Command server. + - Reproduced by upstream swagger: **No**. + - Regression test pins this: **Compile-only** — `v24/client.go` (the top-level wrapper) + calls `v1.NewAPIClientWithAuth` / `v2.NewAPIClientWithAuth` directly, so `go build ./...` + fails if either is removed or its signature changes, but no test in *this* repo asserts + its runtime behavior. Downstream consumers' VCR test suites depend on it at compile time + too. + - Action: **preserve**. + +2. **`229db7d`** — *Feat/ca cleanup enrollment fields (#32)* — restores `AccessToken`, + `Audience`, and `Scopes` to the `auth_providers.CommandConfigOauth{...}` struct literal + inside `buildHttpClientV2`'s OAuth branch. Commit `2b88eb2` (2026-03-18) had silently + dropped these three fields in an earlier refactor, which broke pre-fetched + `access_token`-only authentication (callers supplying just hostname + access token, no + `client_id`/`client_secret`/`token_url`). + - Reproduced by upstream swagger: **No**. + - Regression test pins this: **Yes** — + `TestCommandConfigOauth_AccessTokenFieldPropagation` fails to compile if any of the three + fields are removed from either struct, and asserts they propagate correctly. + - Action: **preserve**. (This is the second time these fields were silently dropped and + restored — see the near-identical prior loss at `2b88eb2` — so it is a high-value + preserve.) + +3. **`229db7d`** (same commit) — changes `prepareRequest`'s port guard from + `serverConfig.Port > 0 && serverConfig.Port <= 65535` to + `serverConfig.Port > 0 && serverConfig.Port <= 65535 && serverConfig.Port != 443`, so a + `Server` configured with the default HTTPS port no longer produces + `https://host:443/...` request URLs (some servers/proxies that match on an exact, + port-suffix-free `Host` header rejected the explicit `:443`). + - Reproduced by upstream swagger: **No**. + - Regression test pins this: **Yes, as of this change set** — + `TestPrepareRequest_Port443Guard` (added alongside this document update) asserts both + that port 443 is omitted and that a non-443 port (8443) is still appended. Prior to this + change, reverting the guard failed nothing in CI. - Action: **preserve**. + +4. **`2a6c5b4`** — *fix(v24): plumb Server.ClientTimeout into rebuilt auth config* — inside + `buildHttpClientV2()`, adds `HttpClientTimeout: cfg.ClientTimeout` to the + `baseConfig := auth_providers.CommandAuthConfig{...}` struct literal in both files. Without + it, `Server.ClientTimeout` (added upstream by `keyfactor-auth-client-go` v1.6.0-rc.2 to fix + [issue #51](https://github.com/Keyfactor/keyfactor-auth-client-go/issues/51)) was silently + dropped when this SDK rebuilt its own `CommandAuthConfig`, so every caller — including the + Terraform provider's `request_timeout` setting — fell back to + `auth_providers.DefaultClientTimeout` (60s) regardless of what was configured. This + surfaced as `net/http: timeout awaiting response headers` on long-running calls such as PFX + enrollment. + - Reproduced by upstream swagger: **No**. + - Regression test pins this: **Yes** — `TestBuildHttpClientV2_ClientTimeoutPropagation` in + both `v1/client_test.go` and `v2/client_test.go` calls `buildHttpClientV2()` against a + fake Command server and asserts the resulting `CommandAuthConfigBasic.HttpClientTimeout` + and derived `BuildTransport().ResponseHeaderTimeout` reflect the configured value. That + test is itself hermetic against ambient `KEYFACTOR_SKIP_VERIFY` / + `KEYFACTOR_CA_CERT` / `KEYFACTOR_CLIENT_TIMEOUT` env values (see the `unsetEnvForTest` + helper in the same file) so it can't be spuriously broken by a caller's shell + environment. + - Action: **preserve**. + +### `v24/api/keyfactor/v1/model_certificate_authorities_certificate_authority_request.go` + `..._response.go` + +5. **`229db7d`** — adds `UseForEnrollment *bool`, `CertificateCleanupEnabled NullableBool`, + `DeleteWithArchivedKey NullableBool`, `TimeAfterExpiration NullableInt32`, and + `TimeAfterExpirationUnits *CSSCMSDataModelEnumsCertificateCleanupTimeUnits` fields (with + generator-style getters/setters/Has*) to both the CA request and response models. + - Reproduced by upstream swagger: **No**. + - Regression test pins this: **Yes** — `TestCARequestFields_CleanupAndEnrollment`, + `TestCAResponseFields_CleanupAndEnrollment`, `TestCARequestFields_NilSafety`, and + `TestCAResponseFields_NilSafety` in `model_certificate_authorities_test.go` exercise every + setter/getter/Has* and nil-receiver safety for all five fields on both structs. + - Action: **preserve**. + +### `v24/api/keyfactor/v1/model_css_cms_core_enums_enrollment_type.go` + +6. **`af6340b`** — adds enum value `3` and the bitmask-combination values `5`, `6`, `7` to + `AllowedCSSCMSCoreEnumsEnrollmentTypeEnumValues` (the type is a bitmask: 1=PFX, 2=CSR, so + combined values are valid), with a comment documenting the bitmask semantics. + - Reproduced by upstream swagger: **No**. + - Regression test pins this: **No** — no test in this repo references + `CSSCMSCoreEnumsEnrollmentType`'s allowed-values list. + - Action: **preserve**. + +### `v24/api/keyfactor/v1/model_css_cms_core_enums_key_retention_policy.go` + +7. **`af6340b`** — rewrites `UnmarshalJSON` to try integer form first (original generated + behavior), then fall back to a new `keyRetentionPolicyStringToInt` map for EJBCA's + string-form responses (e.g. `"None"`, `"ShortTerm"`), defaulting unknown strings to `0` + (`None`) rather than erroring, to avoid breaking reads of new/unknown values. + - Reproduced by upstream swagger: **No**. + - Regression test pins this: **No** — no test in this repo exercises + `CSSCMSCoreEnumsKeyRetentionPolicy.UnmarshalJSON`'s string-form or unknown-value paths. + - Action: **preserve**. + +### `v24/api/keyfactor/v1/model_css_cms_data_model_enums_certificate_cleanup_time_units.go` + +8. **`af6340b`** — new file (does not exist before this commit). Hand-authored in the exact + style of a generated enum file (including the generator's `DO NOT EDIT` header and the + known openapi-generator template artifact where `Parse()` unconditionally returns an error + for enums generated with no string-value mapping, before checking a `stringsToEnum` map + that is always empty — this is not new breakage, it reproduces a real upstream generator + quirk found on comparable enum files elsewhere in this SDK). Defines + `CSSCMSDataModelEnumsCertificateCleanupTimeUnits` (0=Days, 1=Weeks, 2=Months), consumed by + the CA and template cleanup fields added in the same commit / in `229db7d`. + - Reproduced by upstream swagger: **No** — hand-created, not generated. + - Regression test pins this: **Compile-only** — `TestCARequestFields_CleanupAndEnrollment` + references the `CSSCMSDATAMODELENUMSCERTIFICATECLEANUPTIMEUNITS__1` constant, so removing + the type breaks compilation, but no test exercises its `UnmarshalJSON`/`Parse` behavior. + - Action: **preserve**. + +### `v24/api/keyfactor/v1/model_system_day_of_week.go` + `v24/api/keyfactor/v2/model_system_day_of_week.go` + +9. **`96dd817`** — rewrites `SystemDayOfWeek.UnmarshalJSON` to try the integer form first + (preserving the original generated validation against + `AllowedSystemDayOfWeekEnumValues`), then fall back to the existing `Parse()` day-name + mapping (e.g. `"Monday"`) when the payload is a JSON string, since Keyfactor Command + serializes `WeeklyModel.Days` as day-name strings in some API responses. Applied + identically to both v1 and v2 packages. + - Reproduced by upstream swagger: **No**. + - Regression test pins this: **Yes** — `TestSystemDayOfWeek_UnmarshalJSON_IntForm`, + `_StringForm`, `_InvalidString`, `_OutOfRangeInt`, `TestWeeklyModel_UnmarshalJSON_DayNameStrings`, + and `_DayIndexInts` in `model_system_day_of_week_test.go` cover both forms and error + cases. + - Action: **preserve**. + +### `v24/api/keyfactor/v1/model_templates_template_retrieval_response.go` + `model_templates_template_update_request.go` + +10. **`af6340b`** — adds `CertificateCleanupEnabled NullableBool`, `TimeAfterExpiration + NullableInt32`, `TimeAfterExpirationUnits + *CSSCMSDataModelEnumsCertificateCleanupTimeUnits`, `DeleteWithArchivedKey NullableBool`, + and (retrieval response only) `Manageability NullableInt32` fields, with + generator-style getters/setters/Has*/`ToMap` entries. `Manageability` was present in + actual JSON responses but missing from the Go struct, so it always deserialized as the + zero value before this fix. + - Reproduced by upstream swagger: **No**. + - Regression test pins this: **No** — no test in this repo references + `TemplatesTemplateRetrievalResponse`, `TemplatesTemplateUpdateRequest`, or + `Manageability`. + - Action: **preserve**. + +--- + +## v25 (partial — not a full audit) + +The v25 module (baseline `536f3e2`) independently carries its own OAuth-field-restoration +(`0c1df4d`) and port-443-guard (`b374f3d`) hand-edits, predating this document and this +branch. Those have **not** been audited against this document's conventions and are +deliberately **not** claimed as covered here — see "Scope" above. + +The one v25 edit made as part of this change set: + +1. **`db2be75`** — *fix(v25): plumb Server.ClientTimeout into rebuilt auth config* — the same + edit as v24 entry #4 above, applied to `v25/api/keyfactor/v1/client.go` and + `v25/api/keyfactor/v2/client.go`. Required bumping v25's `keyfactor-auth-client-go` + dependency from `v1.3.0` to `v1.6.0-rc.2` (the first version with `Server.ClientTimeout`); + verified no API compatibility breaks (`go mod tidy && go build ./...` clean). + - Reproduced by upstream swagger: **No**. + - Regression test pins this: **Yes** — `TestBuildHttpClientV2_ClientTimeoutPropagation`, + ported verbatim from v24, in both v25 v1 and v2 `client_test.go`. + - Action: **preserve**. + +2. Also ported to v25 as part of this change set: `TestPrepareRequest_Port443Guard` (see v24 + entry #3), protecting v25's pre-existing `b374f3d` port-443 guard, which had the same + "unprotected hand-edit" gap as v24's copy. + +**Not fixed in this change set:** the repo-root `api/keyfactor/` module and the `v2/` +(`github.com/Keyfactor/keyfactor-go-client-sdk/v2`) module contain the identical +`buildHttpClientV2` `ClientTimeout`-drop bug. A dependency bump to `keyfactor-auth-client-go +v1.6.0-rc.2` was verified to build cleanly in both (`go mod tidy && go build ./...`, +`GOWORK=off`), so the bug is technically fixable the same way. It was deliberately left +unfixed here because: (a) neither module has a single `*_test.go` file today — fixing "with +equivalent tests" per the standard set by v24/v25 would mean authoring first-ever test +scaffolding for these modules, a materially larger and separate undertaking than the +one-line v24/v25 fix; (b) both appear to be legacy/superseded major-API-version modules (the +README states v25 is "the latest available SDK"; `CHANGELOG.md` has no recent root/v2-module +entries); (c) the known primary consumer (terraform-provider-keyfactor) pins only the v24 +module. If an active consumer of the root or `v2` module surfaces, file a follow-up issue +rather than assuming this gap is safe to ignore indefinitely. From 7b5c4ac84f3c66dd65d7f91770974bdfe71335e9 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Fri, 14 Aug 2026 09:58:55 -0700 Subject: [PATCH 10/13] test(client): close OAuth AccessToken hand-edit coverage gap in buildHttpClientV2 HAND_EDITS.md's v24 entry #2 claimed TestCommandConfigOauth_AccessTokenFieldPropagation pinned the AccessToken/Audience/Scopes restoration in buildHttpClientV2's OAuth branch. It doesn't: that test only builds its own local CommandConfigOauth literal and never calls buildHttpClientV2, so deleting the three fields from the actual hand-edit still compiled and passed. Add TestBuildHttpClientV2_OAuthAccessTokenPropagation, which drives buildHttpClientV2 end-to-end against a fake Command server with an access-token-only Server config and asserts the token reaches the wire as a Bearer credential. Reproduced red (fails with a client_id-required error) against the pre-fix literal, green with it restored. Applied identically to v24 v1/v2 and ported to v25 v1/v2, which had the same gap. Corrected the HAND_EDITS.md entry to describe what actually protects this hand-edit now. --- HAND_EDITS.md | 24 +++++++- v24/api/keyfactor/v1/client_test.go | 89 +++++++++++++++++++++++++++++ v24/api/keyfactor/v2/client_test.go | 89 +++++++++++++++++++++++++++++ v25/api/keyfactor/v1/client_test.go | 89 +++++++++++++++++++++++++++++ v25/api/keyfactor/v2/client_test.go | 89 +++++++++++++++++++++++++++++ 5 files changed, 377 insertions(+), 3 deletions(-) diff --git a/HAND_EDITS.md b/HAND_EDITS.md index 65db980..d8f098c 100644 --- a/HAND_EDITS.md +++ b/HAND_EDITS.md @@ -62,9 +62,20 @@ For each file, hand-edits are listed in commit order (oldest first). Each entry `access_token`-only authentication (callers supplying just hostname + access token, no `client_id`/`client_secret`/`token_url`). - Reproduced by upstream swagger: **No**. - - Regression test pins this: **Yes** — - `TestCommandConfigOauth_AccessTokenFieldPropagation` fails to compile if any of the three - fields are removed from either struct, and asserts they propagate correctly. + - Regression test pins this: **Yes** — `TestBuildHttpClientV2_OAuthAccessTokenPropagation` + drives `buildHttpClientV2` itself end-to-end against a fake Command server (the same + `newFakeCommandServer`-style harness `TestBuildHttpClientV2_ClientTimeoutPropagation` uses + for entry #4 below) with a `Server{AccessToken: ...}` and no `client_id`/`client_secret`/ + `token_url`, then asserts the request that reaches the fake server carries the token as a + `Bearer` credential. Deleting `AccessToken`/`Audience`/`Scopes` from this file's literal + makes that test fail (verified by reproduction against the pre-fix code). Note: + `TestCommandConfigOauth_AccessTokenFieldPropagation`, despite its name and despite living + in this file, does **not** exercise `buildHttpClientV2` — it only builds its own local + `auth_providers.CommandConfigOauth{}` literal and would keep passing even if this file's + literal dropped the three fields. That test still has value (it compile-pins the upstream + `auth_providers` field names existing at all), but it is + `TestBuildHttpClientV2_OAuthAccessTokenPropagation`, not it, that protects this specific + hand-edit. - Action: **preserve**. (This is the second time these fields were silently dropped and restored — see the near-identical prior loss at `2b88eb2` — so it is a high-value preserve.) @@ -208,6 +219,13 @@ The one v25 edit made as part of this change set: entry #3), protecting v25's pre-existing `b374f3d` port-443 guard, which had the same "unprotected hand-edit" gap as v24's copy. +3. Also ported to v25 as part of this change set: `TestBuildHttpClientV2_OAuthAccessTokenPropagation` + (see v24 entry #2), protecting v25's pre-existing `0c1df4d` OAuth-field-restoration, which had + the same "unprotected hand-edit" gap as v24's copy before this change set (the only test that + named the fields, `TestCommandConfigOauth_AccessTokenFieldPropagation`, never called + `buildHttpClientV2`). This does not constitute a full audit of `0c1df4d` against this + document's conventions — see "Scope" above — only this one test gap is closed. + **Not fixed in this change set:** the repo-root `api/keyfactor/` module and the `v2/` (`github.com/Keyfactor/keyfactor-go-client-sdk/v2`) module contain the identical `buildHttpClientV2` `ClientTimeout`-drop bug. A dependency bump to `keyfactor-auth-client-go diff --git a/v24/api/keyfactor/v1/client_test.go b/v24/api/keyfactor/v1/client_test.go index 3bf0088..8fced1e 100644 --- a/v24/api/keyfactor/v1/client_test.go +++ b/v24/api/keyfactor/v1/client_test.go @@ -215,6 +215,95 @@ func TestBuildHttpClientV2_ClientTimeoutPropagation(t *testing.T) { } } +// newFakeCommandServerCapturingAuth is like newFakeCommandServer but also +// records the Authorization header of the last request it received, so a +// test can assert which credential actually reached the wire (rather than +// only asserting the shape of a struct literal that never leaves the test). +func newFakeCommandServerCapturingAuth(t *testing.T) (*httptest.Server, *string) { + t.Helper() + var gotAuth string + server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotAuth = r.Header.Get("Authorization") + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`["endpoint1"]`)) + })) + t.Cleanup(server.Close) + return server, &gotAuth +} + +// TestBuildHttpClientV2_OAuthAccessTokenPropagation is a regression test for +// the hand-edit cataloged in HAND_EDITS.md (v24 entry #2) that restores +// AccessToken, Audience, and Scopes to the auth_providers.CommandConfigOauth{} +// literal inside buildHttpClientV2's OAuth branch. Those three fields were +// silently dropped once already (2b88eb2) and restored (229db7d); this test +// -- unlike TestCommandConfigOauth_AccessTokenFieldPropagation above, which +// only constructs its own local CommandConfigOauth literal and would keep +// passing even if buildHttpClientV2's actual literal dropped these fields -- +// drives buildHttpClientV2 itself end-to-end against a fake Command server +// and asserts the pre-fetched access token actually reaches the wire as a +// Bearer credential. +func TestBuildHttpClientV2_OAuthAccessTokenPropagation(t *testing.T) { + // See TestBuildHttpClientV2_ClientTimeoutPropagation for why each of + // these is pinned/unset: makes the test hermetic against the ambient + // shell environment. + t.Setenv(auth_providers.EnvKeyfactorSkipVerify, "true") + unsetEnvForTest(t, auth_providers.EnvKeyfactorCACert) + unsetEnvForTest(t, auth_providers.EnvKeyfactorClientTimeout) + unsetEnvForTest(t, auth_providers.EnvKeyfactorAccessToken) + unsetEnvForTest(t, auth_providers.EnvKeyfactorClientID) + + server, gotAuth := newFakeCommandServerCapturingAuth(t) + u, uErr := url.Parse(server.URL) + if uErr != nil { + t.Fatalf("failed to parse test server URL: %v", uErr) + } + + srv := &auth_providers.Server{ + Host: u.Host, + // Deliberately no ClientID/ClientSecret/OAuthTokenUrl: this is the + // pre-fetched access_token-only auth path that 2b88eb2 broke. + AccessToken: "mytoken-abc123", + Audience: "https://my.audience.example.com", + Scopes: []string{"read", "write"}, + APIPath: "api", + SkipTLSVerify: true, + ClientTimeout: 60, + } + + authCfg, err := buildHttpClientV2(srv) + if err != nil { + t.Fatalf("buildHttpClientV2() returned unexpected error: %v", err) + } + + oauthCfg, ok := authCfg.(*auth_providers.CommandConfigOauth) + if !ok { + t.Fatalf("expected AuthConfig to be *auth_providers.CommandConfigOauth, got %T", authCfg) + } + + if oauthCfg.AccessToken != "mytoken-abc123" { + t.Errorf("CommandConfigOauth.AccessToken = %q, want %q", oauthCfg.AccessToken, "mytoken-abc123") + } + if oauthCfg.Audience != "https://my.audience.example.com" { + t.Errorf("CommandConfigOauth.Audience = %q, want %q", oauthCfg.Audience, "https://my.audience.example.com") + } + if !reflect.DeepEqual(oauthCfg.Scopes, []string{"read", "write"}) { + t.Errorf("CommandConfigOauth.Scopes = %v, want %v", oauthCfg.Scopes, []string{"read", "write"}) + } + + // The real assertion: buildHttpClientV2's internal Authenticate() call + // made an actual HTTP request to the fake Command server, and that + // request must carry the access token as a Bearer credential. If + // AccessToken were dropped from buildHttpClientV2's literal, the OAuth + // branch would fall through to the client-credentials grant with no + // ClientID/ClientSecret/TokenURL and buildHttpClientV2 would return an + // error above instead of ever reaching this assertion. + wantAuth := "Bearer mytoken-abc123" + if *gotAuth != wantAuth { + t.Errorf("request Authorization header = %q, want %q", *gotAuth, wantAuth) + } +} + // TestPrepareRequest_Port443Guard is a regression test for the hand-edit in // commit 229db7d that added "&& serverConfig.Port != 443" to prepareRequest's // port guard. Without it, a Server configured with Port: 443 (the default diff --git a/v24/api/keyfactor/v2/client_test.go b/v24/api/keyfactor/v2/client_test.go index e0ea389..5df6091 100644 --- a/v24/api/keyfactor/v2/client_test.go +++ b/v24/api/keyfactor/v2/client_test.go @@ -215,6 +215,95 @@ func TestBuildHttpClientV2_ClientTimeoutPropagation(t *testing.T) { } } +// newFakeCommandServerCapturingAuth is like newFakeCommandServer but also +// records the Authorization header of the last request it received, so a +// test can assert which credential actually reached the wire (rather than +// only asserting the shape of a struct literal that never leaves the test). +func newFakeCommandServerCapturingAuth(t *testing.T) (*httptest.Server, *string) { + t.Helper() + var gotAuth string + server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotAuth = r.Header.Get("Authorization") + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`["endpoint1"]`)) + })) + t.Cleanup(server.Close) + return server, &gotAuth +} + +// TestBuildHttpClientV2_OAuthAccessTokenPropagation is a regression test for +// the hand-edit cataloged in HAND_EDITS.md (v24 entry #2) that restores +// AccessToken, Audience, and Scopes to the auth_providers.CommandConfigOauth{} +// literal inside buildHttpClientV2's OAuth branch. Those three fields were +// silently dropped once already (2b88eb2) and restored (229db7d); this test +// -- unlike TestCommandConfigOauth_AccessTokenFieldPropagation above, which +// only constructs its own local CommandConfigOauth literal and would keep +// passing even if buildHttpClientV2's actual literal dropped these fields -- +// drives buildHttpClientV2 itself end-to-end against a fake Command server +// and asserts the pre-fetched access token actually reaches the wire as a +// Bearer credential. +func TestBuildHttpClientV2_OAuthAccessTokenPropagation(t *testing.T) { + // See TestBuildHttpClientV2_ClientTimeoutPropagation for why each of + // these is pinned/unset: makes the test hermetic against the ambient + // shell environment. + t.Setenv(auth_providers.EnvKeyfactorSkipVerify, "true") + unsetEnvForTest(t, auth_providers.EnvKeyfactorCACert) + unsetEnvForTest(t, auth_providers.EnvKeyfactorClientTimeout) + unsetEnvForTest(t, auth_providers.EnvKeyfactorAccessToken) + unsetEnvForTest(t, auth_providers.EnvKeyfactorClientID) + + server, gotAuth := newFakeCommandServerCapturingAuth(t) + u, uErr := url.Parse(server.URL) + if uErr != nil { + t.Fatalf("failed to parse test server URL: %v", uErr) + } + + srv := &auth_providers.Server{ + Host: u.Host, + // Deliberately no ClientID/ClientSecret/OAuthTokenUrl: this is the + // pre-fetched access_token-only auth path that 2b88eb2 broke. + AccessToken: "mytoken-abc123", + Audience: "https://my.audience.example.com", + Scopes: []string{"read", "write"}, + APIPath: "api", + SkipTLSVerify: true, + ClientTimeout: 60, + } + + authCfg, err := buildHttpClientV2(srv) + if err != nil { + t.Fatalf("buildHttpClientV2() returned unexpected error: %v", err) + } + + oauthCfg, ok := authCfg.(*auth_providers.CommandConfigOauth) + if !ok { + t.Fatalf("expected AuthConfig to be *auth_providers.CommandConfigOauth, got %T", authCfg) + } + + if oauthCfg.AccessToken != "mytoken-abc123" { + t.Errorf("CommandConfigOauth.AccessToken = %q, want %q", oauthCfg.AccessToken, "mytoken-abc123") + } + if oauthCfg.Audience != "https://my.audience.example.com" { + t.Errorf("CommandConfigOauth.Audience = %q, want %q", oauthCfg.Audience, "https://my.audience.example.com") + } + if !reflect.DeepEqual(oauthCfg.Scopes, []string{"read", "write"}) { + t.Errorf("CommandConfigOauth.Scopes = %v, want %v", oauthCfg.Scopes, []string{"read", "write"}) + } + + // The real assertion: buildHttpClientV2's internal Authenticate() call + // made an actual HTTP request to the fake Command server, and that + // request must carry the access token as a Bearer credential. If + // AccessToken were dropped from buildHttpClientV2's literal, the OAuth + // branch would fall through to the client-credentials grant with no + // ClientID/ClientSecret/TokenURL and buildHttpClientV2 would return an + // error above instead of ever reaching this assertion. + wantAuth := "Bearer mytoken-abc123" + if *gotAuth != wantAuth { + t.Errorf("request Authorization header = %q, want %q", *gotAuth, wantAuth) + } +} + // TestPrepareRequest_Port443Guard is a regression test for the hand-edit in // commit 229db7d that added "&& serverConfig.Port != 443" to prepareRequest's // port guard. Without it, a Server configured with Port: 443 (the default diff --git a/v25/api/keyfactor/v1/client_test.go b/v25/api/keyfactor/v1/client_test.go index 3bf0088..8fced1e 100644 --- a/v25/api/keyfactor/v1/client_test.go +++ b/v25/api/keyfactor/v1/client_test.go @@ -215,6 +215,95 @@ func TestBuildHttpClientV2_ClientTimeoutPropagation(t *testing.T) { } } +// newFakeCommandServerCapturingAuth is like newFakeCommandServer but also +// records the Authorization header of the last request it received, so a +// test can assert which credential actually reached the wire (rather than +// only asserting the shape of a struct literal that never leaves the test). +func newFakeCommandServerCapturingAuth(t *testing.T) (*httptest.Server, *string) { + t.Helper() + var gotAuth string + server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotAuth = r.Header.Get("Authorization") + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`["endpoint1"]`)) + })) + t.Cleanup(server.Close) + return server, &gotAuth +} + +// TestBuildHttpClientV2_OAuthAccessTokenPropagation is a regression test for +// the hand-edit cataloged in HAND_EDITS.md (v24 entry #2) that restores +// AccessToken, Audience, and Scopes to the auth_providers.CommandConfigOauth{} +// literal inside buildHttpClientV2's OAuth branch. Those three fields were +// silently dropped once already (2b88eb2) and restored (229db7d); this test +// -- unlike TestCommandConfigOauth_AccessTokenFieldPropagation above, which +// only constructs its own local CommandConfigOauth literal and would keep +// passing even if buildHttpClientV2's actual literal dropped these fields -- +// drives buildHttpClientV2 itself end-to-end against a fake Command server +// and asserts the pre-fetched access token actually reaches the wire as a +// Bearer credential. +func TestBuildHttpClientV2_OAuthAccessTokenPropagation(t *testing.T) { + // See TestBuildHttpClientV2_ClientTimeoutPropagation for why each of + // these is pinned/unset: makes the test hermetic against the ambient + // shell environment. + t.Setenv(auth_providers.EnvKeyfactorSkipVerify, "true") + unsetEnvForTest(t, auth_providers.EnvKeyfactorCACert) + unsetEnvForTest(t, auth_providers.EnvKeyfactorClientTimeout) + unsetEnvForTest(t, auth_providers.EnvKeyfactorAccessToken) + unsetEnvForTest(t, auth_providers.EnvKeyfactorClientID) + + server, gotAuth := newFakeCommandServerCapturingAuth(t) + u, uErr := url.Parse(server.URL) + if uErr != nil { + t.Fatalf("failed to parse test server URL: %v", uErr) + } + + srv := &auth_providers.Server{ + Host: u.Host, + // Deliberately no ClientID/ClientSecret/OAuthTokenUrl: this is the + // pre-fetched access_token-only auth path that 2b88eb2 broke. + AccessToken: "mytoken-abc123", + Audience: "https://my.audience.example.com", + Scopes: []string{"read", "write"}, + APIPath: "api", + SkipTLSVerify: true, + ClientTimeout: 60, + } + + authCfg, err := buildHttpClientV2(srv) + if err != nil { + t.Fatalf("buildHttpClientV2() returned unexpected error: %v", err) + } + + oauthCfg, ok := authCfg.(*auth_providers.CommandConfigOauth) + if !ok { + t.Fatalf("expected AuthConfig to be *auth_providers.CommandConfigOauth, got %T", authCfg) + } + + if oauthCfg.AccessToken != "mytoken-abc123" { + t.Errorf("CommandConfigOauth.AccessToken = %q, want %q", oauthCfg.AccessToken, "mytoken-abc123") + } + if oauthCfg.Audience != "https://my.audience.example.com" { + t.Errorf("CommandConfigOauth.Audience = %q, want %q", oauthCfg.Audience, "https://my.audience.example.com") + } + if !reflect.DeepEqual(oauthCfg.Scopes, []string{"read", "write"}) { + t.Errorf("CommandConfigOauth.Scopes = %v, want %v", oauthCfg.Scopes, []string{"read", "write"}) + } + + // The real assertion: buildHttpClientV2's internal Authenticate() call + // made an actual HTTP request to the fake Command server, and that + // request must carry the access token as a Bearer credential. If + // AccessToken were dropped from buildHttpClientV2's literal, the OAuth + // branch would fall through to the client-credentials grant with no + // ClientID/ClientSecret/TokenURL and buildHttpClientV2 would return an + // error above instead of ever reaching this assertion. + wantAuth := "Bearer mytoken-abc123" + if *gotAuth != wantAuth { + t.Errorf("request Authorization header = %q, want %q", *gotAuth, wantAuth) + } +} + // TestPrepareRequest_Port443Guard is a regression test for the hand-edit in // commit 229db7d that added "&& serverConfig.Port != 443" to prepareRequest's // port guard. Without it, a Server configured with Port: 443 (the default diff --git a/v25/api/keyfactor/v2/client_test.go b/v25/api/keyfactor/v2/client_test.go index e0ea389..5df6091 100644 --- a/v25/api/keyfactor/v2/client_test.go +++ b/v25/api/keyfactor/v2/client_test.go @@ -215,6 +215,95 @@ func TestBuildHttpClientV2_ClientTimeoutPropagation(t *testing.T) { } } +// newFakeCommandServerCapturingAuth is like newFakeCommandServer but also +// records the Authorization header of the last request it received, so a +// test can assert which credential actually reached the wire (rather than +// only asserting the shape of a struct literal that never leaves the test). +func newFakeCommandServerCapturingAuth(t *testing.T) (*httptest.Server, *string) { + t.Helper() + var gotAuth string + server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotAuth = r.Header.Get("Authorization") + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`["endpoint1"]`)) + })) + t.Cleanup(server.Close) + return server, &gotAuth +} + +// TestBuildHttpClientV2_OAuthAccessTokenPropagation is a regression test for +// the hand-edit cataloged in HAND_EDITS.md (v24 entry #2) that restores +// AccessToken, Audience, and Scopes to the auth_providers.CommandConfigOauth{} +// literal inside buildHttpClientV2's OAuth branch. Those three fields were +// silently dropped once already (2b88eb2) and restored (229db7d); this test +// -- unlike TestCommandConfigOauth_AccessTokenFieldPropagation above, which +// only constructs its own local CommandConfigOauth literal and would keep +// passing even if buildHttpClientV2's actual literal dropped these fields -- +// drives buildHttpClientV2 itself end-to-end against a fake Command server +// and asserts the pre-fetched access token actually reaches the wire as a +// Bearer credential. +func TestBuildHttpClientV2_OAuthAccessTokenPropagation(t *testing.T) { + // See TestBuildHttpClientV2_ClientTimeoutPropagation for why each of + // these is pinned/unset: makes the test hermetic against the ambient + // shell environment. + t.Setenv(auth_providers.EnvKeyfactorSkipVerify, "true") + unsetEnvForTest(t, auth_providers.EnvKeyfactorCACert) + unsetEnvForTest(t, auth_providers.EnvKeyfactorClientTimeout) + unsetEnvForTest(t, auth_providers.EnvKeyfactorAccessToken) + unsetEnvForTest(t, auth_providers.EnvKeyfactorClientID) + + server, gotAuth := newFakeCommandServerCapturingAuth(t) + u, uErr := url.Parse(server.URL) + if uErr != nil { + t.Fatalf("failed to parse test server URL: %v", uErr) + } + + srv := &auth_providers.Server{ + Host: u.Host, + // Deliberately no ClientID/ClientSecret/OAuthTokenUrl: this is the + // pre-fetched access_token-only auth path that 2b88eb2 broke. + AccessToken: "mytoken-abc123", + Audience: "https://my.audience.example.com", + Scopes: []string{"read", "write"}, + APIPath: "api", + SkipTLSVerify: true, + ClientTimeout: 60, + } + + authCfg, err := buildHttpClientV2(srv) + if err != nil { + t.Fatalf("buildHttpClientV2() returned unexpected error: %v", err) + } + + oauthCfg, ok := authCfg.(*auth_providers.CommandConfigOauth) + if !ok { + t.Fatalf("expected AuthConfig to be *auth_providers.CommandConfigOauth, got %T", authCfg) + } + + if oauthCfg.AccessToken != "mytoken-abc123" { + t.Errorf("CommandConfigOauth.AccessToken = %q, want %q", oauthCfg.AccessToken, "mytoken-abc123") + } + if oauthCfg.Audience != "https://my.audience.example.com" { + t.Errorf("CommandConfigOauth.Audience = %q, want %q", oauthCfg.Audience, "https://my.audience.example.com") + } + if !reflect.DeepEqual(oauthCfg.Scopes, []string{"read", "write"}) { + t.Errorf("CommandConfigOauth.Scopes = %v, want %v", oauthCfg.Scopes, []string{"read", "write"}) + } + + // The real assertion: buildHttpClientV2's internal Authenticate() call + // made an actual HTTP request to the fake Command server, and that + // request must carry the access token as a Bearer credential. If + // AccessToken were dropped from buildHttpClientV2's literal, the OAuth + // branch would fall through to the client-credentials grant with no + // ClientID/ClientSecret/TokenURL and buildHttpClientV2 would return an + // error above instead of ever reaching this assertion. + wantAuth := "Bearer mytoken-abc123" + if *gotAuth != wantAuth { + t.Errorf("request Authorization header = %q, want %q", *gotAuth, wantAuth) + } +} + // TestPrepareRequest_Port443Guard is a regression test for the hand-edit in // commit 229db7d that added "&& serverConfig.Port != 443" to prepareRequest's // port guard. Without it, a Server configured with Port: 443 (the default From a6ec53934445c349bba043bbcc05826bf0c6bfc4 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Fri, 14 Aug 2026 10:17:13 -0700 Subject: [PATCH 11/13] chore(deps): bump keyfactor-auth-client-go to v1.6.0-rc.3 in v24/v25 Picks up the fully-converged fix set: gated ClientTimeout persistence across all three concrete auth types (delegated to the base type, affecting CommandConfigOauth.GetServerConfig() which buildHttpClientV2 constructs and calls), a BOM-prefix bypass fix in nested-JSON secret redaction, an unbounded MaxConnsPerHost fix, and body redaction coverage for JSON-in-string values such as Command's certificate-store Properties field and PAM's Value key. Validated against the published tag with no replace directive: go build and go test (-vet=off for the pre-existing generated-code printf vet failure, unrelated) are green in both v1 and v2 packages of both modules, including the ClientTimeout/OAuth-field propagation and port-443 guard regression tests and their env-isolation hermeticity helpers. --- v24/go.mod | 2 +- v24/go.sum | 4 ++-- v25/go.mod | 2 +- v25/go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/v24/go.mod b/v24/go.mod index 3ed3a82..0870744 100644 --- a/v24/go.mod +++ b/v24/go.mod @@ -2,7 +2,7 @@ module github.com/Keyfactor/keyfactor-go-client-sdk/v24 go 1.24.0 -require github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.2 +require github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.3 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect diff --git a/v24/go.sum b/v24/go.sum index af2a2f3..b11104c 100644 --- a/v24/go.sum +++ b/v24/go.sum @@ -14,8 +14,8 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= -github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.2 h1:wp7LBuNSpHZYPlzEuipNeuWwwBow8lgLj8lD2gMivhM= -github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.2/go.mod h1:rFBZPMSHWwWuUwE1kXhLsDaOxjGiHMbXTTEni8Dmufo= +github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.3 h1:1j0ZVOmay13SrpQkXrfaGBml8pEAsE7sDJzHHK6C2+Y= +github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.3/go.mod h1:rFBZPMSHWwWuUwE1kXhLsDaOxjGiHMbXTTEni8Dmufo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/v25/go.mod b/v25/go.mod index 21fe280..d4d2f32 100644 --- a/v25/go.mod +++ b/v25/go.mod @@ -2,7 +2,7 @@ module github.com/Keyfactor/keyfactor-go-client-sdk/v25 go 1.24.0 -require github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.2 +require github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.3 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect diff --git a/v25/go.sum b/v25/go.sum index af2a2f3..b11104c 100644 --- a/v25/go.sum +++ b/v25/go.sum @@ -14,8 +14,8 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= -github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.2 h1:wp7LBuNSpHZYPlzEuipNeuWwwBow8lgLj8lD2gMivhM= -github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.2/go.mod h1:rFBZPMSHWwWuUwE1kXhLsDaOxjGiHMbXTTEni8Dmufo= +github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.3 h1:1j0ZVOmay13SrpQkXrfaGBml8pEAsE7sDJzHHK6C2+Y= +github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.3/go.mod h1:rFBZPMSHWwWuUwE1kXhLsDaOxjGiHMbXTTEni8Dmufo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= From e4ab17c28f2318537495b0fcab631b519e325a8e Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Mon, 17 Aug 2026 13:45:54 -0700 Subject: [PATCH 12/13] chore(deps): bump keyfactor-auth-client-go to v1.6.0-rc.4 in v24/v25 Purely internal timeout-plumbing fix inside kfc-auth's OAuth token-fetch path (bounds the TCP dial phase and overall call during Configure); no public API surface change. Validated against the published tag with no replace directive in both modules. --- v24/go.mod | 2 +- v24/go.sum | 4 ++-- v25/go.mod | 2 +- v25/go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/v24/go.mod b/v24/go.mod index 0870744..4a02c3f 100644 --- a/v24/go.mod +++ b/v24/go.mod @@ -2,7 +2,7 @@ module github.com/Keyfactor/keyfactor-go-client-sdk/v24 go 1.24.0 -require github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.3 +require github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.4 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect diff --git a/v24/go.sum b/v24/go.sum index b11104c..a008eb2 100644 --- a/v24/go.sum +++ b/v24/go.sum @@ -14,8 +14,8 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= -github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.3 h1:1j0ZVOmay13SrpQkXrfaGBml8pEAsE7sDJzHHK6C2+Y= -github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.3/go.mod h1:rFBZPMSHWwWuUwE1kXhLsDaOxjGiHMbXTTEni8Dmufo= +github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.4 h1:pDKfmVk74gRjwtqtz7khMMM+sc6SxQAfcla+f7Q3ZuY= +github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.4/go.mod h1:rFBZPMSHWwWuUwE1kXhLsDaOxjGiHMbXTTEni8Dmufo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/v25/go.mod b/v25/go.mod index d4d2f32..6d62fbb 100644 --- a/v25/go.mod +++ b/v25/go.mod @@ -2,7 +2,7 @@ module github.com/Keyfactor/keyfactor-go-client-sdk/v25 go 1.24.0 -require github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.3 +require github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.4 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect diff --git a/v25/go.sum b/v25/go.sum index b11104c..a008eb2 100644 --- a/v25/go.sum +++ b/v25/go.sum @@ -14,8 +14,8 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= -github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.3 h1:1j0ZVOmay13SrpQkXrfaGBml8pEAsE7sDJzHHK6C2+Y= -github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.3/go.mod h1:rFBZPMSHWwWuUwE1kXhLsDaOxjGiHMbXTTEni8Dmufo= +github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.4 h1:pDKfmVk74gRjwtqtz7khMMM+sc6SxQAfcla+f7Q3ZuY= +github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.4/go.mod h1:rFBZPMSHWwWuUwE1kXhLsDaOxjGiHMbXTTEni8Dmufo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= From c23b1b79e9a460b99fd4e06fb9e277651ee2b870 Mon Sep 17 00:00:00 2001 From: spbsoluble <1661003+spbsoluble@users.noreply.github.com> Date: Mon, 17 Aug 2026 15:11:09 -0700 Subject: [PATCH 13/13] chore(deps): bump keyfactor-auth-client-go to v1.6.0-rc.5 in v24/v25 rc.5 fixes a subtle latency bug in the client_credentials OAuth flow: golang.org/x/oauth2's AuthStyle-probing behavior was causing two sequential HTTP round trips per token fetch attempt, and each attempt got its own fresh timeout budget instead of sharing one deadline, silently doubling worst-case latency under retry. No public API surface changed; validated against the published tag with no replace directive in both modules. --- v24/go.mod | 2 +- v24/go.sum | 4 ++-- v25/go.mod | 2 +- v25/go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/v24/go.mod b/v24/go.mod index 4a02c3f..309020c 100644 --- a/v24/go.mod +++ b/v24/go.mod @@ -2,7 +2,7 @@ module github.com/Keyfactor/keyfactor-go-client-sdk/v24 go 1.24.0 -require github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.4 +require github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.5 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect diff --git a/v24/go.sum b/v24/go.sum index a008eb2..57b346a 100644 --- a/v24/go.sum +++ b/v24/go.sum @@ -14,8 +14,8 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= -github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.4 h1:pDKfmVk74gRjwtqtz7khMMM+sc6SxQAfcla+f7Q3ZuY= -github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.4/go.mod h1:rFBZPMSHWwWuUwE1kXhLsDaOxjGiHMbXTTEni8Dmufo= +github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.5 h1:nsp5hrG7EtGFOAaAIyRHt7FSLSWJSIDz66GrLaJU4yA= +github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.5/go.mod h1:rFBZPMSHWwWuUwE1kXhLsDaOxjGiHMbXTTEni8Dmufo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/v25/go.mod b/v25/go.mod index 6d62fbb..3dad71e 100644 --- a/v25/go.mod +++ b/v25/go.mod @@ -2,7 +2,7 @@ module github.com/Keyfactor/keyfactor-go-client-sdk/v25 go 1.24.0 -require github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.4 +require github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.5 require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect diff --git a/v25/go.sum b/v25/go.sum index a008eb2..57b346a 100644 --- a/v25/go.sum +++ b/v25/go.sum @@ -14,8 +14,8 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= -github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.4 h1:pDKfmVk74gRjwtqtz7khMMM+sc6SxQAfcla+f7Q3ZuY= -github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.4/go.mod h1:rFBZPMSHWwWuUwE1kXhLsDaOxjGiHMbXTTEni8Dmufo= +github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.5 h1:nsp5hrG7EtGFOAaAIyRHt7FSLSWJSIDz66GrLaJU4yA= +github.com/Keyfactor/keyfactor-auth-client-go v1.6.0-rc.5/go.mod h1:rFBZPMSHWwWuUwE1kXhLsDaOxjGiHMbXTTEni8Dmufo= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=