From 334fe72cccc9edd1091b59d65f4b23eff4c0d1ef Mon Sep 17 00:00:00 2001 From: Srujan Bandarkar Date: Wed, 7 May 2025 13:50:07 -0400 Subject: [PATCH 1/7] Adding Support for FlexibleFIC --- .../identity/_client_factory.py | 2 +- .../cli/command_modules/identity/_help.py | 12 ++++++-- .../cli/command_modules/identity/_params.py | 6 ++-- .../cli/command_modules/identity/commands.py | 4 +-- .../cli/command_modules/identity/custom.py | 30 ++++++++++++++----- 5 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/identity/_client_factory.py b/src/azure-cli/azure/cli/command_modules/identity/_client_factory.py index a549775369d..c8d5c21b3f6 100644 --- a/src/azure-cli/azure/cli/command_modules/identity/_client_factory.py +++ b/src/azure-cli/azure/cli/command_modules/identity/_client_factory.py @@ -27,4 +27,4 @@ def _msi_operations_operations(cli_ctx, _): def _msi_federated_identity_credentials_operations(cli_ctx, _): - return _msi_client_factory(cli_ctx).federated_identity_credentials + return _msi_client_factory(cli_ctx, api_version='2025-01-31-preview').federated_identity_credentials \ No newline at end of file diff --git a/src/azure-cli/azure/cli/command_modules/identity/_help.py b/src/azure-cli/azure/cli/command_modules/identity/_help.py index 44949e01792..4ea3868627a 100644 --- a/src/azure-cli/azure/cli/command_modules/identity/_help.py +++ b/src/azure-cli/azure/cli/command_modules/identity/_help.py @@ -45,18 +45,24 @@ type: command short-summary: Create a federated identity credential under an existing user assigned identity. examples: - - name: Create a federated identity credential under a specific user assigned identity. + - name: Create a federated identity credential using subject. text: | az identity federated-credential create --name myFicName --identity-name myIdentityName --resource-group myResourceGroup --issuer myIssuer --subject mySubject --audiences myAudiences + - name: Create a federated identity credential using claims matching expression. + text: | + az identity federated-credential create --name myFicName --identity-name myIdentityName --resource-group myResourceGroup --issuer myIssuer --cme-value "expression" --cme-version 1 --audiences myAudiences """ helps['identity federated-credential update'] = """ type: command short-summary: Update a federated identity credential under an existing user assigned identity. examples: - - name: Update a federated identity credential under a specific user assigned identity. + - name: Update a federated identity credential using subject. text: | az identity federated-credential update --name myFicName --identity-name myIdentityName --resource-group myResourceGroup --issuer myIssuer --subject mySubject --audiences myAudiences + - name: Update a federated identity credential using claims matching expression. + text: | + az identity federated-credential update --name myFicName --identity-name myIdentityName --resource-group myResourceGroup --issuer myIssuer --cme-value "expression" --cme-version 1 --audiences myAudiences """ helps['identity federated-credential delete'] = """ @@ -84,4 +90,4 @@ - name: List all federated identity credentials under an existing user assigned identity. text: | az identity federated-credential list --identity-name myIdentityName --resource-group myResourceGroup -""" +""" \ No newline at end of file diff --git a/src/azure-cli/azure/cli/command_modules/identity/_params.py b/src/azure-cli/azure/cli/command_modules/identity/_params.py index 9ff9aaee2bc..187a8da7f34 100644 --- a/src/azure-cli/azure/cli/command_modules/identity/_params.py +++ b/src/azure-cli/azure/cli/command_modules/identity/_params.py @@ -22,12 +22,14 @@ def load_arguments(self, _): c.argument('location', get_location_type(self.cli_ctx), required=False) c.argument('tags', tags_type) - with self.argument_context('identity federated-credential', min_api='2022-01-31-preview') as c: + with self.argument_context('identity federated-credential', min_api='2025-01-31-preview') as c: c.argument('federated_credential_name', options_list=('--name', '-n'), help='The name of the federated identity credential resource.') c.argument('identity_name', help='The name of the identity resource.') for scope in ['identity federated-credential create', 'identity federated-credential update']: with self.argument_context(scope) as c: c.argument('issuer', help='The openId connect metadata URL of the issuer of the identity provider that Azure AD would use in the token exchange protocol for validating tokens before issuing a token as the user-assigned managed identity.') - c.argument('subject', help='The sub value in the token sent to Azure AD for getting the user-assigned managed identity token. The value configured in the federated credential and the one in the incoming token must exactly match for Azure AD to issue the access token.') + c.argument('subject', help='The sub value in the token sent to Azure AD for getting the user-assigned managed identity token. The value configured in the federated credential and the one in the incoming token must exactly match for Azure AD to issue the access token. Cannot be used with --claims-matching-expression-value.') c.argument('audiences', nargs='+', help='The aud value in the token sent to Azure for getting the user-assigned managed identity token. The value configured in the federated credential and the one in the incoming token must exactly match for Azure to issue the access token.') + c.argument('claims_matching_expression_value', options_list=['--cme-value'], help='The claims matching expression value that will be used to match against the subject claim in the token. When specified, --subject cannot be used.') + c.argument('claims_matching_expression_language_version', type=int, options_list=['--cme-version'], help='The language version of the claims matching expression. Default is 1.') \ No newline at end of file diff --git a/src/azure-cli/azure/cli/command_modules/identity/commands.py b/src/azure-cli/azure/cli/command_modules/identity/commands.py index d43da6df438..5d6c6b3e39b 100644 --- a/src/azure-cli/azure/cli/command_modules/identity/commands.py +++ b/src/azure-cli/azure/cli/command_modules/identity/commands.py @@ -39,9 +39,9 @@ def load_command_table(self, _): with self.command_group('identity federated-credential', federated_identity_credentials_sdk, client_factory=_msi_federated_identity_credentials_operations, - min_api='2022-01-31-preview') as g: + min_api='2025-01-31-preview') as g: g.custom_command('create', 'create_or_update_federated_credential') g.custom_command('update', 'create_or_update_federated_credential') g.custom_show_command('show', 'show_federated_credential') g.custom_command('delete', 'delete_federated_credential', confirmation=True) - g.custom_command('list', 'list_federated_credential') + g.custom_command('list', 'list_federated_credential') \ No newline at end of file diff --git a/src/azure-cli/azure/cli/command_modules/identity/custom.py b/src/azure-cli/azure/cli/command_modules/identity/custom.py index c1b80cb8848..081c3e7461e 100644 --- a/src/azure-cli/azure/cli/command_modules/identity/custom.py +++ b/src/azure-cli/azure/cli/command_modules/identity/custom.py @@ -35,15 +35,29 @@ def list_identity_resources(cmd, resource_group_name, resource_name): def create_or_update_federated_credential(cmd, client, resource_group_name, identity_name, federated_credential_name, - issuer=None, subject=None, audiences=None): + issuer=None, subject=None, audiences=None, claims_matching_expression_value=None, + claims_matching_expression_language_version=1): _default_audiences = ['api://AzureADTokenExchange'] audiences = _default_audiences if not audiences else audiences - if not issuer or not subject: - raise RequiredArgumentMissingError('usage error: please provide both --issuer and --subject parameters') - - FederatedIdentityCredential = cmd.get_models('FederatedIdentityCredential', resource_type=ResourceType.MGMT_MSI, - operation_group='federated_identity_credentials') - parameters = FederatedIdentityCredential(issuer=issuer, subject=subject, audiences=audiences) + + if not issuer: + raise RequiredArgumentMissingError('usage error: --issuer parameter is required') + if subject and claims_matching_expression_value: + raise RequiredArgumentMissingError('usage error: --subject and --claims_matching_expression_value cannot be used together') + if not subject and not claims_matching_expression_value: + raise RequiredArgumentMissingError('usage error: either --subject or --claims_matching_expression_value must be specified') + + FederatedIdentityCredential = cmd.get_models('FederatedIdentityCredential', resource_type=ResourceType.MGMT_MSI) + + parameters = FederatedIdentityCredential( + issuer=issuer, + subject=subject if subject else None, + audiences=audiences, + claims_matching_expression={ + 'value': claims_matching_expression_value, + 'languageVersion': claims_matching_expression_language_version + } if claims_matching_expression_value else None + ) return client.create_or_update(resource_group_name=resource_group_name, resource_name=identity_name, federated_identity_credential_resource_name=federated_credential_name, @@ -61,4 +75,4 @@ def show_federated_credential(client, resource_group_name, identity_name, federa def list_federated_credential(client, resource_group_name, identity_name): - return client.list(resource_group_name=resource_group_name, resource_name=identity_name) + return client.list(resource_group_name=resource_group_name, resource_name=identity_name) \ No newline at end of file From 1f451e1b70017cc5fb3bcb08df08efbb4bd075b5 Mon Sep 17 00:00:00 2001 From: Srujan Bandarkar Date: Wed, 7 May 2025 14:10:06 -0400 Subject: [PATCH 2/7] add back the operation_group --- src/azure-cli/azure/cli/command_modules/identity/custom.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/azure-cli/azure/cli/command_modules/identity/custom.py b/src/azure-cli/azure/cli/command_modules/identity/custom.py index 081c3e7461e..6717d486cab 100644 --- a/src/azure-cli/azure/cli/command_modules/identity/custom.py +++ b/src/azure-cli/azure/cli/command_modules/identity/custom.py @@ -47,7 +47,8 @@ def create_or_update_federated_credential(cmd, client, resource_group_name, iden if not subject and not claims_matching_expression_value: raise RequiredArgumentMissingError('usage error: either --subject or --claims_matching_expression_value must be specified') - FederatedIdentityCredential = cmd.get_models('FederatedIdentityCredential', resource_type=ResourceType.MGMT_MSI) + FederatedIdentityCredential = cmd.get_models('FederatedIdentityCredential', resource_type=ResourceType.MGMT_MSI, + operation_group='federated_identity_credentials') parameters = FederatedIdentityCredential( issuer=issuer, From 1640ec6feefc6a62f63ea2505dc6854349227975 Mon Sep 17 00:00:00 2001 From: Srujan Bandarkar Date: Wed, 7 May 2025 15:18:05 -0400 Subject: [PATCH 3/7] Handle API versions through the SDK profile system. --- src/azure-cli-core/azure/cli/core/profiles/_shared.py | 6 +++++- .../azure/cli/command_modules/identity/_client_factory.py | 2 +- src/azure-cli/azure/cli/command_modules/identity/_params.py | 2 +- .../azure/cli/command_modules/identity/commands.py | 3 +-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/azure-cli-core/azure/cli/core/profiles/_shared.py b/src/azure-cli-core/azure/cli/core/profiles/_shared.py index 05bf41b7df9..1efbebac936 100644 --- a/src/azure-cli-core/azure/cli/core/profiles/_shared.py +++ b/src/azure-cli-core/azure/cli/core/profiles/_shared.py @@ -221,7 +221,10 @@ def default_api_version(self): ResourceType.MGMT_SERVICEBUS: '2022-10-01-preview', ResourceType.MGMT_EVENTHUB: '2022-01-01-preview', ResourceType.MGMT_MONITOR: None, - ResourceType.MGMT_MSI: '2023-01-31', + ResourceType.MGMT_MSI: SDKProfile('2023-01-31', { + 'federated_identity_credentials': '2025-01-31-preview', + 'user_assigned_identities': '2022-01-31-preview' + }), ResourceType.MGMT_APPSERVICE: '2023-01-01', ResourceType.MGMT_IOTHUB: '2023-06-30-preview', ResourceType.MGMT_IOTDPS: '2021-10-15', @@ -263,6 +266,7 @@ def default_api_version(self): }, ResourceType.MGMT_MSI: { 'user_assigned_identities': '2022-01-31-preview', + 'federated_identity_credentials': '2025-01-31-preview' } } diff --git a/src/azure-cli/azure/cli/command_modules/identity/_client_factory.py b/src/azure-cli/azure/cli/command_modules/identity/_client_factory.py index c8d5c21b3f6..a549775369d 100644 --- a/src/azure-cli/azure/cli/command_modules/identity/_client_factory.py +++ b/src/azure-cli/azure/cli/command_modules/identity/_client_factory.py @@ -27,4 +27,4 @@ def _msi_operations_operations(cli_ctx, _): def _msi_federated_identity_credentials_operations(cli_ctx, _): - return _msi_client_factory(cli_ctx, api_version='2025-01-31-preview').federated_identity_credentials \ No newline at end of file + return _msi_client_factory(cli_ctx).federated_identity_credentials diff --git a/src/azure-cli/azure/cli/command_modules/identity/_params.py b/src/azure-cli/azure/cli/command_modules/identity/_params.py index 187a8da7f34..762dfd104eb 100644 --- a/src/azure-cli/azure/cli/command_modules/identity/_params.py +++ b/src/azure-cli/azure/cli/command_modules/identity/_params.py @@ -22,7 +22,7 @@ def load_arguments(self, _): c.argument('location', get_location_type(self.cli_ctx), required=False) c.argument('tags', tags_type) - with self.argument_context('identity federated-credential', min_api='2025-01-31-preview') as c: + with self.argument_context('identity federated-credential') as c: c.argument('federated_credential_name', options_list=('--name', '-n'), help='The name of the federated identity credential resource.') c.argument('identity_name', help='The name of the identity resource.') diff --git a/src/azure-cli/azure/cli/command_modules/identity/commands.py b/src/azure-cli/azure/cli/command_modules/identity/commands.py index 5d6c6b3e39b..a230e278829 100644 --- a/src/azure-cli/azure/cli/command_modules/identity/commands.py +++ b/src/azure-cli/azure/cli/command_modules/identity/commands.py @@ -38,8 +38,7 @@ def load_command_table(self, _): g.command('list-operations', 'list') with self.command_group('identity federated-credential', federated_identity_credentials_sdk, - client_factory=_msi_federated_identity_credentials_operations, - min_api='2025-01-31-preview') as g: + client_factory=_msi_federated_identity_credentials_operations) as g: g.custom_command('create', 'create_or_update_federated_credential') g.custom_command('update', 'create_or_update_federated_credential') g.custom_show_command('show', 'show_federated_credential') From ec545653b9564bdeb77076ba15e9921623bc19b1 Mon Sep 17 00:00:00 2001 From: Srujan Bandarkar Date: Wed, 7 May 2025 15:51:27 -0400 Subject: [PATCH 4/7] Specified operation group in both commands.py and \_params.py, which tells the CLI core which API version to use from the SDK profile --- .../azure/cli/command_modules/identity/_params.py | 10 +++++----- .../azure/cli/command_modules/identity/commands.py | 11 +++++++---- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/identity/_params.py b/src/azure-cli/azure/cli/command_modules/identity/_params.py index 762dfd104eb..db0d2e5d596 100644 --- a/src/azure-cli/azure/cli/command_modules/identity/_params.py +++ b/src/azure-cli/azure/cli/command_modules/identity/_params.py @@ -15,21 +15,21 @@ def load_arguments(self, _): - with self.argument_context('identity') as c: + with self.argument_context('identity', operation_group='user_assigned_identities') as c: c.argument('resource_name', arg_type=name_arg_type, id_part='name') - with self.argument_context('identity create') as c: + with self.argument_context('identity create', operation_group='user_assigned_identities') as c: c.argument('location', get_location_type(self.cli_ctx), required=False) c.argument('tags', tags_type) - with self.argument_context('identity federated-credential') as c: + with self.argument_context('identity federated-credential', operation_group='federated_identity_credentials') as c: c.argument('federated_credential_name', options_list=('--name', '-n'), help='The name of the federated identity credential resource.') c.argument('identity_name', help='The name of the identity resource.') for scope in ['identity federated-credential create', 'identity federated-credential update']: - with self.argument_context(scope) as c: + with self.argument_context(scope, operation_group='federated_identity_credentials') as c: c.argument('issuer', help='The openId connect metadata URL of the issuer of the identity provider that Azure AD would use in the token exchange protocol for validating tokens before issuing a token as the user-assigned managed identity.') c.argument('subject', help='The sub value in the token sent to Azure AD for getting the user-assigned managed identity token. The value configured in the federated credential and the one in the incoming token must exactly match for Azure AD to issue the access token. Cannot be used with --claims-matching-expression-value.') c.argument('audiences', nargs='+', help='The aud value in the token sent to Azure for getting the user-assigned managed identity token. The value configured in the federated credential and the one in the incoming token must exactly match for Azure to issue the access token.') c.argument('claims_matching_expression_value', options_list=['--cme-value'], help='The claims matching expression value that will be used to match against the subject claim in the token. When specified, --subject cannot be used.') - c.argument('claims_matching_expression_language_version', type=int, options_list=['--cme-version'], help='The language version of the claims matching expression. Default is 1.') \ No newline at end of file + c.argument('claims_matching_expression_language_version', type=int, options_list=['--cme-version'], help='The language version of the claims matching expression. Default is 1.') diff --git a/src/azure-cli/azure/cli/command_modules/identity/commands.py b/src/azure-cli/azure/cli/command_modules/identity/commands.py index a230e278829..9c49bab0d89 100644 --- a/src/azure-cli/azure/cli/command_modules/identity/commands.py +++ b/src/azure-cli/azure/cli/command_modules/identity/commands.py @@ -16,15 +16,18 @@ def load_command_table(self, _): identity_sdk = CliCommandType( operations_tmpl='azure.mgmt.msi.operations#UserAssignedIdentitiesOperations.{}', - client_factory=_msi_user_identities_operations + client_factory=_msi_user_identities_operations, + operation_group='user_assigned_identities' ) msi_operations_sdk = CliCommandType( operations_tmpl='azure.mgmt.msi.operations#Operations.{}', - client_factory=_msi_operations_operations + client_factory=_msi_operations_operations, + operation_group='operations' ) federated_identity_credentials_sdk = CliCommandType( operations_tmpl='azure.mgmt.msi.operations#FederatedIdentityCredentialsOperations.{}', - client_factory=_msi_federated_identity_credentials_operations + client_factory=_msi_federated_identity_credentials_operations, + operation_group='federated_identity_credentials' ) with self.command_group('identity', identity_sdk, client_factory=_msi_user_identities_operations) as g: @@ -43,4 +46,4 @@ def load_command_table(self, _): g.custom_command('update', 'create_or_update_federated_credential') g.custom_show_command('show', 'show_federated_credential') g.custom_command('delete', 'delete_federated_credential', confirmation=True) - g.custom_command('list', 'list_federated_credential') \ No newline at end of file + g.custom_command('list', 'list_federated_credential') From e89f63b4f9165fd250a40e6d742198cd1b99c2e6 Mon Sep 17 00:00:00 2001 From: Srujan Bandarkar Date: Wed, 7 May 2025 16:14:15 -0400 Subject: [PATCH 5/7] Fixed the cmeVersion default value error --- .../azure/cli/command_modules/identity/_params.py | 2 +- .../azure/cli/command_modules/identity/custom.py | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/identity/_params.py b/src/azure-cli/azure/cli/command_modules/identity/_params.py index db0d2e5d596..1ec83777879 100644 --- a/src/azure-cli/azure/cli/command_modules/identity/_params.py +++ b/src/azure-cli/azure/cli/command_modules/identity/_params.py @@ -32,4 +32,4 @@ def load_arguments(self, _): c.argument('subject', help='The sub value in the token sent to Azure AD for getting the user-assigned managed identity token. The value configured in the federated credential and the one in the incoming token must exactly match for Azure AD to issue the access token. Cannot be used with --claims-matching-expression-value.') c.argument('audiences', nargs='+', help='The aud value in the token sent to Azure for getting the user-assigned managed identity token. The value configured in the federated credential and the one in the incoming token must exactly match for Azure to issue the access token.') c.argument('claims_matching_expression_value', options_list=['--cme-value'], help='The claims matching expression value that will be used to match against the subject claim in the token. When specified, --subject cannot be used.') - c.argument('claims_matching_expression_language_version', type=int, options_list=['--cme-version'], help='The language version of the claims matching expression. Default is 1.') + c.argument('claims_matching_expression_language_version', type=int, options_list=['--cme-version'], help='The language version of the claims matching expression.') diff --git a/src/azure-cli/azure/cli/command_modules/identity/custom.py b/src/azure-cli/azure/cli/command_modules/identity/custom.py index 6717d486cab..a74eda6752b 100644 --- a/src/azure-cli/azure/cli/command_modules/identity/custom.py +++ b/src/azure-cli/azure/cli/command_modules/identity/custom.py @@ -36,16 +36,18 @@ def list_identity_resources(cmd, resource_group_name, resource_name): def create_or_update_federated_credential(cmd, client, resource_group_name, identity_name, federated_credential_name, issuer=None, subject=None, audiences=None, claims_matching_expression_value=None, - claims_matching_expression_language_version=1): + claims_matching_expression_language_version=None): _default_audiences = ['api://AzureADTokenExchange'] audiences = _default_audiences if not audiences else audiences if not issuer: - raise RequiredArgumentMissingError('usage error: --issuer parameter is required') + raise RequiredArgumentMissingError('usage error: --issuer is required') if subject and claims_matching_expression_value: - raise RequiredArgumentMissingError('usage error: --subject and --claims_matching_expression_value cannot be used together') + raise RequiredArgumentMissingError('usage error: --subject and --claims-matching-expression-value cannot be used together') if not subject and not claims_matching_expression_value: - raise RequiredArgumentMissingError('usage error: either --subject or --claims_matching_expression_value must be specified') + raise RequiredArgumentMissingError('usage error: --subject or --claims-matching-expression-value is required') + if claims_matching_expression_value and claims_matching_expression_language_version is None: + raise RequiredArgumentMissingError('usage error: --claims-matching-expression-language-version must be specified when using --claims-matching-expression-value') FederatedIdentityCredential = cmd.get_models('FederatedIdentityCredential', resource_type=ResourceType.MGMT_MSI, operation_group='federated_identity_credentials') @@ -76,4 +78,4 @@ def show_federated_credential(client, resource_group_name, identity_name, federa def list_federated_credential(client, resource_group_name, identity_name): - return client.list(resource_group_name=resource_group_name, resource_name=identity_name) \ No newline at end of file + return client.list(resource_group_name=resource_group_name, resource_name=identity_name) From a8d1d6209b389e73cee854f80b535e40bfb61a76 Mon Sep 17 00:00:00 2001 From: Srujan Bandarkar Date: Wed, 7 May 2025 16:23:05 -0400 Subject: [PATCH 6/7] add test coverage --- .../identity/tests/latest/test_identity.py | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/azure-cli/azure/cli/command_modules/identity/tests/latest/test_identity.py b/src/azure-cli/azure/cli/command_modules/identity/tests/latest/test_identity.py index 9367a106f03..c3f326f8901 100644 --- a/src/azure-cli/azure/cli/command_modules/identity/tests/latest/test_identity.py +++ b/src/azure-cli/azure/cli/command_modules/identity/tests/latest/test_identity.py @@ -35,11 +35,14 @@ def test_federated_identity_credential(self, resource_group): 'identity': 'ide', 'fic1': 'fic1', 'fic2': 'fic2', + 'fic3': 'fic3', 'subject1': 'system:serviceaccount:ns:svcaccount1', 'subject2': 'system:serviceaccount:ns:svcaccount2', 'subject3': 'system:serviceaccount:ns:svcaccount3', 'issuer': 'https://oidc.prod-aks.azure.com/IssuerGUID', 'audience': 'api://AzureADTokenExchange', + 'cme_value': 'value.matches(\'test\')', + 'cme_version': '1' }) self.cmd('identity create -n {identity} -g {rg}') @@ -118,3 +121,56 @@ def test_federated_identity_credential(self, resource_group): self.check('type(@)', 'array'), self.check('length(@)', 0) ]) + + @ResourceGroupPreparer(name_prefix='cli_test_federated_identity_credential_cme_', location='eastus2euap') + def test_federated_identity_credential_claims_matching(self, resource_group): + self.kwargs.update({ + 'identity': 'ide', + 'fic1': 'fic1', + 'issuer': 'https://oidc.prod-aks.azure.com/IssuerGUID', + 'audience': 'api://AzureADTokenExchange', + 'cme_value': 'value.matches(\'test\')', + 'cme_version': '1', + 'subject': 'system:serviceaccount:ns:svcaccount1' + }) + + self.cmd('identity create -n {identity} -g {rg}') + + # create a federated identity credential with claims matching expression + self.cmd('identity federated-credential create --name {fic1} --identity-name {identity} --resource-group {rg} ' + '--issuer {issuer} --audiences {audience} --cme-value {cme_value} --cme-version {cme_version}', + checks=[ + self.check('length(audiences)', 1), + self.check('audiences[0]', '{audience}'), + self.check('issuer', '{issuer}'), + self.check('claimsMatchingExpression.value', '{cme_value}'), + self.check('claimsMatchingExpression.languageVersion', '{cme_version}') + ]) + + # update to use subject instead of claims matching expression + self.cmd('identity federated-credential update --name {fic1} --identity-name {identity} --resource-group {rg} ' + '--issuer {issuer} --audiences {audience} --subject {subject}', + checks=[ + self.check('subject', '{subject}'), + self.check('claimsMatchingExpression', None) + ]) + + # update back to claims matching expression + self.cmd('identity federated-credential update --name {fic1} --identity-name {identity} --resource-group {rg} ' + '--issuer {issuer} --audiences {audience} --cme-value {cme_value} --cme-version {cme_version}', + checks=[ + self.check('claimsMatchingExpression.value', '{cme_value}'), + self.check('claimsMatchingExpression.languageVersion', '{cme_version}'), + self.check('subject', None) + ]) + + def test_federated_identity_credential_validation(self): + # Test missing claims matching expression version + with self.assertRaisesRegex( + Exception, '--claims-matching-expression-language-version must be specified when using --claims-matching-expression-value'): + self.cmd('identity federated-credential create -g rg1 --identity-name testid --name testfic --issuer https://test.com --cme-value "test"') + + # Test version without value + with self.assertRaisesRegex( + Exception, '--subject or --claims-matching-expression-value is required'): + self.cmd('identity federated-credential create -g rg1 --identity-name testid --name testfic --issuer https://test.com --cme-version 1') From 247f63e8cd5ba561ded25cc4099f31da79b8a3a3 Mon Sep 17 00:00:00 2001 From: Srujan Bandarkar Date: Wed, 7 May 2025 17:29:47 -0400 Subject: [PATCH 7/7] use snakecase --- src/azure-cli/azure/cli/command_modules/identity/custom.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/azure-cli/azure/cli/command_modules/identity/custom.py b/src/azure-cli/azure/cli/command_modules/identity/custom.py index a74eda6752b..c45b0509f13 100644 --- a/src/azure-cli/azure/cli/command_modules/identity/custom.py +++ b/src/azure-cli/azure/cli/command_modules/identity/custom.py @@ -56,7 +56,7 @@ def create_or_update_federated_credential(cmd, client, resource_group_name, iden issuer=issuer, subject=subject if subject else None, audiences=audiences, - claims_matching_expression={ + claimsMatchingExpression={ 'value': claims_matching_expression_value, 'languageVersion': claims_matching_expression_language_version } if claims_matching_expression_value else None