diff --git a/api/autorest-config.yaml b/api/autorest-config.yaml index c113fad4d9..f78f15c56b 100644 --- a/api/autorest-config.yaml +++ b/api/autorest-config.yaml @@ -1,4 +1,6 @@ input-file: redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/hcpclusters/preview/2024-06-10-preview/openapi.json +use: +- "@autorest/go@4.0.0-preview.73" go: namespace: redhatopenshift project-folder: ../internal diff --git a/api/redhatopenshift/HcpCluster.Management/hcpCluster-models.tsp b/api/redhatopenshift/HcpCluster.Management/hcpCluster-models.tsp index 4571421249..e8df1143d7 100644 --- a/api/redhatopenshift/HcpCluster.Management/hcpCluster-models.tsp +++ b/api/redhatopenshift/HcpCluster.Management/hcpCluster-models.tsp @@ -942,7 +942,7 @@ model UsernameClaimProfile { /** Prefix policy is an optional field that configures how a prefix should be * applied to the value of the JWT claim specified in the 'claim' field. * - * Allowed values are 'Prefix', 'NoPrefix', and omitted (not provided or an empty string). + * Allowed values are 'Prefix', 'NoPrefix', and 'None'. * * When set to 'Prefix', the value specified in the prefix field will be * prepended to the value of the JWT claim. @@ -951,7 +951,7 @@ model UsernameClaimProfile { * When set to 'NoPrefix', no prefix will be prepended to the value * of the JWT claim. * - * When omitted, this means no opinion and the platform is left to choose + * When set to 'None', this means no opinion and the platform is left to choose * any prefixes that are applied which is subject to change over time. * Currently, the platform prepends `{issuerURL}#` to the value of the JWT claim * when the claim is not 'email'. diff --git a/api/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/hcpclusters/preview/2024-06-10-preview/openapi.json b/api/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/hcpclusters/preview/2024-06-10-preview/openapi.json index b96b01dbe6..8389b6fdcc 100644 --- a/api/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/hcpclusters/preview/2024-06-10-preview/openapi.json +++ b/api/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/hcpclusters/preview/2024-06-10-preview/openapi.json @@ -3582,7 +3582,7 @@ }, "prefixPolicy": { "type": "string", - "description": "Prefix policy is an optional field that configures how a prefix should be\napplied to the value of the JWT claim specified in the 'claim' field.\n\nAllowed values are 'Prefix', 'NoPrefix', and omitted (not provided or an empty string).\n\nWhen set to 'Prefix', the value specified in the prefix field will be\nprepended to the value of the JWT claim.\nThe prefix field must be set when prefixPolicy is 'Prefix'.\n\nWhen set to 'NoPrefix', no prefix will be prepended to the value\nof the JWT claim.\n\nWhen omitted, this means no opinion and the platform is left to choose\nany prefixes that are applied which is subject to change over time.\nCurrently, the platform prepends `{issuerURL}#` to the value of the JWT claim\nwhen the claim is not 'email'.\nAs an example, consider the following scenario:\n`prefix` is unset, `issuerURL` is set to `https://myoidc.tld`,\nthe JWT claims include \"username\":\"userA\" and \"email\":\"userA" + "description": "Prefix policy is an optional field that configures how a prefix should be\napplied to the value of the JWT claim specified in the 'claim' field.\n\nAllowed values are 'Prefix', 'NoPrefix', and 'None'.\n\nWhen set to 'Prefix', the value specified in the prefix field will be\nprepended to the value of the JWT claim.\nThe prefix field must be set when prefixPolicy is 'Prefix'.\n\nWhen set to 'NoPrefix', no prefix will be prepended to the value\nof the JWT claim.\n\nWhen set to 'None', this means no opinion and the platform is left to choose\nany prefixes that are applied which is subject to change over time.\nCurrently, the platform prepends `{issuerURL}#` to the value of the JWT claim\nwhen the claim is not 'email'.\nAs an example, consider the following scenario:\n`prefix` is unset, `issuerURL` is set to `https://myoidc.tld`,\nthe JWT claims include \"username\":\"userA\" and \"email\":\"userA" } }, "required": [ @@ -3605,7 +3605,7 @@ }, "prefixPolicy": { "type": "string", - "description": "Prefix policy is an optional field that configures how a prefix should be\napplied to the value of the JWT claim specified in the 'claim' field.\n\nAllowed values are 'Prefix', 'NoPrefix', and omitted (not provided or an empty string).\n\nWhen set to 'Prefix', the value specified in the prefix field will be\nprepended to the value of the JWT claim.\nThe prefix field must be set when prefixPolicy is 'Prefix'.\n\nWhen set to 'NoPrefix', no prefix will be prepended to the value\nof the JWT claim.\n\nWhen omitted, this means no opinion and the platform is left to choose\nany prefixes that are applied which is subject to change over time.\nCurrently, the platform prepends `{issuerURL}#` to the value of the JWT claim\nwhen the claim is not 'email'.\nAs an example, consider the following scenario:\n`prefix` is unset, `issuerURL` is set to `https://myoidc.tld`,\nthe JWT claims include \"username\":\"userA\" and \"email\":\"userA" + "description": "Prefix policy is an optional field that configures how a prefix should be\napplied to the value of the JWT claim specified in the 'claim' field.\n\nAllowed values are 'Prefix', 'NoPrefix', and 'None'.\n\nWhen set to 'Prefix', the value specified in the prefix field will be\nprepended to the value of the JWT claim.\nThe prefix field must be set when prefixPolicy is 'Prefix'.\n\nWhen set to 'NoPrefix', no prefix will be prepended to the value\nof the JWT claim.\n\nWhen set to 'None', this means no opinion and the platform is left to choose\nany prefixes that are applied which is subject to change over time.\nCurrently, the platform prepends `{issuerURL}#` to the value of the JWT claim\nwhen the claim is not 'email'.\nAs an example, consider the following scenario:\n`prefix` is unset, `issuerURL` is set to `https://myoidc.tld`,\nthe JWT claims include \"username\":\"userA\" and \"email\":\"userA" } } }, diff --git a/frontend/pkg/frontend/external_auth_test.go b/frontend/pkg/frontend/external_auth_test.go index d3a6cb65ab..f6cea562a5 100644 --- a/frontend/pkg/frontend/external_auth_test.go +++ b/frontend/pkg/frontend/external_auth_test.go @@ -20,6 +20,7 @@ import ( "encoding/json" "io" "net/http" + "strings" "testing" "time" @@ -33,12 +34,11 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" + "github.com/Azure/ARO-HCP/internal/api/arm" // This will invoke the init() function in each // API version package so it can register itself. - _ "github.com/Azure/ARO-HCP/internal/api/v20240610preview" - "github.com/Azure/ARO-HCP/internal/api" - "github.com/Azure/ARO-HCP/internal/api/arm" + _ "github.com/Azure/ARO-HCP/internal/api/v20240610preview" "github.com/Azure/ARO-HCP/internal/api/v20240610preview/generated" "github.com/Azure/ARO-HCP/internal/database" "github.com/Azure/ARO-HCP/internal/mocks" @@ -47,8 +47,23 @@ import ( var dummyExternalAuthHREF = ocm.GenerateExternalAuthHREF(dummyClusterHREF, api.TestExternalAuthName) -var dummyURL = "Spain" -var dummyAudiences = []string{"audience1"} +var dummyURL = "https://redhat.com" +var dummyCA = `-----BEGIN CERTIFICATE----- +MIICMzCCAZygAwIBAgIJALiPnVsvq8dsMA0GCSqGSIb3DQEBBQUAMFMxCzAJBgNV +BAYTAlVTMQwwCgYDVQQIEwNmb28xDDAKBgNVBAcTA2ZvbzEMMAoGA1UEChMDZm9v +MQwwCgYDVQQLEwNmb28xDDAKBgNVBAMTA2ZvbzAeFw0xMzAzMTkxNTQwMTlaFw0x +ODAzMTgxNTQwMTlaMFMxCzAJBgNVBAYTAlVTMQwwCgYDVQQIEwNmb28xDDAKBgNV +BAcTA2ZvbzEMMAoGA1UEChMDZm9vMQwwCgYDVQQLEwNmb28xDDAKBgNVBAMTA2Zv +bzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAzdGfxi9CNbMf1UUcvDQh7MYB +OveIHyc0E0KIbhjK5FkCBU4CiZrbfHagaW7ZEcN0tt3EvpbOMxxc/ZQU2WN/s/wP +xph0pSfsfFsTKM4RhTWD2v4fgk+xZiKd1p0+L4hTtpwnEw0uXRVd0ki6muwV5y/P ++5FHUeldq+pgTcgzuK8CAwEAAaMPMA0wCwYDVR0PBAQDAgLkMA0GCSqGSIb3DQEB +BQUAA4GBAJiDAAtY0mQQeuxWdzLRzXmjvdSuL9GoyT3BF/jSnpxz5/58dba8pWen +v3pj4P3w5DoOso0rzkZy2jEsEitlVM2mLSbQpMM+MUVQCQoiG6W9xuCFuxSrwPIS +pAqEAuV4DNoxQKKWmhVv+J0ptMWD25Pnpxeq5sXzghfJnslJlQND +-----END CERTIFICATE----- +` +var dummyAudiences = []string{"audience1", "audience2"} var dummyClaim = "4.18.0" func TestCreateExternalAuth(t *testing.T) { @@ -64,6 +79,7 @@ func TestCreateExternalAuth(t *testing.T) { Properties: &generated.ExternalAuthProperties{ Issuer: &generated.TokenIssuerProfile{ URL: &dummyURL, + Ca: &dummyCA, Audiences: api.StringSliceToStringPtrSlice(dummyAudiences), }, Claim: &generated.ExternalAuthClaimProfile{ @@ -75,15 +91,32 @@ func TestCreateExternalAuth(t *testing.T) { }, }, } + expectedCSExternalAuth, _ := arohcpv1alpha1.NewExternalAuth(). + ID(strings.ToLower(api.TestExternalAuthName)). + Issuer(arohcpv1alpha1.NewTokenIssuer(). + URL(dummyURL). + CA(dummyCA). + Audiences(dummyAudiences...), + ). + Claim(arohcpv1alpha1.NewExternalAuthClaim(). + Mappings(arohcpv1alpha1.NewTokenClaimMappings(). + UserName(arohcpv1alpha1.NewUsernameClaim(). + Claim(dummyClaim). + Prefix(""). + PrefixPolicy(""), + ), + ), + ).Build() tests := []struct { - name string - urlPath string - subscription *arm.Subscription - systemData *arm.SystemData - subDoc *arm.Subscription - clusterDoc *database.ResourceDocument - externalAuthDoc *database.ResourceDocument - expectedStatusCode int + name string + urlPath string + subscription *arm.Subscription + systemData *arm.SystemData + subDoc *arm.Subscription + clusterDoc *database.ResourceDocument + externalAuthDoc *database.ResourceDocument + expectedCSExternalAuth *arohcpv1alpha1.ExternalAuth + expectedStatusCode int }{ { name: "PUT External Auth - Create a new External Auth", @@ -93,9 +126,10 @@ func TestCreateExternalAuth(t *testing.T) { RegistrationDate: api.Ptr(time.Now().String()), Properties: nil, }, - clusterDoc: clusterDoc, - externalAuthDoc: externalAuthDoc, - expectedStatusCode: http.StatusCreated, + clusterDoc: clusterDoc, + externalAuthDoc: externalAuthDoc, + expectedCSExternalAuth: expectedCSExternalAuth, + expectedStatusCode: http.StatusCreated, }, } @@ -130,7 +164,7 @@ func TestCreateExternalAuth(t *testing.T) { // CreateOrUpdateExternalAuth mockCSClient.EXPECT(). - PostExternalAuth(gomock.Any(), clusterDoc.InternalID, gomock.Any()). + PostExternalAuth(gomock.Any(), clusterDoc.InternalID, test.expectedCSExternalAuth). DoAndReturn( func(ctx context.Context, clusterInternalID ocm.InternalID, externalAuth *arohcpv1alpha1.ExternalAuth) (*arohcpv1alpha1.ExternalAuth, error) { builder := arohcpv1alpha1.NewExternalAuth(). diff --git a/frontend/pkg/frontend/node_pool_test.go b/frontend/pkg/frontend/node_pool_test.go index cf79106f00..c29374f93b 100644 --- a/frontend/pkg/frontend/node_pool_test.go +++ b/frontend/pkg/frontend/node_pool_test.go @@ -20,6 +20,7 @@ import ( "encoding/json" "io" "net/http" + "strings" "testing" "time" @@ -73,6 +74,29 @@ func TestCreateNodePool(t *testing.T) { }, }, } + + expectedCSNodePool, _ := arohcpv1alpha1.NewNodePool(). + ID(strings.ToLower(api.TestNodePoolName)). + AvailabilityZone(""). + AzureNodePool(arohcpv1alpha1.NewAzureNodePool(). + ResourceName(strings.ToLower(api.TestNodePoolName)). + VMSize(dummyVMSize). + EncryptionAtHost( + arohcpv1alpha1.NewAzureNodePoolEncryptionAtHost(). + State(azureNodePoolEncryptionAtHostDisabled), + ). + OSDiskSizeGibibytes(64). + OSDiskStorageAccountType("Premium_LRS"), + ). + Labels(make(map[string]string)). + Subnet(""). + Version(arohcpv1alpha1.NewVersion(). + ID("openshift-v" + dummyVersionID). + ChannelGroup("stable"), + ). + Replicas(0). + AutoRepair(true).Build() + tests := []struct { name string urlPath string @@ -81,6 +105,7 @@ func TestCreateNodePool(t *testing.T) { subDoc *arm.Subscription clusterDoc *database.ResourceDocument nodePoolDoc *database.ResourceDocument + expectedCSNodePool *arohcpv1alpha1.NodePool expectedStatusCode int }{ { @@ -93,6 +118,7 @@ func TestCreateNodePool(t *testing.T) { }, clusterDoc: clusterDoc, nodePoolDoc: nodePoolDoc, + expectedCSNodePool: expectedCSNodePool, expectedStatusCode: http.StatusCreated, }, } @@ -134,7 +160,7 @@ func TestCreateNodePool(t *testing.T) { Build()) // CreateOrUpdateNodePool mockCSClient.EXPECT(). - PostNodePool(gomock.Any(), clusterDoc.InternalID, gomock.Any()). + PostNodePool(gomock.Any(), clusterDoc.InternalID, expectedCSNodePool). DoAndReturn( func(ctx context.Context, clusterInternalID ocm.InternalID, nodePool *arohcpv1alpha1.NodePool) (*arohcpv1alpha1.NodePool, error) { builder := arohcpv1alpha1.NewNodePool(). diff --git a/frontend/pkg/frontend/ocm.go b/frontend/pkg/frontend/ocm.go index 01913df95a..86712e22bd 100644 --- a/frontend/pkg/frontend/ocm.go +++ b/frontend/pkg/frontend/ocm.go @@ -47,6 +47,8 @@ const ( csImageRegistryStateDisabled string = "disabled" csImageRegistryStateEnabled string = "enabled" csPlatformOutboundType string = "load_balancer" + csUsernameClaimPrefixPolicyPrefix string = "Prefix" + csUsernameClaimPrefixPolicyNoPrefix string = "NoPrefix" ) func convertListeningToVisibility(listening arohcpv1alpha1.ListeningMethod) (visibility api.Visibility) { @@ -85,6 +87,30 @@ func convertOutboundTypeRPToCS(outboundTypeRP api.OutboundType) (outboundTypeCS return } +func convertUsernameClaimPrefixPolicyCSToRP(prefixPolicyCS string) (prefixPolicyRP api.UsernameClaimPrefixPolicyType) { + switch prefixPolicyCS { + case csUsernameClaimPrefixPolicyPrefix: + prefixPolicyRP = api.UsernameClaimPrefixPolicyTypePrefix + case csUsernameClaimPrefixPolicyNoPrefix: + prefixPolicyRP = api.UsernameClaimPrefixPolicyTypeNoPrefix + case "": + prefixPolicyRP = api.UsernameClaimPrefixPolicyTypeNone + } + return +} + +func convertUsernameClaimPrefixPolicyRPToCS(prefixPolicyRP api.UsernameClaimPrefixPolicyType) (prefixPolicyCS string) { + switch prefixPolicyRP { + case api.UsernameClaimPrefixPolicyTypePrefix: + prefixPolicyCS = csUsernameClaimPrefixPolicyPrefix + case api.UsernameClaimPrefixPolicyTypeNoPrefix: + prefixPolicyCS = csUsernameClaimPrefixPolicyNoPrefix + case api.UsernameClaimPrefixPolicyTypeNone: + prefixPolicyCS = "" + } + return +} + func convertEnableEncryptionAtHostToCSBuilder(in api.NodePoolPlatformProfile) *arohcpv1alpha1.AzureNodePoolEncryptionAtHostBuilder { var state string @@ -107,6 +133,7 @@ func convertClusterImageRegistryToCSBuilder(in api.ClusterImageRegistryProfile) } return arohcpv1alpha1.NewClusterImageRegistry().State(state) } + func convertClusterImageRegistryStateCSToRP(state string) api.ClusterImageRegistryProfileState { var registryState api.ClusterImageRegistryProfileState switch state { @@ -517,11 +544,16 @@ func (f *Frontend) BuildCSNodePool(ctx context.Context, nodePool *api.HCPOpenShi npBuilder.Replicas(int(nodePool.Properties.Replicas)) } - for _, t := range nodePool.Properties.Taints { - npBuilder = npBuilder.Taints(arohcpv1alpha1.NewTaint(). - Effect(string(t.Effect)). - Key(t.Key). - Value(t.Value)) + if len(nodePool.Properties.Taints) > 0 { + taintBuilders := []*arohcpv1alpha1.TaintBuilder{} + for _, t := range nodePool.Properties.Taints { + newTaintBuilder := arohcpv1alpha1.NewTaint(). + Effect(string(t.Effect)). + Key(t.Key). + Value(t.Value) + taintBuilders = append(taintBuilders, newTaintBuilder) + } + npBuilder = npBuilder.Taints(taintBuilders...) } if nodePool.Properties.NodeDrainTimeoutMinutes != nil { @@ -548,7 +580,7 @@ func ConvertCStoExternalAuth(resourceID *azcorearm.ResourceID, csExternalAuth *a // Condition: api.ExternalAuthCondition{}, Issuer: api.TokenIssuerProfile{ Url: csExternalAuth.Issuer().URL(), - Ca: csExternalAuth.Issuer().CA(), + Ca: api.PtrOrNil(csExternalAuth.Issuer().CA()), Audiences: csExternalAuth.Issuer().Audiences(), }, Claim: api.ExternalAuthClaimProfile{ @@ -556,7 +588,7 @@ func ConvertCStoExternalAuth(resourceID *azcorearm.ResourceID, csExternalAuth *a Username: api.UsernameClaimProfile{ Claim: csExternalAuth.Claim().Mappings().UserName().Claim(), Prefix: csExternalAuth.Claim().Mappings().UserName().Prefix(), - PrefixPolicy: csExternalAuth.Claim().Mappings().UserName().PrefixPolicy(), + PrefixPolicy: convertUsernameClaimPrefixPolicyCSToRP(csExternalAuth.Claim().Mappings().UserName().PrefixPolicy()), }, }, }, @@ -612,61 +644,67 @@ func (f *Frontend) BuildCSExternalAuth(ctx context.Context, externalAuth *api.HC externalAuthBuilder = externalAuthBuilder.ID(externalAuth.Name) } - tokenClaimValidationRuleBuilder := arohcpv1alpha1.NewTokenClaimValidationRule() - - if externalAuth.Properties.Claim.ValidationRules != nil { - for _, t := range externalAuth.Properties.Claim.ValidationRules { - tokenClaimValidationRuleBuilder = tokenClaimValidationRuleBuilder. - Claim(t.RequiredClaim.Claim). - RequiredValue(t.RequiredClaim.RequiredValue) + issuerBuilder := arohcpv1alpha1.NewTokenIssuer() + issuerBuilder.URL(externalAuth.Properties.Issuer.Url) + if externalAuth.Properties.Issuer.Ca != nil { + issuerBuilder.CA(*externalAuth.Properties.Issuer.Ca) + } + if len(externalAuth.Properties.Issuer.Audiences) > 0 { + issuerBuilder.Audiences(externalAuth.Properties.Issuer.Audiences...) + } + externalAuthBuilder.Issuer(issuerBuilder) + + if len(externalAuth.Properties.Clients) > 0 { + clientConfigs := []*arohcpv1alpha1.ExternalAuthClientConfigBuilder{} + for _, t := range externalAuth.Properties.Clients { + newClientConfig := arohcpv1alpha1.NewExternalAuthClientConfig(). + ID(t.ClientId). + Component(arohcpv1alpha1.NewClientComponent(). + Name(t.Component.Name). + Namespace(t.Component.AuthClientNamespace), + ). + ExtraScopes(t.ExtraScopes...). + Type(arohcpv1alpha1.ExternalAuthClientType(t.ExternalAuthClientProfileType)) + clientConfigs = append(clientConfigs, newClientConfig) } + externalAuthBuilder = externalAuthBuilder.Clients(clientConfigs...) } - externalAuthBuilder. - Issuer(arohcpv1alpha1.NewTokenIssuer(). - URL(externalAuth.Properties.Issuer.Url). - Audiences(externalAuth.Properties.Issuer.Audiences...). - CA(externalAuth.Properties.Issuer.Ca)). - Claim(arohcpv1alpha1.NewExternalAuthClaim(). - Mappings(arohcpv1alpha1.NewTokenClaimMappings(). - UserName(arohcpv1alpha1.NewUsernameClaim(). - Claim(externalAuth.Properties.Claim.Mappings.Username.Claim). - Prefix(externalAuth.Properties.Claim.Mappings.Username.Prefix). - PrefixPolicy(externalAuth.Properties.Claim.Mappings.Username.PrefixPolicy), - ), - ), - ) + buildClaims(externalAuthBuilder, *externalAuth) - if externalAuth.Properties.Claim.Mappings.Groups != nil { - externalAuthBuilder.Claim(arohcpv1alpha1.NewExternalAuthClaim(). - Mappings(arohcpv1alpha1.NewTokenClaimMappings(). - Groups(arohcpv1alpha1.NewGroupsClaim(). - Claim(externalAuth.Properties.Claim.Mappings.Groups.Claim). - Prefix(externalAuth.Properties.Claim.Mappings.Groups.Prefix), - ), - ), - ) - } + return externalAuthBuilder.Build() +} - if len(externalAuth.Properties.Claim.ValidationRules) > 0 { - externalAuthBuilder.Claim(arohcpv1alpha1.NewExternalAuthClaim(). - ValidationRules(tokenClaimValidationRuleBuilder), - ) +func buildClaims(externalAuthBuilder *arohcpv1alpha1.ExternalAuthBuilder, hcpExternalAuth api.HCPOpenShiftClusterExternalAuth) { + claimBuilder := arohcpv1alpha1.NewExternalAuthClaim() + mappingsBuilder := arohcpv1alpha1.NewTokenClaimMappings() + mappingsBuilder.UserName(arohcpv1alpha1.NewUsernameClaim(). + Claim(hcpExternalAuth.Properties.Claim.Mappings.Username.Claim). + Prefix(hcpExternalAuth.Properties.Claim.Mappings.Username.Prefix). + PrefixPolicy(convertUsernameClaimPrefixPolicyRPToCS(hcpExternalAuth.Properties.Claim.Mappings.Username.PrefixPolicy)), + ) + + if hcpExternalAuth.Properties.Claim.Mappings.Groups != nil { + mappingsBuilder.Groups(arohcpv1alpha1.NewGroupsClaim(). + Claim(hcpExternalAuth.Properties.Claim.Mappings.Groups.Claim). + Prefix(hcpExternalAuth.Properties.Claim.Mappings.Groups.Prefix), + ) } + claimBuilder.Mappings(mappingsBuilder) - for _, t := range externalAuth.Properties.Clients { - externalAuthBuilder = externalAuthBuilder.Clients(arohcpv1alpha1.NewExternalAuthClientConfig(). - ID(t.ClientId). - Component(arohcpv1alpha1.NewClientComponent(). - Name(t.Component.Name). - Namespace(t.Component.AuthClientNamespace), - ). - ExtraScopes(t.ExtraScopes...). - Type(arohcpv1alpha1.ExternalAuthClientType(t.ExternalAuthClientProfileType))) + if len(hcpExternalAuth.Properties.Claim.ValidationRules) > 0 { + validationRules := []*arohcpv1alpha1.TokenClaimValidationRuleBuilder{} + for _, t := range hcpExternalAuth.Properties.Claim.ValidationRules { + newClientConfig := arohcpv1alpha1.NewTokenClaimValidationRule(). + Claim(t.RequiredClaim.Claim). + RequiredValue(t.RequiredClaim.RequiredValue) + validationRules = append(validationRules, newClientConfig) + } + claimBuilder.ValidationRules(validationRules...) } - return externalAuthBuilder.Build() + externalAuthBuilder.Claim(claimBuilder) } // ConvertCStoAdminCredential converts a CS BreakGlassCredential object into an HCPOpenShiftClusterAdminCredential. diff --git a/frontend/pkg/frontend/ocm_test.go b/frontend/pkg/frontend/ocm_test.go index ec7f651f2d..79b960eafe 100644 --- a/frontend/pkg/frontend/ocm_test.go +++ b/frontend/pkg/frontend/ocm_test.go @@ -236,7 +236,7 @@ func withOCMClusterDefaults() func(*arohcpv1alpha1.ClusterBuilder) *arohcpv1alph return func(b *arohcpv1alpha1.ClusterBuilder) *arohcpv1alpha1.ClusterBuilder { // This reflects how the immutable attributes get set when passed an empty[*] RP // cluster. (well, not exactly empty, need to set Platform.ManagedResourceGroupName - // so that we don't get a corresdponding random value in the output.) + // so that we don't get a corresponding random value in the output.) return b. API(arohcpv1alpha1.NewClusterAPI().Listening("")). Azure(arohcpv1alpha1.NewAzure(). @@ -274,3 +274,260 @@ func withOCMClusterDefaults() func(*arohcpv1alpha1.ClusterBuilder) *arohcpv1alph State("")) } } + +func getHCPNodePoolResource(opts ...func(*api.HCPOpenShiftClusterNodePool)) *api.HCPOpenShiftClusterNodePool { + nodePool := &api.HCPOpenShiftClusterNodePool{ + Properties: api.HCPOpenShiftClusterNodePoolProperties{}, + } + + for _, opt := range opts { + opt(nodePool) + } + return nodePool +} + +// Because we don't distinguish between unset and empty values in our JSON parsing +// we will get the resulting CS object from an empty HCPOpenShiftClusterNodePool object. +func getBaseCSNodePoolBuilder() *arohcpv1alpha1.NodePoolBuilder { + return arohcpv1alpha1.NewNodePool(). + ID(""). + AvailabilityZone(""). + AzureNodePool(arohcpv1alpha1.NewAzureNodePool(). + ResourceName(""). + VMSize(""). + EncryptionAtHost( + arohcpv1alpha1.NewAzureNodePoolEncryptionAtHost(). + State(azureNodePoolEncryptionAtHostDisabled), + ). + OSDiskSizeGibibytes(0). + OSDiskStorageAccountType(""), + ). + Subnet(""). + Version(arohcpv1alpha1.NewVersion(). + ID("openshift-v"). + ChannelGroup(""), + ). + Replicas(0). + AutoRepair(false) +} +func TestBuildCSNodePool(t *testing.T) { + resourceID := testResourceID(t) + testCases := []struct { + name string + hcpNodePool *api.HCPOpenShiftClusterNodePool + expectedCSNodePool *arohcpv1alpha1.NodePoolBuilder + }{ + { + name: "zero", + hcpNodePool: getHCPNodePoolResource(), + expectedCSNodePool: getBaseCSNodePoolBuilder(), + }, + { + name: "handle multiple taints", + hcpNodePool: getHCPNodePoolResource( + func(hsc *api.HCPOpenShiftClusterNodePool) { + hsc.Properties.Taints = []api.Taint{ + {Effect: "a"}, + {Effect: "b"}, + } + }, + ), + expectedCSNodePool: getBaseCSNodePoolBuilder().Taints( + []*arohcpv1alpha1.TaintBuilder{ + arohcpv1alpha1.NewTaint(). + Effect("a"). + Key(""). + Value(""), + arohcpv1alpha1.NewTaint().Effect("b"). + Key(""). + Value(""), + }...), + }, + } + for _, tc := range testCases { + f := NewTestFrontend(t) + t.Run(tc.name, func(t *testing.T) { + ctx := ContextWithLogger(context.Background(), api.NewTestLogger()) + expected, err := tc.expectedCSNodePool.Build() + require.NoError(t, err) + generatedCSNodePool, _ := f.BuildCSNodePool(ctx, tc.hcpNodePool, false) + assert.Equalf(t, expected, generatedCSNodePool, "BuildCSNodePool(%v, %v)", resourceID, expected) + }) + } +} + +func externalAuthResource(opts ...func(*api.HCPOpenShiftClusterExternalAuth)) *api.HCPOpenShiftClusterExternalAuth { + externalAuth := &api.HCPOpenShiftClusterExternalAuth{ + Properties: api.HCPOpenShiftClusterExternalAuthProperties{}, + } + + for _, opt := range opts { + opt(externalAuth) + } + return externalAuth +} + +// Because we don't distinguish between unset and empty values in our JSON parsing +// we will get the resulting CS object from an empty HCPOpenShiftClusterExternalAuth object. +func getBaseCSExternalAuthBuilder() *arohcpv1alpha1.ExternalAuthBuilder { + return arohcpv1alpha1.NewExternalAuth(). + ID(""). + Issuer(arohcpv1alpha1.NewTokenIssuer(). + URL("")). + Claim(arohcpv1alpha1.NewExternalAuthClaim(). + Mappings(arohcpv1alpha1.NewTokenClaimMappings(). + UserName(arohcpv1alpha1.NewUsernameClaim(). + Claim(""). + Prefix(""). + PrefixPolicy(""), + ), + ), + ) +} + +func TestBuildCSExternalAuth(t *testing.T) { + resourceID := testResourceID(t) + testCases := []struct { + name string + hcpExternalAuth *api.HCPOpenShiftClusterExternalAuth + expectedCSExternalAuth *arohcpv1alpha1.ExternalAuthBuilder + }{ + { + name: "zero", + hcpExternalAuth: externalAuthResource(), + expectedCSExternalAuth: getBaseCSExternalAuthBuilder(), + }, + { + name: "correctly parse PrefixPolicyType", + hcpExternalAuth: externalAuthResource( + func(hsc *api.HCPOpenShiftClusterExternalAuth) { + hsc.Properties.Claim.Mappings.Username.PrefixPolicy = api.UsernameClaimPrefixPolicyTypePrefix + }, + ), + expectedCSExternalAuth: getBaseCSExternalAuthBuilder().Claim(arohcpv1alpha1.NewExternalAuthClaim(). + Mappings(arohcpv1alpha1.NewTokenClaimMappings(). + UserName(arohcpv1alpha1.NewUsernameClaim(). + Claim(""). + Prefix(""). + PrefixPolicy(string(api.UsernameClaimPrefixPolicyTypePrefix)), + ), + )), + }, + { + name: "correctly parse Issuer", + hcpExternalAuth: externalAuthResource( + func(hsc *api.HCPOpenShiftClusterExternalAuth) { + hsc.Properties.Issuer = api.TokenIssuerProfile{ + Ca: &dummyCA, + Url: dummyURL, + Audiences: dummyAudiences, + } + }, + ), + expectedCSExternalAuth: getBaseCSExternalAuthBuilder().Issuer( + arohcpv1alpha1.NewTokenIssuer(). + CA(dummyCA). + URL(dummyURL). + Audiences(dummyAudiences...), + ), + }, + { + name: "correctly parse Claim", + hcpExternalAuth: externalAuthResource( + func(hsc *api.HCPOpenShiftClusterExternalAuth) { + hsc.Properties.Claim = api.ExternalAuthClaimProfile{ + Mappings: api.TokenClaimMappingsProfile{ + Username: api.UsernameClaimProfile{ + Claim: "a", + Prefix: "", + PrefixPolicy: "None", + }, + Groups: &api.GroupClaimProfile{ + Claim: "b", + Prefix: "", + }, + }, + ValidationRules: []api.TokenClaimValidationRule{ + { + TokenClaimValidationRuleType: api.TokenValidationRuleTypeRequiredClaim, + RequiredClaim: api.TokenRequiredClaim{ + Claim: "A", + RequiredValue: "B", + }, + }, + { + TokenClaimValidationRuleType: api.TokenValidationRuleTypeRequiredClaim, + RequiredClaim: api.TokenRequiredClaim{ + Claim: "C", + RequiredValue: "D", + }, + }, + }, + } + }, + ), + expectedCSExternalAuth: getBaseCSExternalAuthBuilder().Claim( + arohcpv1alpha1.NewExternalAuthClaim(). + Mappings(arohcpv1alpha1.NewTokenClaimMappings(). + UserName(arohcpv1alpha1.NewUsernameClaim(). + Claim("a"). + Prefix(""). + PrefixPolicy(""), + ). + Groups(arohcpv1alpha1.NewGroupsClaim(). + Claim("b"). + Prefix(""), + ), + ). + ValidationRules([]*arohcpv1alpha1.TokenClaimValidationRuleBuilder{ + arohcpv1alpha1.NewTokenClaimValidationRule(). + Claim("A"). + RequiredValue("B"), + arohcpv1alpha1.NewTokenClaimValidationRule(). + Claim("C"). + RequiredValue("D"), + }...), + ), + }, + { + name: "handle multiple clients", + hcpExternalAuth: externalAuthResource( + func(hsc *api.HCPOpenShiftClusterExternalAuth) { + hsc.Properties.Clients = []api.ExternalAuthClientProfile{ + {ClientId: "a"}, + {ClientId: "b"}, + } + }, + ), + expectedCSExternalAuth: getBaseCSExternalAuthBuilder().Clients( + []*arohcpv1alpha1.ExternalAuthClientConfigBuilder{ + arohcpv1alpha1.NewExternalAuthClientConfig(). + ID("a"). + Component(arohcpv1alpha1.NewClientComponent(). + Name(""). + Namespace(""), + ). + ExtraScopes(). + Type(""), + arohcpv1alpha1.NewExternalAuthClientConfig(). + ID("b"). + Component(arohcpv1alpha1.NewClientComponent(). + Name(""). + Namespace(""), + ). + ExtraScopes(). + Type(""), + }...), + }, + } + for _, tc := range testCases { + f := NewTestFrontend(t) + t.Run(tc.name, func(t *testing.T) { + ctx := ContextWithLogger(context.Background(), api.NewTestLogger()) + expected, err := tc.expectedCSExternalAuth.Build() + require.NoError(t, err) + generatedCSExternalAuth, _ := f.BuildCSExternalAuth(ctx, tc.hcpExternalAuth, false) + assert.Equalf(t, expected, generatedCSExternalAuth, "BuildCSExternalAuth(%v, %v)", resourceID, expected) + }) + } +} diff --git a/frontend/pkg/frontend/testhelpers.go b/frontend/pkg/frontend/testhelpers.go index 63ffad9915..3f10aa8a0a 100644 --- a/frontend/pkg/frontend/testhelpers.go +++ b/frontend/pkg/frontend/testhelpers.go @@ -17,9 +17,13 @@ package frontend import ( "testing" + "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + "github.com/Azure/ARO-HCP/internal/api" "github.com/Azure/ARO-HCP/internal/audit" + "github.com/Azure/ARO-HCP/internal/mocks" ) // The definitions in this file are meant for unit tests. @@ -29,3 +33,21 @@ func newNoopAuditClient(t *testing.T) *audit.AuditClient { require.NoError(t, err) return c } + +func NewTestFrontend(t *testing.T) *Frontend { + ctrl := gomock.NewController(t) + mockDBClient := mocks.NewMockDBClient(ctrl) + reg := prometheus.NewRegistry() + + f := NewFrontend( + api.NewTestLogger(), + nil, + nil, + reg, + mockDBClient, + "", + nil, + newNoopAuditClient(t), + ) + return f +} diff --git a/internal/api/enums.go b/internal/api/enums.go index f6a78c5952..4288621d70 100644 --- a/internal/api/enums.go +++ b/internal/api/enums.go @@ -122,3 +122,14 @@ const ( // ConditionStatusTypeUnknown - the condition status is unknown. ConditionStatusTypeUnknown ConditionStatusType = "Unknown" ) + +type UsernameClaimPrefixPolicyType string + +const ( + // UsernameClaimPrefixPolicyTypePrefix - prefix the JWT claim with the value of Prefix. + UsernameClaimPrefixPolicyTypePrefix UsernameClaimPrefixPolicyType = "Prefix" + // UsernameClaimPrefixPolicyTypeNoPrefix - do not prefix the JWT claim. + UsernameClaimPrefixPolicyTypeNoPrefix UsernameClaimPrefixPolicyType = "NoPrefix" + // UsernameClaimPrefixPolicyTypeNone - let the platform choose an appropriate prefix. + UsernameClaimPrefixPolicyTypeNone UsernameClaimPrefixPolicyType = "None" +) diff --git a/internal/api/hcpopenshiftclusterexternalauth.go b/internal/api/hcpopenshiftclusterexternalauth.go index 64bb9d62f3..b6d42aa559 100644 --- a/internal/api/hcpopenshiftclusterexternalauth.go +++ b/internal/api/hcpopenshiftclusterexternalauth.go @@ -43,11 +43,11 @@ type HCPOpenShiftClusterExternalAuthProperties struct { /** Condition defines an observation of the external auth state. */ type ExternalAuthCondition struct { - ConditionType ExternalAuthConditionType `json:"type" validate:"enum_externalauthconditiontype"` - Status ConditionStatusType `json:"status" validate:"enum_externalauthconditionstatustype"` - LastTransitionTime time.Time `json:"lastTransitionTime"` - Reason string `json:"reason"` - Message string `json:"message"` + ConditionType ExternalAuthConditionType `json:"type" visibility:"read" validate:"enum_externalauthconditiontype"` + Status ConditionStatusType `json:"status" visibility:"read" validate:"enum_externalauthconditionstatustype"` + LastTransitionTime time.Time `json:"lastTransitionTime" visibility:"read"` + Reason string `json:"reason" visibility:"read"` + Message string `json:"message" visibility:"read"` } /** Token issuer profile @@ -55,22 +55,19 @@ type ExternalAuthCondition struct { * how tokens issued from the identity provider are evaluated by the Kubernetes API server. */ type TokenIssuerProfile struct { - // TODO: validate https url - Url string `json:"url" visibility:"read create update" validate:"required_for_put"` - // TODO: validate at least one of the entries must match the 'aud' claim in the JWT token. - Audiences []string `json:"audiences" visibility:"read create update" validate:"required_for_put,max=10"` - Ca string `json:"ca" visibility:"read create update" validate:"omitempty,pem_certificates"` + Url string `json:"url" visibility:"read create update" validate:"required_for_put,url,startswith=https://"` + Audiences []string `json:"audiences" visibility:"read create update" validate:"required_for_put,min=0,max=10"` + Ca *string `json:"ca" visibility:"read create update" validate:"omitempty,pem_certificates"` } /** External Auth client profile * This configures how on-cluster, platform clients should request tokens from the identity provider. */ type ExternalAuthClientProfile struct { - Component ExternalAuthClientComponentProfile `json:"component" visibility:"read create update" validate:"required_for_put"` - // TODO: The clientId must appear in the audience field of the TokenIssuerProfile. - ClientId string `json:"clientId" visibility:"read create update" validate:"required_for_put"` - ExtraScopes []string `json:"extraScopes" visibility:"read create update" validate:"omitempty"` - ExternalAuthClientProfileType ExternalAuthClientType `json:"enum_externalauthclienttype" visibility:"read create update" validate:"required_for_put"` + Component ExternalAuthClientComponentProfile `json:"component" visibility:"read create update" validate:"required_for_put"` + ClientId string `json:"clientId" visibility:"read create update" validate:"required_for_put"` + ExtraScopes []string `json:"extraScopes" visibility:"read create update" validate:"omitempty"` + ExternalAuthClientProfileType ExternalAuthClientType `json:"type" visibility:"read create update" validate:"required_for_put,enum_externalauthclienttype"` } /** External Auth component profile @@ -113,9 +110,9 @@ type GroupClaimProfile struct { * from the claims in a JWT token issued by the identity provider. */ type UsernameClaimProfile struct { - Claim string `json:"claim" visibility:"read create update" validate:"required_for_put,max=256"` - Prefix string `json:"prefix" visibility:"read create update" validate:"omitempty"` - PrefixPolicy string `json:"prefixPolicy" visibility:"read create update" validate:"omitempty"` + Claim string `json:"claim" visibility:"read create update" validate:"required_for_put,max=256"` + Prefix string `json:"prefix" visibility:"read create update" validate:"omitempty"` + PrefixPolicy UsernameClaimPrefixPolicyType `json:"prefixPolicy" visibility:"read create update" validate:"omitempty,enum_usernameclaimprefixpolicytype"` } /** External Auth claim validation rule */ @@ -131,9 +128,17 @@ type TokenRequiredClaim struct { } func NewDefaultHCPOpenShiftClusterExternalAuth() *HCPOpenShiftClusterExternalAuth { - // Currently the only defaults in External Auth is for TokenValidationRuleType but as - // there are no TokenValidationRules by default the object is just empty. - return &HCPOpenShiftClusterExternalAuth{} + return &HCPOpenShiftClusterExternalAuth{ + Properties: HCPOpenShiftClusterExternalAuthProperties{ + Claim: ExternalAuthClaimProfile{ + Mappings: TokenClaimMappingsProfile{ + Username: UsernameClaimProfile{ + PrefixPolicy: UsernameClaimPrefixPolicyTypeNone, + }, + }, + }, + }, + } } // This combination is used later in the system as a unique identifier and as @@ -175,6 +180,60 @@ func (c ExternalAuthClientProfile) generateUniqueIdentifier() string { return c.Component.Name + c.Component.AuthClientNamespace } +// validateClientIdInAudiences checks that each ClientId matches an audience in the TokenIssuerProfile. +func (externalAuth *HCPOpenShiftClusterExternalAuth) validateClientIdInAudiences() []arm.CloudErrorBody { + var errorDetails []arm.CloudErrorBody + + if len(externalAuth.Properties.Clients) > 0 { + audiencesSet := make(map[string]struct{}, len(externalAuth.Properties.Issuer.Audiences)) + for _, aud := range externalAuth.Properties.Issuer.Audiences { + audiencesSet[aud] = struct{}{} + } + + for i, client := range externalAuth.Properties.Clients { + if _, found := audiencesSet[client.ClientId]; !found { + errorDetails = append(errorDetails, arm.CloudErrorBody{ + Code: arm.CloudErrorCodeInvalidRequestContent, + Message: fmt.Sprintf("ClientId '%s' in clients[%d] must match an audience in TokenIssuerProfile", client.ClientId, i), + Target: "properties.clients", + }) + } + } + } + + return errorDetails +} + +// validateUsernamePrefixPolicy checks that a usernameClaimProfile obeys it's own type +func (externalAuth *HCPOpenShiftClusterExternalAuth) validateUsernamePrefixPolicy() []arm.CloudErrorBody { + var errorDetails []arm.CloudErrorBody + + switch externalAuth.Properties.Claim.Mappings.Username.PrefixPolicy { + case UsernameClaimPrefixPolicyTypePrefix: + if len(externalAuth.Properties.Claim.Mappings.Username.Prefix) == 0 { + errorDetails = append(errorDetails, arm.CloudErrorBody{ + Code: arm.CloudErrorCodeInvalidRequestContent, + Message: "UsernameClaimProfile has a PrefixPolicy of 'Prefix' but Username.Prefix is unset", + Target: "properties.claim.mappings.username.prefix", + }) + } + case UsernameClaimPrefixPolicyTypeNoPrefix: + if len(externalAuth.Properties.Claim.Mappings.Username.Prefix) > 0 { + errorDetails = append(errorDetails, arm.CloudErrorBody{ + Code: arm.CloudErrorCodeInvalidRequestContent, + Message: fmt.Sprintf( + "UsernameClaimProfile has a PrefixPolicy of 'NoPrefix' but Username.Prefix is set to %s", + externalAuth.Properties.Claim.Mappings.Username.Prefix, + ), + Target: "properties.claim.mappings.username.prefix", + }) + } + case UsernameClaimPrefixPolicyTypeNone: + } + + return errorDetails +} + func (externalAuth *HCPOpenShiftClusterExternalAuth) Validate(validate *validator.Validate, request *http.Request) []arm.CloudErrorBody { errorDetails := ValidateRequest(validate, request, externalAuth) @@ -184,6 +243,8 @@ func (externalAuth *HCPOpenShiftClusterExternalAuth) Validate(validate *validato // becoming overwhelming. if len(errorDetails) == 0 { errorDetails = append(errorDetails, externalAuth.validateUniqueClientIdentifiers()...) + errorDetails = append(errorDetails, externalAuth.validateClientIdInAudiences()...) + errorDetails = append(errorDetails, externalAuth.validateUsernamePrefixPolicy()...) } return errorDetails diff --git a/internal/api/hcpopenshiftclusterexternalauth_test.go b/internal/api/hcpopenshiftclusterexternalauth_test.go index 676b25eaee..f707cb491c 100644 --- a/internal/api/hcpopenshiftclusterexternalauth_test.go +++ b/internal/api/hcpopenshiftclusterexternalauth_test.go @@ -46,8 +46,12 @@ func TestExternalAuthRequiredForPut(t *testing.T) { resource: NewDefaultHCPOpenShiftClusterExternalAuth(), expectErrors: []arm.CloudErrorBody{ { - Message: "Missing required field 'properties'", - Target: "properties", + Message: "Missing required field 'claim'", + Target: "properties.claim.mappings.username.claim", + }, + { + Message: "Missing required field 'issuer'", + Target: "properties.issuer", }, }, }, @@ -135,15 +139,12 @@ func TestExternalAuthValidate(t *testing.T) { }, }, }, - // //-------------------------------- - // // Complex field validation - // //-------------------------------- { name: "Bad properties.issuer.ca", tweaks: &HCPOpenShiftClusterExternalAuth{ Properties: HCPOpenShiftClusterExternalAuthProperties{ Issuer: TokenIssuerProfile{ - Ca: "NOT A PEM DOC", + Ca: Ptr("NOT A PEM DOC"), }, }, }, @@ -154,10 +155,120 @@ func TestExternalAuthValidate(t *testing.T) { }, }, }, + { + name: "Bad properties.issuer.url - InvalidURL", + tweaks: &HCPOpenShiftClusterExternalAuth{ + Properties: HCPOpenShiftClusterExternalAuthProperties{ + Issuer: TokenIssuerProfile{ + Url: "aaa", + }, + }, + }, + expectErrors: []arm.CloudErrorBody{ + { + Message: "Invalid value 'aaa' for field 'url' (must be a URL)", + Target: "properties.issuer.url", + }, + }, + }, + { + name: "Bad properties.issuer.url - Not starting with https://", + tweaks: &HCPOpenShiftClusterExternalAuth{ + Properties: HCPOpenShiftClusterExternalAuthProperties{ + Issuer: TokenIssuerProfile{ + Url: "http://microsoft.com", + }, + }, + }, + expectErrors: []arm.CloudErrorBody{ + { + Message: "Invalid value 'http://microsoft.com' for field 'url' (must start with 'https://')", + Target: "properties.issuer.url", + }, + }, + }, + { + name: "Bad properties.issuer.audiences", + tweaks: &HCPOpenShiftClusterExternalAuth{ + Properties: HCPOpenShiftClusterExternalAuthProperties{ + Issuer: TokenIssuerProfile{ + Audiences: []string{"omitempty"}, + }, + }, + }, + expectErrors: nil, + }, + //-------------------------------- + // Complex field validation + //-------------------------------- + { + name: "Valid ClientId in audiences", + tweaks: &HCPOpenShiftClusterExternalAuth{ + Properties: HCPOpenShiftClusterExternalAuthProperties{ + Issuer: TokenIssuerProfile{ + Url: "https://example.com", + Audiences: []string{ClientId1}, + }, + Clients: []ExternalAuthClientProfile{ + { + ClientId: ClientId1, + Component: ExternalAuthClientComponentProfile{ + Name: ClientComponentName, + AuthClientNamespace: ClientComponentNamespace, + }, + ExternalAuthClientProfileType: "confidential", + }, + }, + Claim: ExternalAuthClaimProfile{ + Mappings: TokenClaimMappingsProfile{ + Username: UsernameClaimProfile{Claim: "email"}, + }, + }, + }, + }, + expectErrors: nil, + }, + { + name: "Invalid ClientId not in audiences", + tweaks: &HCPOpenShiftClusterExternalAuth{ + Properties: HCPOpenShiftClusterExternalAuthProperties{ + Issuer: TokenIssuerProfile{ + Url: "https://example.com", + Audiences: []string{}, + }, + Clients: []ExternalAuthClientProfile{ + { + ClientId: ClientId1, + Component: ExternalAuthClientComponentProfile{ + Name: ClientComponentName, + AuthClientNamespace: ClientComponentNamespace, + }, + ExternalAuthClientProfileType: "confidential", + }, + }, + Claim: ExternalAuthClaimProfile{ + Mappings: TokenClaimMappingsProfile{ + Username: UsernameClaimProfile{Claim: "email"}, + }, + }, + }, + }, + expectErrors: []arm.CloudErrorBody{ + { + Code: "InvalidRequestContent", + Message: fmt.Sprintf("ClientId '%s' in clients[0] must match an audience in TokenIssuerProfile", ClientId1), + Target: "properties.clients", + }, + }, + }, { name: "External Auth with multiple clients that have the same Name/Namespace pair", tweaks: &HCPOpenShiftClusterExternalAuth{ Properties: HCPOpenShiftClusterExternalAuthProperties{ + Issuer: TokenIssuerProfile{ + Url: "https://example.com", + Audiences: []string{ClientId1, ClientId2}, + }, Clients: []ExternalAuthClientProfile{ { ClientId: ClientId1, @@ -165,6 +276,7 @@ func TestExternalAuthValidate(t *testing.T) { Name: ClientComponentName, AuthClientNamespace: ClientComponentNamespace, }, + ExternalAuthClientProfileType: "confidential", }, { ClientId: ClientId2, @@ -172,6 +284,12 @@ func TestExternalAuthValidate(t *testing.T) { Name: ClientComponentName, AuthClientNamespace: ClientComponentNamespace, }, + ExternalAuthClientProfileType: "confidential", + }, + }, + Claim: ExternalAuthClaimProfile{ + Mappings: TokenClaimMappingsProfile{ + Username: UsernameClaimProfile{Claim: "email"}, }, }, }, @@ -181,11 +299,35 @@ func TestExternalAuthValidate(t *testing.T) { Message: fmt.Sprintf( "External Auth Clients must have a unique combination of component.Name & component.AuthClientNamespace. "+ "The following clientIds share the same unique combination '%s%s' and are invalid: \n '[%s %s]' ", - ClientComponentName, ClientComponentNamespace, ClientId1, ClientId2), + ClientComponentName, ClientComponentNamespace, ClientId1, ClientId2, + ), Target: "properties.clients", }, }, }, + { + name: "Invalid UsernamePrefixPolicy - A Policy of Prefix but none is set", + tweaks: &HCPOpenShiftClusterExternalAuth{ + Properties: HCPOpenShiftClusterExternalAuthProperties{ + Claim: ExternalAuthClaimProfile{ + Mappings: TokenClaimMappingsProfile{ + Username: UsernameClaimProfile{ + Claim: "email", + Prefix: "", + PrefixPolicy: "Prefix", + }, + }, + }, + }, + }, + expectErrors: []arm.CloudErrorBody{ + { + Code: "InvalidRequestContent", + Message: "UsernameClaimProfile has a PrefixPolicy of 'Prefix' but Username.Prefix is unset", + Target: "properties.claim.mappings.username.prefix", + }, + }, + }, } validate := NewTestValidator() diff --git a/internal/api/testhelpers.go b/internal/api/testhelpers.go index bccfac012d..6f953a3d11 100644 --- a/internal/api/testhelpers.go +++ b/internal/api/testhelpers.go @@ -95,6 +95,11 @@ func NewTestValidator() *validator.Validate { ExternalAuthClientTypeConfidential, ExternalAuthClientTypePublic, )) + validate.RegisterAlias("enum_usernameclaimprefixpolicytype", EnumValidateTag( + UsernameClaimPrefixPolicyTypePrefix, + UsernameClaimPrefixPolicyTypeNoPrefix, + UsernameClaimPrefixPolicyTypeNone, + )) validate.RegisterAlias("enum_tokenvalidationruletyperequiredclaim", EnumValidateTag( TokenValidationRuleTypeRequiredClaim, )) @@ -144,9 +149,10 @@ func NodePoolTestCase(t *testing.T, tweaks *HCPOpenShiftClusterNodePool) *HCPOpe } func MinimumValidExternalAuthTestCase() *HCPOpenShiftClusterExternalAuth { + dummyURL := "https://www.redhat.com" resource := NewDefaultHCPOpenShiftClusterExternalAuth() resource.Properties.Issuer = TokenIssuerProfile{ - Url: "https://www.redhat.com", + Url: dummyURL, Audiences: []string{"audience1"}, } resource.Properties.Claim.Mappings = TokenClaimMappingsProfile{ diff --git a/internal/api/v20240610preview/external_auth_methods.go b/internal/api/v20240610preview/external_auth_methods.go index f954b3564f..67d59352d9 100644 --- a/internal/api/v20240610preview/external_auth_methods.go +++ b/internal/api/v20240610preview/external_auth_methods.go @@ -83,12 +83,12 @@ func (c *ExternalAuth) ValidateStatic(current api.VersionedHCPOpenShiftClusterEx var normalized api.HCPOpenShiftClusterExternalAuth var errorDetails []arm.CloudErrorBody - // Pass the embedded HcpOpenShiftCluster struct so the - // struct field names match the clusterStructTagMap keys. + // Pass the embedded ExternalAuth struct so the + // struct field names match the externalAuthStructTagMap keys. errorDetails = api.ValidateVisibility( c.ExternalAuth, current.(*ExternalAuth).ExternalAuth, - clusterStructTagMap, updating) + externalAuthStructTagMap, updating) c.Normalize(&normalized) @@ -129,7 +129,7 @@ func normalizeTokenIssuerProfile(p *generated.TokenIssuerProfile, out *api.Token } } if p.Ca != nil { - out.Ca = *p.Ca + out.Ca = p.Ca } } @@ -154,7 +154,7 @@ func normalizeTokenClaimMappingsProfile(p *generated.TokenClaimMappingsProfile, out.Username.Prefix = *p.Username.Prefix } if p.Username.PrefixPolicy != nil { - out.Username.PrefixPolicy = *p.Username.PrefixPolicy + out.Username.PrefixPolicy = api.UsernameClaimPrefixPolicyType(*p.Username.PrefixPolicy) } } if p.Groups != nil { @@ -199,7 +199,7 @@ func newTokenIssuerProfile(from *api.TokenIssuerProfile) *generated.TokenIssuerP return &generated.TokenIssuerProfile{ URL: api.PtrOrNil(from.Url), Audiences: api.StringSliceToStringPtrSlice(from.Audiences), - Ca: api.PtrOrNil(from.Ca), + Ca: from.Ca, } } @@ -218,7 +218,7 @@ func newExternalAuthClaimProfile(from *api.ExternalAuthClaimProfile) *generated. Username: &generated.UsernameClaimProfile{ Claim: api.PtrOrNil(from.Mappings.Username.Claim), Prefix: api.PtrOrNil(from.Mappings.Username.Prefix), - PrefixPolicy: api.PtrOrNil(from.Mappings.Username.PrefixPolicy), + PrefixPolicy: api.PtrOrNil(string(from.Mappings.Username.PrefixPolicy)), }, Groups: groups, }, @@ -238,11 +238,11 @@ func (v version) NewHCPOpenShiftClusterExternalAuth(from *api.HCPOpenShiftCluste Properties: &generated.ExternalAuthProperties{ ProvisioningState: api.PtrOrNil(generated.ExternalAuthProvisioningState(from.Properties.ProvisioningState)), Condition: newExternalAuthCondition(&from.Properties.Condition), - Issuer: newTokenIssuerProfile(&from.Properties.Issuer), Claim: newExternalAuthClaimProfile(&from.Properties.Claim), }, }, } + out.Properties.Issuer = newTokenIssuerProfile(&from.Properties.Issuer) if from.SystemData != nil { out.SystemData = &generated.SystemData{ @@ -263,7 +263,7 @@ func (v version) NewHCPOpenShiftClusterExternalAuth(from *api.HCPOpenShiftCluste }, ClientID: api.PtrOrNil(client.ClientId), ExtraScopes: api.StringSliceToStringPtrSlice(client.ExtraScopes), - Type: api.PtrOrNil(generated.ExternalAuthClientType(from.Type)), + Type: api.PtrOrNil(generated.ExternalAuthClientType(client.ExternalAuthClientProfileType)), }) } return out diff --git a/internal/api/v20240610preview/generated/client_factory.go b/internal/api/v20240610preview/generated/client_factory.go index 9b02dbf446..e90a2c10c6 100644 --- a/internal/api/v20240610preview/generated/client_factory.go +++ b/internal/api/v20240610preview/generated/client_factory.go @@ -1,4 +1,4 @@ -// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.72) +// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.73) // Changes may cause incorrect behavior and will be lost if the code is regenerated. // Code generated by @autorest/go. DO NOT EDIT. diff --git a/internal/api/v20240610preview/generated/constants.go b/internal/api/v20240610preview/generated/constants.go index 49f8aab1d7..7afc8ded15 100644 --- a/internal/api/v20240610preview/generated/constants.go +++ b/internal/api/v20240610preview/generated/constants.go @@ -1,14 +1,9 @@ -// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.72) +// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.73) // Changes may cause incorrect behavior and will be lost if the code is regenerated. // Code generated by @autorest/go. DO NOT EDIT. package generated -const ( - moduleName = "does_not_matter" - moduleVersion = "v0.0.1" -) - // ActionType - Enum. Indicates the action type. "Internal" refers to actions that are for internal only APIs. type ActionType string diff --git a/internal/api/v20240610preview/generated/externalauths_client.go b/internal/api/v20240610preview/generated/externalauths_client.go index e8a48bba56..bc8ac734c7 100644 --- a/internal/api/v20240610preview/generated/externalauths_client.go +++ b/internal/api/v20240610preview/generated/externalauths_client.go @@ -1,4 +1,4 @@ -// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.72) +// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.73) // Changes may cause incorrect behavior and will be lost if the code is regenerated. // Code generated by @autorest/go. DO NOT EDIT. diff --git a/internal/api/v20240610preview/generated/hcpopenshiftclusters_client.go b/internal/api/v20240610preview/generated/hcpopenshiftclusters_client.go index 338481eb59..8e70efdb41 100644 --- a/internal/api/v20240610preview/generated/hcpopenshiftclusters_client.go +++ b/internal/api/v20240610preview/generated/hcpopenshiftclusters_client.go @@ -1,4 +1,4 @@ -// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.72) +// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.73) // Changes may cause incorrect behavior and will be lost if the code is regenerated. // Code generated by @autorest/go. DO NOT EDIT. diff --git a/internal/api/v20240610preview/generated/hcpopenshiftversions_client.go b/internal/api/v20240610preview/generated/hcpopenshiftversions_client.go index 4e7f12444b..f0b3a206ef 100644 --- a/internal/api/v20240610preview/generated/hcpopenshiftversions_client.go +++ b/internal/api/v20240610preview/generated/hcpopenshiftversions_client.go @@ -1,4 +1,4 @@ -// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.72) +// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.73) // Changes may cause incorrect behavior and will be lost if the code is regenerated. // Code generated by @autorest/go. DO NOT EDIT. diff --git a/internal/api/v20240610preview/generated/hcpoperatoridentityrolesets_client.go b/internal/api/v20240610preview/generated/hcpoperatoridentityrolesets_client.go index 9b1e923c87..c4147c0004 100644 --- a/internal/api/v20240610preview/generated/hcpoperatoridentityrolesets_client.go +++ b/internal/api/v20240610preview/generated/hcpoperatoridentityrolesets_client.go @@ -1,4 +1,4 @@ -// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.72) +// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.73) // Changes may cause incorrect behavior and will be lost if the code is regenerated. // Code generated by @autorest/go. DO NOT EDIT. diff --git a/internal/api/v20240610preview/generated/models.go b/internal/api/v20240610preview/generated/models.go index 8af22eb3d0..4091545a17 100644 --- a/internal/api/v20240610preview/generated/models.go +++ b/internal/api/v20240610preview/generated/models.go @@ -1,4 +1,4 @@ -// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.72) +// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.73) // Changes may cause incorrect behavior and will be lost if the code is regenerated. // Code generated by @autorest/go. DO NOT EDIT. @@ -1106,15 +1106,15 @@ type UsernameClaimProfile struct { // Prefix policy is an optional field that configures how a prefix should be applied to the value of the JWT claim specified // in the 'claim' field. - // Allowed values are 'Prefix', 'NoPrefix', and omitted (not provided or an empty string). + // Allowed values are 'Prefix', 'NoPrefix', and 'None'. // When set to 'Prefix', the value specified in the prefix field will be prepended to the value of the JWT claim. The prefix // field must be set when prefixPolicy is 'Prefix'. // When set to 'NoPrefix', no prefix will be prepended to the value of the JWT claim. - // When omitted, this means no opinion and the platform is left to choose any prefixes that are applied which is subject to - // change over time. Currently, the platform prepends {issuerURL}# to the value of - // the JWT claim when the claim is not 'email'. As an example, consider the following scenario:prefix is unset, issuerURL - // is set to https://myoidc.tld, the JWT claims include "username":"userA" and - // "email":"userA + // When set to 'None', this means no opinion and the platform is left to choose any prefixes that are applied which is subject + // to change over time. Currently, the platform prepends {issuerURL}# to the + // value of the JWT claim when the claim is not 'email'. As an example, consider the following scenario:prefix is unset, issuerURL + // is set to https://myoidc.tld, the JWT claims include "username":"userA" + // and "email":"userA PrefixPolicy *string } @@ -1129,15 +1129,15 @@ type UsernameClaimProfileUpdate struct { // Prefix policy is an optional field that configures how a prefix should be applied to the value of the JWT claim specified // in the 'claim' field. - // Allowed values are 'Prefix', 'NoPrefix', and omitted (not provided or an empty string). + // Allowed values are 'Prefix', 'NoPrefix', and 'None'. // When set to 'Prefix', the value specified in the prefix field will be prepended to the value of the JWT claim. The prefix // field must be set when prefixPolicy is 'Prefix'. // When set to 'NoPrefix', no prefix will be prepended to the value of the JWT claim. - // When omitted, this means no opinion and the platform is left to choose any prefixes that are applied which is subject to - // change over time. Currently, the platform prepends {issuerURL}# to the value of - // the JWT claim when the claim is not 'email'. As an example, consider the following scenario:prefix is unset, issuerURL - // is set to https://myoidc.tld, the JWT claims include "username":"userA" and - // "email":"userA + // When set to 'None', this means no opinion and the platform is left to choose any prefixes that are applied which is subject + // to change over time. Currently, the platform prepends {issuerURL}# to the + // value of the JWT claim when the claim is not 'email'. As an example, consider the following scenario:prefix is unset, issuerURL + // is set to https://myoidc.tld, the JWT claims include "username":"userA" + // and "email":"userA PrefixPolicy *string } diff --git a/internal/api/v20240610preview/generated/models_serde.go b/internal/api/v20240610preview/generated/models_serde.go index 8389460854..b4331c29b4 100644 --- a/internal/api/v20240610preview/generated/models_serde.go +++ b/internal/api/v20240610preview/generated/models_serde.go @@ -1,4 +1,4 @@ -// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.72) +// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.73) // Changes may cause incorrect behavior and will be lost if the code is regenerated. // Code generated by @autorest/go. DO NOT EDIT. diff --git a/internal/api/v20240610preview/generated/nodepools_client.go b/internal/api/v20240610preview/generated/nodepools_client.go index 6b965de010..80937aa2de 100644 --- a/internal/api/v20240610preview/generated/nodepools_client.go +++ b/internal/api/v20240610preview/generated/nodepools_client.go @@ -1,4 +1,4 @@ -// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.72) +// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.73) // Changes may cause incorrect behavior and will be lost if the code is regenerated. // Code generated by @autorest/go. DO NOT EDIT. diff --git a/internal/api/v20240610preview/generated/operations_client.go b/internal/api/v20240610preview/generated/operations_client.go index 6ecc358859..e05b083354 100644 --- a/internal/api/v20240610preview/generated/operations_client.go +++ b/internal/api/v20240610preview/generated/operations_client.go @@ -1,4 +1,4 @@ -// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.72) +// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.73) // Changes may cause incorrect behavior and will be lost if the code is regenerated. // Code generated by @autorest/go. DO NOT EDIT. diff --git a/internal/api/v20240610preview/generated/options.go b/internal/api/v20240610preview/generated/options.go index 4e241cd369..cda386ddee 100644 --- a/internal/api/v20240610preview/generated/options.go +++ b/internal/api/v20240610preview/generated/options.go @@ -1,4 +1,4 @@ -// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.72) +// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.73) // Changes may cause incorrect behavior and will be lost if the code is regenerated. // Code generated by @autorest/go. DO NOT EDIT. diff --git a/internal/api/v20240610preview/generated/responses.go b/internal/api/v20240610preview/generated/responses.go index 6f7d8127ce..a822855100 100644 --- a/internal/api/v20240610preview/generated/responses.go +++ b/internal/api/v20240610preview/generated/responses.go @@ -1,4 +1,4 @@ -// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.72) +// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.73) // Changes may cause incorrect behavior and will be lost if the code is regenerated. // Code generated by @autorest/go. DO NOT EDIT. diff --git a/internal/api/v20240610preview/generated/time_rfc3339.go b/internal/api/v20240610preview/generated/time_rfc3339.go index 4cc25b5536..12caa71355 100644 --- a/internal/api/v20240610preview/generated/time_rfc3339.go +++ b/internal/api/v20240610preview/generated/time_rfc3339.go @@ -1,4 +1,4 @@ -// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.72) +// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.73) // Changes may cause incorrect behavior and will be lost if the code is regenerated. // Code generated by @autorest/go. DO NOT EDIT. diff --git a/internal/api/v20240610preview/generated/version.go b/internal/api/v20240610preview/generated/version.go new file mode 100644 index 0000000000..ec56b9ec1d --- /dev/null +++ b/internal/api/v20240610preview/generated/version.go @@ -0,0 +1,22 @@ +// Copyright 2025 Microsoft Corporation +// +// 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. + +// Code generated by Microsoft (R) AutoRest Code Generator (autorest: 3.10.8, generator: @autorest/go@4.0.0-preview.73) + +package generated + +const ( + moduleName = "does_not_matter" + moduleVersion = "v0.0.1" +) diff --git a/internal/api/v20240610preview/register.go b/internal/api/v20240610preview/register.go index fa0b7b4d78..c69d5c2081 100644 --- a/internal/api/v20240610preview/register.go +++ b/internal/api/v20240610preview/register.go @@ -27,11 +27,30 @@ func (v version) String() string { } var ( - validate = api.NewValidator() - clusterStructTagMap = api.NewStructTagMap[api.HCPOpenShiftCluster]() - nodePoolStructTagMap = api.NewStructTagMap[api.HCPOpenShiftClusterNodePool]() + validate = api.NewValidator() + clusterStructTagMap = api.NewStructTagMap[api.HCPOpenShiftCluster]() + nodePoolStructTagMap = api.NewStructTagMap[api.HCPOpenShiftClusterNodePool]() + externalAuthStructTagMap = api.NewStructTagMap[api.HCPOpenShiftClusterExternalAuth]() ) +type UsernameClaimPrefixPolicyType string + +const ( + UsernameClaimPrefixPolicyTypePrefix UsernameClaimPrefixPolicyType = "Prefix" + UsernameClaimPrefixPolicyTypeNoPrefix UsernameClaimPrefixPolicyType = "NoPrefix" + UsernameClaimPrefixPolicyTypeNone UsernameClaimPrefixPolicyType = "None" +) + +// FIXME This is a hack because we typed this field as string and not an enum in the API spec. +// PossibleUsernameClaimPrefixPolicyTypeValues returns the possible values for the UsernameClaimPrefixPolicyType const type. +func PossibleUsernameClaimPrefixPolicyTypeValues() []UsernameClaimPrefixPolicyType { + return []UsernameClaimPrefixPolicyType{ + UsernameClaimPrefixPolicyTypePrefix, + UsernameClaimPrefixPolicyTypeNoPrefix, + UsernameClaimPrefixPolicyTypeNone, + } +} + func init() { // NOTE: If future versions of the API expand field visibility, such as // a field with @visibility("read","create") becoming updatable, @@ -59,6 +78,7 @@ func init() { validate.RegisterAlias("enum_visibility", api.EnumValidateTag(generated.PossibleVisibilityValues()...)) validate.RegisterAlias("enum_clusterimageregistryprofilestate", api.EnumValidateTag(generated.PossibleClusterImageRegistryProfileStateValues()...)) validate.RegisterAlias("enum_externalauthclienttype", api.EnumValidateTag(generated.PossibleExternalAuthClientTypeValues()...)) + validate.RegisterAlias("enum_usernameclaimprefixpolicytype", api.EnumValidateTag(PossibleUsernameClaimPrefixPolicyTypeValues()...)) validate.RegisterAlias("enum_tokenvalidationruletyperequiredclaim", api.EnumValidateTag(generated.PossibleTokenValidationRuleTypeValues()...)) validate.RegisterAlias("enum_externalauthconditiontype", api.EnumValidateTag(generated.PossibleExternalAuthConditionTypeValues()...)) validate.RegisterAlias("enum_externalauthconditionstatustype", api.EnumValidateTag(generated.PossibleStatusTypeValues()...)) diff --git a/internal/api/v20240610preview/register_test.go b/internal/api/v20240610preview/register_test.go index 1532b1dab5..63782c76d9 100644 --- a/internal/api/v20240610preview/register_test.go +++ b/internal/api/v20240610preview/register_test.go @@ -182,3 +182,55 @@ func TestNodePoolStructTagMap(t *testing.T) { testStructTagMap(t, nodePoolStructTagMap, expectedVisibility) } + +func TestExternalAuthStructTagMap(t *testing.T) { + // This should include any nodePoolStructTagMap + // overrides from the package's init() function. + expectedVisibility := map[string]api.VisibilityFlags{ + "ProxyResource.Resource.ID": api.VisibilityRead, + "ProxyResource.Resource.Name": api.VisibilityRead, + "ProxyResource.Resource.Type": api.VisibilityRead, + "ProxyResource.Resource.SystemData": skip, + "ProxyResource.Resource.SystemData.CreatedBy": api.VisibilityRead, + "ProxyResource.Resource.SystemData.CreatedByType": api.VisibilityRead, + "ProxyResource.Resource.SystemData.CreatedAt": api.VisibilityRead, + "ProxyResource.Resource.SystemData.LastModifiedBy": api.VisibilityRead, + "ProxyResource.Resource.SystemData.LastModifiedByType": api.VisibilityRead, + "ProxyResource.Resource.SystemData.LastModifiedAt": api.VisibilityRead, + "Properties": skip, + "Properties.ProvisioningState": api.VisibilityRead, + "Properties.Condition": skip, + "Properties.Condition.ConditionType": api.VisibilityRead, + "Properties.Condition.Status": api.VisibilityRead, + "Properties.Condition.LastTransitionTime": api.VisibilityRead, + "Properties.Condition.Reason": api.VisibilityRead, + "Properties.Condition.Message": api.VisibilityRead, + "Properties.Issuer": skip, + "Properties.Issuer.Url": api.VisibilityRead | api.VisibilityCreate | api.VisibilityUpdate, + "Properties.Issuer.Audiences": api.VisibilityRead | api.VisibilityCreate | api.VisibilityUpdate, + "Properties.Issuer.Ca": api.VisibilityRead | api.VisibilityCreate | api.VisibilityUpdate, + "Properties.Clients": skip, + "Properties.Clients.Component": skip, + "Properties.Clients.Component.Name": api.VisibilityRead | api.VisibilityCreate | api.VisibilityUpdate, + "Properties.Clients.Component.AuthClientNamespace": api.VisibilityRead | api.VisibilityCreate | api.VisibilityUpdate, + "Properties.Clients.ClientId": api.VisibilityRead | api.VisibilityCreate | api.VisibilityUpdate, + "Properties.Clients.ExtraScopes": api.VisibilityRead | api.VisibilityCreate | api.VisibilityUpdate, + "Properties.Clients.ExternalAuthClientProfileType": api.VisibilityRead | api.VisibilityCreate | api.VisibilityUpdate, + "Properties.Claim": skip, + "Properties.Claim.Mappings": skip, + "Properties.Claim.Mappings.Username": skip, + "Properties.Claim.Mappings.Username.Claim": api.VisibilityRead | api.VisibilityCreate | api.VisibilityUpdate, + "Properties.Claim.Mappings.Username.Prefix": api.VisibilityRead | api.VisibilityCreate | api.VisibilityUpdate, + "Properties.Claim.Mappings.Username.PrefixPolicy": api.VisibilityRead | api.VisibilityCreate | api.VisibilityUpdate, + "Properties.Claim.Mappings.Groups": skip, + "Properties.Claim.Mappings.Groups.Claim": api.VisibilityRead | api.VisibilityCreate | api.VisibilityUpdate, + "Properties.Claim.Mappings.Groups.Prefix": api.VisibilityRead | api.VisibilityCreate | api.VisibilityUpdate, + "Properties.Claim.ValidationRules": skip, + "Properties.Claim.ValidationRules.TokenClaimValidationRuleType": api.VisibilityRead | api.VisibilityCreate | api.VisibilityUpdate, + "Properties.Claim.ValidationRules.RequiredClaim": skip, + "Properties.Claim.ValidationRules.RequiredClaim.Claim": api.VisibilityRead | api.VisibilityCreate | api.VisibilityUpdate, + "Properties.Claim.ValidationRules.RequiredClaim.RequiredValue": api.VisibilityRead | api.VisibilityCreate | api.VisibilityUpdate, + } + + testStructTagMap(t, externalAuthStructTagMap, expectedVisibility) +} diff --git a/internal/api/validate.go b/internal/api/validate.go index b626f9ef7a..d6dddd616b 100644 --- a/internal/api/validate.go +++ b/internal/api/validate.go @@ -91,6 +91,11 @@ func NewValidator() *validator.Validate { ExternalAuthClientTypeConfidential, ExternalAuthClientTypePublic, )) + validate.RegisterAlias("enum_usernameclaimprefixpolicytype", EnumValidateTag( + UsernameClaimPrefixPolicyTypePrefix, + UsernameClaimPrefixPolicyTypeNoPrefix, + UsernameClaimPrefixPolicyTypeNone, + )) validate.RegisterAlias("enum_externalauthconditiontype", EnumValidateTag( ExternalAuthConditionTypeAvailable, ExternalAuthConditionTypeDegraded,