diff --git a/pkg/serviceaccounts/oidc_identities.go b/pkg/serviceaccounts/oidc_identities.go index cd13234c..29008c54 100644 --- a/pkg/serviceaccounts/oidc_identities.go +++ b/pkg/serviceaccounts/oidc_identities.go @@ -14,12 +14,24 @@ type OIDCIdentityQuery struct { Take int `uri:"take" url:"take"` } +// OIDCIdentityClaimCondition is a condition a token claim must pass before Octopus exchanges the token for an access token. +// The token passes the condition when its claim matches any one of the values. Values support the same wildcards as the subject. +type OIDCIdentityClaimCondition struct { + Claim string `json:"Claim"` + Values []string `json:"Values"` +} + type OIDCIdentity struct { Audience string `json:"Audience,omitempty"` Issuer string `json:"Issuer"` Name string `json:"Name"` ServiceAccountID string `json:"ServiceAccountId"` Subject string `json:"Subject"` + + // ClaimConditions has no omitempty on purpose. + // On update, a nil slice is sent as null and Octopus keeps the existing conditions. + // An empty slice is sent as [] and Octopus removes them. + ClaimConditions []OIDCIdentityClaimCondition `json:"ClaimConditions"` resources.Resource } diff --git a/pkg/serviceaccounts/oidc_identities_test.go b/pkg/serviceaccounts/oidc_identities_test.go index 89dc3610..ea44d79b 100644 --- a/pkg/serviceaccounts/oidc_identities_test.go +++ b/pkg/serviceaccounts/oidc_identities_test.go @@ -34,3 +34,45 @@ func TestOIDCIdentity_MarshalJSON_IncludesAudienceWhenSet(t *testing.T) { assert.Equal(t, "api://custom", result["Audience"]) } + +func TestOIDCIdentity_MarshalJSON_SendsNullClaimConditionsWhenUnset(t *testing.T) { + identity := NewOIDCIdentity("ServiceAccounts-1", "name", "issuer", "subject") + + data, err := json.Marshal(identity) + assert.NoError(t, err) + + var result map[string]interface{} + err = json.Unmarshal(data, &result) + assert.NoError(t, err) + + value, hasClaimConditions := result["ClaimConditions"] + assert.True(t, hasClaimConditions) + assert.Nil(t, value, "A nil slice must be sent as null so Octopus keeps the existing conditions") +} + +func TestOIDCIdentity_MarshalJSON_SendsEmptyClaimConditionsToClearThem(t *testing.T) { + identity := NewOIDCIdentity("ServiceAccounts-1", "name", "issuer", "subject") + identity.ClaimConditions = []OIDCIdentityClaimCondition{} + + data, err := json.Marshal(identity) + assert.NoError(t, err) + + assert.Contains(t, string(data), `"ClaimConditions":[]`) +} + +func TestOIDCIdentity_JSONRoundTrip_ClaimConditions(t *testing.T) { + identity := NewOIDCIdentity("ServiceAccounts-1", "name", "issuer", "subject") + identity.ClaimConditions = []OIDCIdentityClaimCondition{ + {Claim: "repository_owner_id", Values: []string{"123456"}}, + {Claim: "environment", Values: []string{"prod", "staging-*"}}, + } + + data, err := json.Marshal(identity) + assert.NoError(t, err) + + var result OIDCIdentity + err = json.Unmarshal(data, &result) + assert.NoError(t, err) + + assert.Equal(t, identity.ClaimConditions, result.ClaimConditions) +} diff --git a/test/e2e/project_trigger_test.go b/test/e2e/project_trigger_test.go index 7e94e794..27e4c72a 100644 --- a/test/e2e/project_trigger_test.go +++ b/test/e2e/project_trigger_test.go @@ -52,8 +52,7 @@ func CreateTestProjectTrigger(t *testing.T, client *client.Client, project *proj start := time.Date(now.Year(), now.Month(), now.Day(), 9, 0, 0, 0, time.UTC) // days := []filters.Weekday{filters.Sunday, filters.Monday} - action := actions.NewAutoDeployAction(createRandomBoolean()) - // action := actions.NewDeployLatestReleaseAction(internal.GetRandomName(), createRandomBoolean(), []string{internal.GetRandomName()}, "") + action := actions.NewDeployLatestReleaseAction(environment.GetID(), createRandomBoolean(), []string{environment.GetID()}, "") // action := actions.NewDeployNewReleaseAction(environment.GetID(), "", nil) // filter := filters.NewDeploymentTargetFilter([]string{}, []string{}, []string{"MachineAvailableForDeployment"}, []string{})