From a51745a1003aa5099ac8539254be9dcfe5b534bf Mon Sep 17 00:00:00 2001 From: Haroon Feisal Date: Wed, 18 May 2022 16:57:58 -0400 Subject: [PATCH 01/10] Finished auth microsoft. --- .../azext_containerapp/_params.py | 21 ++ .../azext_containerapp/commands.py | 17 +- src/containerapp/azext_containerapp/custom.py | 250 +++++++++++++++++- src/containerapp/setup.py | 3 +- 4 files changed, 281 insertions(+), 10 deletions(-) diff --git a/src/containerapp/azext_containerapp/_params.py b/src/containerapp/azext_containerapp/_params.py index c4779541fb8..58c5b7debc8 100644 --- a/src/containerapp/azext_containerapp/_params.py +++ b/src/containerapp/azext_containerapp/_params.py @@ -257,3 +257,24 @@ def load_arguments(self, _): c.argument('service_principal_client_id', help='The service principal client ID. Used by Github Actions to authenticate with Azure.', options_list=["--service-principal-client-id", "--sp-cid"]) c.argument('service_principal_client_secret', help='The service principal client secret. Used by Github Actions to authenticate with Azure.', options_list=["--service-principal-client-secret", "--sp-sec"]) c.argument('service_principal_tenant_id', help='The service principal tenant ID. Used by Github Actions to authenticate with Azure.', options_list=["--service-principal-tenant-id", "--sp-tid"]) + + with self.argument_context('containerapp auth microsoft update') as c: + c.argument('client_id', options_list=['--client-id'], + help='The Client ID of this relying party application, known as the client_id.') + c.argument('client_secret', options_list=['--client-secret'], + help='AAD application secret') + c.argument('client_secret_setting_name', options_list=['--client-secret-setting-name', '--secret-setting'], + help='The app setting name that contains the client secret of the relying party application.') + c.argument('issuer', options_list=['--issuer'], + help='The OpenID Connect Issuer URI that represents the entity which issues access tokens for this application.') + c.argument('allowed_token_audiences', options_list=['--allowed-token-audiences', '--allowed-audiences'], + help='The configuration settings of the allowed list of audiences from which to validate the JWT token.') + c.argument('client_secret_certificate_thumbprint', options_list=['--thumbprint', '--client-secret-certificate-thumbprint'], + help='Alternative to AAD Client Secret, thumbprint of a certificate used for signing purposes') + c.argument('client_secret_certificate_san', options_list=['--san', '--client-secret-certificate-san'], + help='Alternative to AAD Client Secret and thumbprint, subject alternative name of a certificate used for signing purposes') + c.argument('client_secret_certificate_issuer', options_list=['--certificate-issuer', '--client-secret-certificate-issuer'], + help='Alternative to AAD Client Secret and thumbprint, issuer of a certificate used for signing purposes') + c.argument('yes', options_list=['--yes', '-y'], help='Do not prompt for confirmation.', action='store_true') + c.argument('tenant_id', options_list=['--tenant-id'], + help='The tenant id of the application.') diff --git a/src/containerapp/azext_containerapp/commands.py b/src/containerapp/azext_containerapp/commands.py index 3ef540e70bf..6ed1efeaf58 100644 --- a/src/containerapp/azext_containerapp/commands.py +++ b/src/containerapp/azext_containerapp/commands.py @@ -8,7 +8,7 @@ # from msrestazure.tools import is_valid_resource_id, parse_resource_id from azext_containerapp._client_factory import ex_handler_factory from ._validators import validate_ssh - +from azure.cli.core.commands import CliCommandType def transform_containerapp_output(app): props = ['name', 'location', 'resourceGroup', 'provisioningState'] @@ -42,8 +42,14 @@ def transform_revision_output(rev): def transform_revision_list_output(revs): return [transform_revision_output(r) for r in revs] +def auth_config_client_factory(cli_ctx, *_): + from azure.cli.core.commands.client_factory import get_mgmt_service_client + from azure.cli.core.profiles import CustomResourceType + MGMT_APPCONTAINERS = CustomResourceType(import_prefix='azure.mgmt.appcontainers', client_name='ContainerAppsAPIClient') + return get_mgmt_service_client(cli_ctx, MGMT_APPCONTAINERS, api_version='2022-03-01').container_apps_auth_configs def load_command_table(self, _): + with self.command_group('containerapp', is_preview=True) as g: g.custom_show_command('show', 'show_containerapp', table_transformer=transform_containerapp_output) g.custom_command('list', 'list_containerapp', table_transformer=transform_containerapp_list_output) @@ -126,3 +132,12 @@ def load_command_table(self, _): with self.command_group('containerapp dapr') as g: g.custom_command('enable', 'enable_dapr', exception_handler=ex_handler_factory()) g.custom_command('disable', 'disable_dapr', exception_handler=ex_handler_factory()) + + # with self.command_group('containerapp auth', client_factory=auth_config_client_factory) as g: + # g.custom_command('show', 'create_azure_static_webapps_config', exception_handler=ex_handler_factory()) + # g.custom_command('list', 'create_azure_static_webapps_config', exception_handler=ex_handler_factory()) + # g.custom_command('disable', 'disable_dapr', exception_handler=ex_handler_factory()) + + with self.command_group('containerapp auth microsoft', client_factory=auth_config_client_factory) as g: + g.custom_command('update', 'update_aad_settings', exception_handler=ex_handler_factory()) + g.custom_command('show', 'get_aad_settings', exception_handler=ex_handler_factory()) diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index 837e2c92df8..442cbfe0145 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -16,7 +16,8 @@ ResourceNotFoundError, CLIError, CLIInternalError, - InvalidArgumentValueError) + InvalidArgumentValueError, + ArgumentUsageError) from azure.cli.core.commands.client_factory import get_subscription_id from azure.cli.core.util import open_page_in_browser from azure.cli.command_modules.appservice.utils import _normalize_location @@ -1851,13 +1852,13 @@ def set_secrets(cmd, name, resource_group_name, secrets, no_wait=False): _validate_subscription_registered(cmd, "Microsoft.App") - for s in secrets: - if s: - parsed = s.split("=") - if parsed: - if len(parsed[0]) > MAXIMUM_SECRET_LENGTH: - raise ValidationError(f"Secret names cannot be longer than {MAXIMUM_SECRET_LENGTH}. " - f"Please shorten {parsed[0]}") + # for s in secrets: + # if s: + # parsed = s.split("=") + # if parsed: + # if len(parsed[0]) > MAXIMUM_SECRET_LENGTH: + # raise ValidationError(f"Secret names cannot be longer than {MAXIMUM_SECRET_LENGTH}. " + # f"Please shorten {parsed[0]}") # if not yaml and not secrets: # raise RequiredArgumentMissingError('Usage error: --secrets is required if not using --yaml') @@ -2390,3 +2391,236 @@ def remove_storage(cmd, storage_name, name, resource_group_name, no_wait=False): return StorageClient.delete(cmd, resource_group_name, name, storage_name, no_wait) except CLIError as e: handle_raw_exception(e) + + +def update_auth_settings(cmd, resource_group_name, name, auth_name, enabled=None, action=None, # pylint: disable=unused-argument + client_id=None, token_store_enabled=None, runtime_version=None, # pylint: disable=unused-argument + token_refresh_extension_hours=None, # pylint: disable=unused-argument + allowed_external_redirect_urls=None, client_secret=None, # pylint: disable=unused-argument + client_secret_certificate_thumbprint=None, # pylint: disable=unused-argument + allowed_audiences=None, issuer=None, facebook_app_id=None, # pylint: disable=unused-argument + facebook_app_secret=None, facebook_oauth_scopes=None, # pylint: disable=unused-argument + twitter_consumer_key=None, twitter_consumer_secret=None, # pylint: disable=unused-argument + google_client_id=None, google_client_secret=None, # pylint: disable=unused-argument + google_oauth_scopes=None, microsoft_account_client_id=None, # pylint: disable=unused-argument + microsoft_account_client_secret=None, # pylint: disable=unused-argument + microsoft_account_oauth_scopes=None, slot=None): # pylint: disable=unused-argument + # auth_settings = get_auth_settings(cmd, resource_group_name, name, slot) + + import inspect + frame = inspect.currentframe() + bool_flags = ['enabled', 'token_store_enabled'] + # note: getargvalues is used already in azure.cli.core.commands. + # and no simple functional replacement for this deprecating method for 3.5 + args, _, _, values = inspect.getargvalues(frame) # pylint: disable=deprecated-method + + for arg in args[2:]: + if values.get(arg, None): + setattr(auth_settings, arg, values[arg] if arg not in bool_flags else values[arg] == 'true') + + # return _generic_site_operation(cmd.cli_ctx, resource_group_name, name, 'update_auth_settings', slot, auth_settings) + + +def create_apple_config(cmd, enabled, client_id, client_secret_name, scopes): + from azure.mgmt.appcontainers.models import Apple, AppleRegistration, LoginScopes + apple_config = Apple(enabled=enabled, registration=AppleRegistration(client_id=client_id, client_secret_setting_name=client_secret_name), login=LoginScopes(scopes=scopes)) + return apple_config + + +def create_facebook_config(cmd, enabled, app_id, app_secret_name, graph_api_version, scopes): + from azure.mgmt.appcontainers.models import Facebook, AppRegistration, LoginScopes + facebook_config = Facebook(enabled=enabled, registration=AppRegistration(app_id=app_id, app_secret_setting_name=app_secret_name), graph_api_version=graph_api_version, login=LoginScopes(scopes=scopes)) + return facebook_config + + +def create_twitter_config(cmd, enabled, consumer_key, consumer_secret_name): + from azure.mgmt.appcontainers.models import Twitter, TwitterRegistration + twitter_config = Twitter(enabled=enabled, registration=TwitterRegistration(consumer_key=consumer_key, consumer_secret_setting_name=consumer_secret_name)) + return twitter_config + + +def create_google_config(cmd, enabled, client_id, client_secret_name, scopes, allowed_audiences): + from azure.mgmt.appcontainers.models import Google, ClientRegistration, LoginScopes, AllowedAudiencesValidation + google_config = Google(enabled=enabled, registration=ClientRegistration(client_id=client_id, client_secret_setting_name=client_secret_name), login=LoginScopes(scopes=scopes), validation=AllowedAudiencesValidation(allowed_audiences=allowed_audiences)) + return google_config + + +def create_github_config(cmd, enabled, client_id, client_secret_name, scopes): + from azure.mgmt.appcontainers.models import GitHub, ClientRegistration, LoginScopes + github_config = GitHub(enabled=enabled, registration=ClientRegistration(client_id=client_id, client_secret_setting_name=client_secret_name), login=LoginScopes(scopes=scopes)) + return github_config + + +def create_azure_static_webapps_config(cmd, enabled, client_id): + from azure.mgmt.appcontainers.models import AzureStaticWebApps, AzureStaticWebAppsRegistration + aswa_config = AzureStaticWebApps(enabled=enabled, registration=AzureStaticWebAppsRegistration(client_id=client_id)) + return aswa_config + + +def create_open_id_connect_provider_config(enabled, client_id, method, client_secret_name, authorization_endpoint, token_endpoint, issuer, certification_uri, well_known_open_id_configuration, name_claim_type, scopes): + from azure.mgmt.appcontainers.models import CustomOpenIdConnectProvider, OpenIdConnectRegistration, OpenIdConnectClientCredential, OpenIdConnectConfig, OpenIdConnectLogin + coicp_config = CustomOpenIdConnectProvider(enabled=enabled, registration=OpenIdConnectRegistration(client_id=client_id, client_credential=OpenIdConnectClientCredential(method=method, client_secret_setting_name=client_secret_name),open_id_connect_configuration=OpenIdConnectConfig(authorization_endpoint=authorization_endpoint, token_endpoint=token_endpoint, issuer=issuer, certification_uri=certification_uri, well_known_open_id_configuration=well_known_open_id_configuration)), login=OpenIdConnectLogin(name_claim_type=name_claim_type, scopes=scopes)) + return coicp_config + + +def update_aad_settings(cmd, client, resource_group_name, name, # pylint: disable=unused-argument + client_id=None, client_secret_setting_name=None, # pylint: disable=unused-argument + issuer=None, allowed_token_audiences=None, client_secret=None, # pylint: disable=unused-argument + client_secret_certificate_thumbprint=None, # pylint: disable=unused-argument + client_secret_certificate_san=None, # pylint: disable=unused-argument + client_secret_certificate_issuer=None, # pylint: disable=unused-argument + yes=False, tenant_id=None): # pylint: disable=unused-argument + MICROSOFT_SECRET_SETTING_NAME = "microsoft-provider-authentication-secret" + from knack.prompting import prompt_y_n + from azure.mgmt.appcontainers.models import AuthConfig + + try: + show_ingress(cmd, name, resource_group_name) + except: + raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") + + if client_secret is not None and client_secret_setting_name is not None: + raise ArgumentUsageError('Usage Error: --client-secret and --client-secret-setting-name cannot both be ' + 'configured to non empty strings') + + if client_secret_setting_name is not None and client_secret_certificate_thumbprint is not None: + raise ArgumentUsageError('Usage Error: --client-secret-setting-name and --thumbprint cannot both be ' + 'configured to non empty strings') + + if client_secret is not None and client_secret_certificate_thumbprint is not None: + raise ArgumentUsageError('Usage Error: --client-secret and --thumbprint cannot both be ' + 'configured to non empty strings') + + if client_secret is not None and client_secret_certificate_san is not None: + raise ArgumentUsageError('Usage Error: --client-secret and --san cannot both be ' + 'configured to non empty strings') + + if client_secret_setting_name is not None and client_secret_certificate_san is not None: + raise ArgumentUsageError('Usage Error: --client-secret-setting-name and --san cannot both be ' + 'configured to non empty strings') + + if client_secret_certificate_thumbprint is not None and client_secret_certificate_san is not None: + raise ArgumentUsageError('Usage Error: --thumbprint and --san cannot both be ' + 'configured to non empty strings') + + if ((client_secret_certificate_san is not None and client_secret_certificate_issuer is None) or + (client_secret_certificate_san is None and client_secret_certificate_issuer is not None)): + raise ArgumentUsageError('Usage Error: --san and --certificate-issuer must both be ' + 'configured to non empty strings') + + if issuer is not None and (tenant_id is not None): + raise ArgumentUsageError('Usage Error: --issuer and --tenant-id cannot be configured ' + 'to non empty strings at the same time.') + + is_new_aad_app = False + existing_auth = {} + + try: + existing_auth = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + except: + existing_auth["properties"] = {} + existing_auth["properties"]["platform"] = {} + existing_auth["properties"]["platform"]["enabled"] = True + existing_auth["properties"]["globalValidation"] = {} + existing_auth["properties"]["login"] = {} + + registration = {} + validation = {} + if "identityProviders" not in existing_auth: + existing_auth["identityProviders"] = {} + if "azureActiveDirectory" not in existing_auth["identityProviders"]: + existing_auth["identityProviders"]["azureActiveDirectory"] = {} + is_new_aad_app = True + + if is_new_aad_app and issuer is None and tenant_id is None: + raise CLIError('Usage Error: Either --issuer or --tenant-id must be specified when configuring the ' + 'Microsoft auth registration.') + + if client_secret is not None and not yes: + msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' + if not prompt_y_n(msg, default="n"): + raise CLIError('Usage Error: --client-secret cannot be used without agreeing to add secret ' + 'to the containerapp.') + + openid_issuer = issuer + if openid_issuer is None: + # cmd.cli_ctx.cloud resolves to whichever cloud the customer is currently logged into + authority = cmd.cli_ctx.cloud.endpoints.active_directory + + if tenant_id is not None: + openid_issuer = authority + "/" + tenant_id + "/v2.0" + + # existing_auth = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + registration = {} + validation = {} + if "identityProviders" not in existing_auth: + existing_auth["identityProviders"] = {} + if "azureActiveDirectory" not in existing_auth["identityProviders"]: + existing_auth["identityProviders"]["azureActiveDirectory"] = {} + if (client_id is not None or client_secret is not None or + client_secret_setting_name is not None or openid_issuer is not None or + client_secret_certificate_thumbprint is not None or + client_secret_certificate_san is not None or + client_secret_certificate_issuer is not None): + if "registration" not in existing_auth["identityProviders"]["azureActiveDirectory"]: + existing_auth["identityProviders"]["azureActiveDirectory"]["registration"] = {} + registration = existing_auth["identityProviders"]["azureActiveDirectory"]["registration"] + if allowed_token_audiences is not None: + if "validation" not in existing_auth["identityProviders"]["azureActiveDirectory"]: + existing_auth["identityProviders"]["azureActiveDirectory"]["validation"] = {} + validation = existing_auth["identityProviders"]["azureActiveDirectory"]["validation"] + + if client_id is not None: + registration["clientId"] = client_id + if client_secret_setting_name is not None: + registration["clientSecretSettingName"] = client_secret_setting_name + if client_secret is not None: + registration["clientSecretSettingName"] = MICROSOFT_SECRET_SETTING_NAME + set_secrets(cmd, name, resource_group_name, secrets=[f"{MICROSOFT_SECRET_SETTING_NAME}={client_secret}"], no_wait=True) + if client_secret_setting_name is not None or client_secret is not None: + if "clientSecretCertificateThumbprint" in registration and registration["clientSecretCertificateThumbprint"] is not None: + registration["clientSecretCertificateThumbprint"] = None + if "clientSecretCertificateSubjectAlternativeName" in registration and registration["clientSecretCertificateSubjectAlternativeName"] is not None: + registration["clientSecretCertificateSubjectAlternativeName"] = None + if "clientSecretCertificateIssuer" in registration and registration["clientSecretCertificateIssuer"] is not None: + registration["clientSecretCertificateIssuer"] = None + if client_secret_certificate_thumbprint is not None: + registration["clientSecretCertificateThumbprint"] = client_secret_certificate_thumbprint + if "clientSecretSettingName" in registration and registration["clientSecretSettingName"] is not None: + registration["clientSecretSettingName"] = None + if "clientSecretCertificateSubjectAlternativeName" in registration and registration["clientSecretCertificateSubjectAlternativeName"] is not None: + registration["clientSecretCertificateSubjectAlternativeName"] = None + if "clientSecretCertificateIssuer" in registration and registration["clientSecretCertificateIssuer"] is not None: + registration["clientSecretCertificateIssuer"] = None + if client_secret_certificate_san is not None: + registration["clientSecretCertificateSubjectAlternativeName"] = client_secret_certificate_san + if client_secret_certificate_issuer is not None: + registration["clientSecretCertificateIssuer"] = client_secret_certificate_issuer + if client_secret_certificate_san is not None and client_secret_certificate_issuer is not None: + if "clientSecretSettingName" in registration and registration["clientSecretSettingName"] is not None: + registration["clientSecretSettingName"] = None + if "clientSecretCertificateThumbprint" in registration and registration["clientSecretCertificateThumbprint"] is not None: + registration["clientSecretCertificateThumbprint"] = None + if openid_issuer is not None: + registration["openIdIssuer"] = openid_issuer + if allowed_token_audiences is not None: + validation["allowedAudiences"] = allowed_token_audiences.split(",") + existing_auth["identityProviders"]["azureActiveDirectory"]["validation"] = validation + if (client_id is not None or client_secret is not None or + client_secret_setting_name is not None or issuer is not None or + client_secret_certificate_thumbprint is not None or + client_secret_certificate_san is not None or + client_secret_certificate_issuer is not None): + existing_auth["identityProviders"]["azureActiveDirectory"]["registration"] = registration + + updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize() + return updated_auth_settings + + +def get_aad_settings(cmd, client, resource_group_name, name): + auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + if "identityProviders" not in auth_settings: + return {} + if "azureActiveDirectory" not in auth_settings["identityProviders"]: + return {} + return auth_settings["identityProviders"]["azureActiveDirectory"] diff --git a/src/containerapp/setup.py b/src/containerapp/setup.py index 1d484ae339c..5418eb34abe 100644 --- a/src/containerapp/setup.py +++ b/src/containerapp/setup.py @@ -38,7 +38,8 @@ # TODO: Add any additional SDK dependencies here DEPENDENCIES = [ - 'azure-cli-core' + 'azure-cli-core', + 'azure-mgmt-appcontainers' ] with open('README.rst', 'r', encoding='utf-8') as f: From dac8c2f45d2afb787ce42efe76e07bb3385fe435 Mon Sep 17 00:00:00 2001 From: Haroon Feisal Date: Wed, 18 May 2022 17:46:37 -0400 Subject: [PATCH 02/10] Added prototypes of other identity providers. --- .../azext_containerapp/_constants.py | 10 + src/containerapp/azext_containerapp/custom.py | 603 ++++++++++++++++-- 2 files changed, 550 insertions(+), 63 deletions(-) diff --git a/src/containerapp/azext_containerapp/_constants.py b/src/containerapp/azext_containerapp/_constants.py index 62e655754b0..9035db14672 100644 --- a/src/containerapp/azext_containerapp/_constants.py +++ b/src/containerapp/azext_containerapp/_constants.py @@ -12,3 +12,13 @@ LOG_ANALYTICS_RP = "Microsoft.OperationalInsights" MAX_ENV_PER_LOCATION = 2 + +MICROSOFT_SECRET_SETTING_NAME = "MICROSOFT_PROVIDER_AUTHENTICATION_SECRET".lower().replace("_", "-") +FACEBOOK_SECRET_SETTING_NAME = "FACEBOOK_PROVIDER_AUTHENTICATION_SECRET".lower().replace("_", "-") +GITHUB_SECRET_SETTING_NAME = "GITHUB_PROVIDER_AUTHENTICATION_SECRET".lower().replace("_", "-") +GOOGLE_SECRET_SETTING_NAME = "GOOGLE_PROVIDER_AUTHENTICATION_SECRET".lower().replace("_", "-") +MSA_SECRET_SETTING_NAME = "MSA_PROVIDER_AUTHENTICATION_SECRET".lower().replace("_", "-") +TWITTER_SECRET_SETTING_NAME = "TWITTER_PROVIDER_AUTHENTICATION_SECRET".lower().replace("_", "-") +APPLE_SECRET_SETTING_NAME = "APPLE_PROVIDER_AUTHENTICATION_SECRET".lower().replace("_", "-") +TRUE_STRING = "true" +FALSE_STRING = "false" \ No newline at end of file diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index 442cbfe0145..bb6835a6381 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -22,6 +22,7 @@ from azure.cli.core.util import open_page_in_browser from azure.cli.command_modules.appservice.utils import _normalize_location from knack.log import get_logger +from knack.prompting import prompt_y_n from msrestazure.tools import parse_resource_id, is_valid_resource_id from msrest.exceptions import DeserializationError @@ -61,7 +62,8 @@ from ._ssh_utils import (SSH_DEFAULT_ENCODING, WebSocketConnection, read_ssh, get_stdin_writer, SSH_CTRL_C_MSG, SSH_BACKUP_ENCODING) -from ._constants import MAXIMUM_SECRET_LENGTH +from ._constants import (MAXIMUM_SECRET_LENGTH, MICROSOFT_SECRET_SETTING_NAME, FACEBOOK_SECRET_SETTING_NAME, GITHUB_SECRET_SETTING_NAME, + GOOGLE_SECRET_SETTING_NAME, MSA_SECRET_SETTING_NAME, TWITTER_SECRET_SETTING_NAME, APPLE_SECRET_SETTING_NAME) logger = get_logger(__name__) @@ -2367,7 +2369,7 @@ def create_or_update_storage(cmd, storage_name, resource_group_name, name, azure pass if r: - logger.warning("Only AzureFile account keys can be updated. In order to change the AzureFile share name or account name, please delete this storage and create a new one.") + logger.warning("Only AzureFile account can be updated. In order to change the AzureFile share name or account name, please delete this storage and create a new one.") storage_def = AzureFilePropertiesModel storage_def["accountKey"] = azure_file_account_key @@ -2393,74 +2395,74 @@ def remove_storage(cmd, storage_name, name, resource_group_name, no_wait=False): handle_raw_exception(e) -def update_auth_settings(cmd, resource_group_name, name, auth_name, enabled=None, action=None, # pylint: disable=unused-argument - client_id=None, token_store_enabled=None, runtime_version=None, # pylint: disable=unused-argument - token_refresh_extension_hours=None, # pylint: disable=unused-argument - allowed_external_redirect_urls=None, client_secret=None, # pylint: disable=unused-argument - client_secret_certificate_thumbprint=None, # pylint: disable=unused-argument - allowed_audiences=None, issuer=None, facebook_app_id=None, # pylint: disable=unused-argument - facebook_app_secret=None, facebook_oauth_scopes=None, # pylint: disable=unused-argument - twitter_consumer_key=None, twitter_consumer_secret=None, # pylint: disable=unused-argument - google_client_id=None, google_client_secret=None, # pylint: disable=unused-argument - google_oauth_scopes=None, microsoft_account_client_id=None, # pylint: disable=unused-argument - microsoft_account_client_secret=None, # pylint: disable=unused-argument - microsoft_account_oauth_scopes=None, slot=None): # pylint: disable=unused-argument - # auth_settings = get_auth_settings(cmd, resource_group_name, name, slot) +# def update_auth_settings(cmd, resource_group_name, name, auth_name, enabled=None, action=None, # pylint: disable=unused-argument +# client_id=None, token_store_enabled=None, runtime_version=None, # pylint: disable=unused-argument +# token_refresh_extension_hours=None, # pylint: disable=unused-argument +# allowed_external_redirect_urls=None, client_secret=None, # pylint: disable=unused-argument +# client_secret_certificate_thumbprint=None, # pylint: disable=unused-argument +# allowed_audiences=None, issuer=None, facebook_app_id=None, # pylint: disable=unused-argument +# facebook_app_secret=None, facebook_oauth_scopes=None, # pylint: disable=unused-argument +# twitter_consumer_key=None, twitter_consumer_secret=None, # pylint: disable=unused-argument +# google_client_id=None, google_client_secret=None, # pylint: disable=unused-argument +# google_oauth_scopes=None, microsoft_account_client_id=None, # pylint: disable=unused-argument +# microsoft_account_client_secret=None, # pylint: disable=unused-argument +# microsoft_account_oauth_scopes=None, slot=None): # pylint: disable=unused-argument +# # auth_settings = get_auth_settings(cmd, resource_group_name, name, slot) - import inspect - frame = inspect.currentframe() - bool_flags = ['enabled', 'token_store_enabled'] - # note: getargvalues is used already in azure.cli.core.commands. - # and no simple functional replacement for this deprecating method for 3.5 - args, _, _, values = inspect.getargvalues(frame) # pylint: disable=deprecated-method +# import inspect +# frame = inspect.currentframe() +# bool_flags = ['enabled', 'token_store_enabled'] +# # note: getargvalues is used already in azure.cli.core.commands. +# # and no simple functional replacement for this deprecating method for 3.5 +# args, _, _, values = inspect.getargvalues(frame) # pylint: disable=deprecated-method - for arg in args[2:]: - if values.get(arg, None): - setattr(auth_settings, arg, values[arg] if arg not in bool_flags else values[arg] == 'true') +# for arg in args[2:]: +# if values.get(arg, None): +# setattr(auth_settings, arg, values[arg] if arg not in bool_flags else values[arg] == 'true') - # return _generic_site_operation(cmd.cli_ctx, resource_group_name, name, 'update_auth_settings', slot, auth_settings) +# # return _generic_site_operation(cmd.cli_ctx, resource_group_name, name, 'update_auth_settings', slot, auth_settings) -def create_apple_config(cmd, enabled, client_id, client_secret_name, scopes): - from azure.mgmt.appcontainers.models import Apple, AppleRegistration, LoginScopes - apple_config = Apple(enabled=enabled, registration=AppleRegistration(client_id=client_id, client_secret_setting_name=client_secret_name), login=LoginScopes(scopes=scopes)) - return apple_config +# def create_apple_config(cmd, enabled, client_id, client_secret_name, scopes): +# from azure.mgmt.appcontainers.models import Apple, AppleRegistration, LoginScopes +# apple_config = Apple(enabled=enabled, registration=AppleRegistration(client_id=client_id, client_secret_setting_name=client_secret_name), login=LoginScopes(scopes=scopes)) +# return apple_config -def create_facebook_config(cmd, enabled, app_id, app_secret_name, graph_api_version, scopes): - from azure.mgmt.appcontainers.models import Facebook, AppRegistration, LoginScopes - facebook_config = Facebook(enabled=enabled, registration=AppRegistration(app_id=app_id, app_secret_setting_name=app_secret_name), graph_api_version=graph_api_version, login=LoginScopes(scopes=scopes)) - return facebook_config +# def create_facebook_config(cmd, enabled, app_id, app_secret_name, graph_api_version, scopes): +# from azure.mgmt.appcontainers.models import Facebook, AppRegistration, LoginScopes +# facebook_config = Facebook(enabled=enabled, registration=AppRegistration(app_id=app_id, app_secret_setting_name=app_secret_name), graph_api_version=graph_api_version, login=LoginScopes(scopes=scopes)) +# return facebook_config -def create_twitter_config(cmd, enabled, consumer_key, consumer_secret_name): - from azure.mgmt.appcontainers.models import Twitter, TwitterRegistration - twitter_config = Twitter(enabled=enabled, registration=TwitterRegistration(consumer_key=consumer_key, consumer_secret_setting_name=consumer_secret_name)) - return twitter_config +# def create_twitter_config(cmd, enabled, consumer_key, consumer_secret_name): +# from azure.mgmt.appcontainers.models import Twitter, TwitterRegistration +# twitter_config = Twitter(enabled=enabled, registration=TwitterRegistration(consumer_key=consumer_key, consumer_secret_setting_name=consumer_secret_name)) +# return twitter_config -def create_google_config(cmd, enabled, client_id, client_secret_name, scopes, allowed_audiences): - from azure.mgmt.appcontainers.models import Google, ClientRegistration, LoginScopes, AllowedAudiencesValidation - google_config = Google(enabled=enabled, registration=ClientRegistration(client_id=client_id, client_secret_setting_name=client_secret_name), login=LoginScopes(scopes=scopes), validation=AllowedAudiencesValidation(allowed_audiences=allowed_audiences)) - return google_config +# def create_google_config(cmd, enabled, client_id, client_secret_name, scopes, allowed_audiences): +# from azure.mgmt.appcontainers.models import Google, ClientRegistration, LoginScopes, AllowedAudiencesValidation +# google_config = Google(enabled=enabled, registration=ClientRegistration(client_id=client_id, client_secret_setting_name=client_secret_name), login=LoginScopes(scopes=scopes), validation=AllowedAudiencesValidation(allowed_audiences=allowed_audiences)) +# return google_config -def create_github_config(cmd, enabled, client_id, client_secret_name, scopes): - from azure.mgmt.appcontainers.models import GitHub, ClientRegistration, LoginScopes - github_config = GitHub(enabled=enabled, registration=ClientRegistration(client_id=client_id, client_secret_setting_name=client_secret_name), login=LoginScopes(scopes=scopes)) - return github_config +# def create_github_config(cmd, enabled, client_id, client_secret_name, scopes): +# from azure.mgmt.appcontainers.models import GitHub, ClientRegistration, LoginScopes +# github_config = GitHub(enabled=enabled, registration=ClientRegistration(client_id=client_id, client_secret_setting_name=client_secret_name), login=LoginScopes(scopes=scopes)) +# return github_config -def create_azure_static_webapps_config(cmd, enabled, client_id): - from azure.mgmt.appcontainers.models import AzureStaticWebApps, AzureStaticWebAppsRegistration - aswa_config = AzureStaticWebApps(enabled=enabled, registration=AzureStaticWebAppsRegistration(client_id=client_id)) - return aswa_config +# def create_azure_static_webapps_config(cmd, enabled, client_id): +# from azure.mgmt.appcontainers.models import AzureStaticWebApps, AzureStaticWebAppsRegistration +# aswa_config = AzureStaticWebApps(enabled=enabled, registration=AzureStaticWebAppsRegistration(client_id=client_id)) +# return aswa_config -def create_open_id_connect_provider_config(enabled, client_id, method, client_secret_name, authorization_endpoint, token_endpoint, issuer, certification_uri, well_known_open_id_configuration, name_claim_type, scopes): - from azure.mgmt.appcontainers.models import CustomOpenIdConnectProvider, OpenIdConnectRegistration, OpenIdConnectClientCredential, OpenIdConnectConfig, OpenIdConnectLogin - coicp_config = CustomOpenIdConnectProvider(enabled=enabled, registration=OpenIdConnectRegistration(client_id=client_id, client_credential=OpenIdConnectClientCredential(method=method, client_secret_setting_name=client_secret_name),open_id_connect_configuration=OpenIdConnectConfig(authorization_endpoint=authorization_endpoint, token_endpoint=token_endpoint, issuer=issuer, certification_uri=certification_uri, well_known_open_id_configuration=well_known_open_id_configuration)), login=OpenIdConnectLogin(name_claim_type=name_claim_type, scopes=scopes)) - return coicp_config +# def create_open_id_connect_provider_config(enabled, client_id, method, client_secret_name, authorization_endpoint, token_endpoint, issuer, certification_uri, well_known_open_id_configuration, name_claim_type, scopes): +# from azure.mgmt.appcontainers.models import CustomOpenIdConnectProvider, OpenIdConnectRegistration, OpenIdConnectClientCredential, OpenIdConnectConfig, OpenIdConnectLogin +# coicp_config = CustomOpenIdConnectProvider(enabled=enabled, registration=OpenIdConnectRegistration(client_id=client_id, client_credential=OpenIdConnectClientCredential(method=method, client_secret_setting_name=client_secret_name),open_id_connect_configuration=OpenIdConnectConfig(authorization_endpoint=authorization_endpoint, token_endpoint=token_endpoint, issuer=issuer, certification_uri=certification_uri, well_known_open_id_configuration=well_known_open_id_configuration)), login=OpenIdConnectLogin(name_claim_type=name_claim_type, scopes=scopes)) +# return coicp_config def update_aad_settings(cmd, client, resource_group_name, name, # pylint: disable=unused-argument @@ -2470,9 +2472,6 @@ def update_aad_settings(cmd, client, resource_group_name, name, # pylint: disabl client_secret_certificate_san=None, # pylint: disable=unused-argument client_secret_certificate_issuer=None, # pylint: disable=unused-argument yes=False, tenant_id=None): # pylint: disable=unused-argument - MICROSOFT_SECRET_SETTING_NAME = "microsoft-provider-authentication-secret" - from knack.prompting import prompt_y_n - from azure.mgmt.appcontainers.models import AuthConfig try: show_ingress(cmd, name, resource_group_name) @@ -2514,15 +2513,14 @@ def update_aad_settings(cmd, client, resource_group_name, name, # pylint: disabl is_new_aad_app = False existing_auth = {} - try: existing_auth = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] except: - existing_auth["properties"] = {} - existing_auth["properties"]["platform"] = {} - existing_auth["properties"]["platform"]["enabled"] = True - existing_auth["properties"]["globalValidation"] = {} - existing_auth["properties"]["login"] = {} + existing_auth = {} + existing_auth["platform"] = {} + existing_auth["platform"]["enabled"] = True + existing_auth["globalValidation"] = {} + existing_auth["login"] = {} registration = {} validation = {} @@ -2614,7 +2612,8 @@ def update_aad_settings(cmd, client, resource_group_name, name, # pylint: disabl existing_auth["identityProviders"]["azureActiveDirectory"]["registration"] = registration updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize() - return updated_auth_settings + + return updated_auth_settings["properties"]["identityProviders"]["azureActiveDirectory"] def get_aad_settings(cmd, client, resource_group_name, name): @@ -2624,3 +2623,481 @@ def get_aad_settings(cmd, client, resource_group_name, name): if "azureActiveDirectory" not in auth_settings["identityProviders"]: return {} return auth_settings["identityProviders"]["azureActiveDirectory"] + + +def get_facebook_settings(cmd, client, resource_group_name, name): + auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + if "identityProviders" not in auth_settings(): + return {} + if "facebook" not in auth_settings["identityProviders"]: + return {} + return auth_settings["identityProviders"]["facebook"] + + +def update_facebook_settings(cmd, client, resource_group_name, name, # pylint: disable=unused-argument + app_id=None, app_secret_setting_name=None, # pylint: disable=unused-argument + graph_api_version=None, scopes=None, app_secret=None, yes=False): # pylint: disable=unused-argument + if app_secret is not None and app_secret_setting_name is not None: + raise CLIError('Usage Error: --app-secret and --app-secret-setting-name cannot both be configured ' + 'to non empty strings') + + if app_secret is not None and not yes: + msg = 'Configuring --app-secret will add app settings to the web app. Are you sure you want to continue?' + if not prompt_y_n(msg, default="n"): + raise CLIError('Usage Error: --app-secret cannot be used without agreeing to add app ' + 'settings to the web app.') + + existing_auth = {} + try: + existing_auth = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + except: + existing_auth = {} + existing_auth["platform"] = {} + existing_auth["platform"]["enabled"] = True + existing_auth["globalValidation"] = {} + existing_auth["login"] = {} + + registration = {} + if "identityProviders" not in existing_auth: + existing_auth["identityProviders"] = {} + if "facebook" not in existing_auth["identityProviders"]: + existing_auth["identityProviders"]["facebook"] = {} + if app_id is not None or app_secret is not None or app_secret_setting_name is not None: + if "registration" not in existing_auth["identityProviders"]["facebook"]: + existing_auth["identityProviders"]["facebook"]["registration"] = {} + registration = existing_auth["identityProviders"]["facebook"]["registration"] + if scopes is not None: + if "login" not in existing_auth["identityProviders"]["facebook"]: + existing_auth["identityProviders"]["facebook"]["login"] = {} + + if app_id is not None: + registration["appId"] = app_id + if app_secret_setting_name is not None: + registration["appSecretSettingName"] = app_secret_setting_name + if app_secret is not None: + registration["appSecretSettingName"] = FACEBOOK_SECRET_SETTING_NAME + set_secrets(cmd, name, resource_group_name, secrets=[f"{FACEBOOK_SECRET_SETTING_NAME}={app_secret}"], no_wait=True) + if graph_api_version is not None: + existing_auth["identityProviders"]["facebook"]["graphApiVersion"] = graph_api_version + if scopes is not None: + existing_auth["identityProviders"]["facebook"]["login"]["scopes"] = scopes.split(",") + if app_id is not None or app_secret is not None or app_secret_setting_name is not None: + existing_auth["identityProviders"]["facebook"]["registration"] = registration + + updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize() + return updated_auth_settings["identityProviders"]["facebook"] + + +def get_github_settings(cmd, client, resource_group_name, name): + auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + if "identityProviders" not in auth_settings: + return {} + if "gitHub" not in auth_settings["identityProviders"]: + return {} + return auth_settings["identityProviders"]["gitHub"] + + +def update_github_settings(cmd, client, resource_group_name, name, # pylint: disable=unused-argument + client_id=None, client_secret_setting_name=None, # pylint: disable=unused-argument + scopes=None, client_secret=None, yes=False): # pylint: disable=unused-argument + if client_secret is not None and client_secret_setting_name is not None: + raise CLIError('Usage Error: --client-secret and --client-secret-setting-name cannot ' + 'both be configured to non empty strings') + + if client_secret is not None and not yes: + msg = 'Configuring --client-secret will add app settings to the web app. Are you sure you want to continue?' + if not prompt_y_n(msg, default="n"): + raise CLIError('Usage Error: --client-secret cannot be used without agreeing to add ' + 'app settings to the web app.') + + existing_auth = {} + try: + existing_auth = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + except: + existing_auth = {} + existing_auth["platform"] = {} + existing_auth["platform"]["enabled"] = True + existing_auth["globalValidation"] = {} + existing_auth["login"] = {} + + registration = {} + if "identityProviders" not in existing_auth: + existing_auth["identityProviders"] = {} + if "gitHub" not in existing_auth["identityProviders"]: + existing_auth["identityProviders"]["gitHub"] = {} + if client_id is not None or client_secret is not None or client_secret_setting_name is not None: + if "registration" not in existing_auth["identityProviders"]["gitHub"]: + existing_auth["identityProviders"]["gitHub"]["registration"] = {} + registration = existing_auth["identityProviders"]["gitHub"]["registration"] + if scopes is not None: + if "login" not in existing_auth["identityProviders"]["gitHub"]: + existing_auth["identityProviders"]["gitHub"]["login"] = {} + + if client_id is not None: + registration["clientId"] = client_id + if client_secret_setting_name is not None: + registration["clientSecretSettingName"] = client_secret_setting_name + if client_secret is not None: + registration["clientSecretSettingName"] = GITHUB_SECRET_SETTING_NAME + set_secrets(cmd, name, resource_group_name, secrets=[f"{GITHUB_SECRET_SETTING_NAME}={client_secret}"], no_wait=True) + if scopes is not None: + existing_auth["identityProviders"]["gitHub"]["login"]["scopes"] = scopes.split(",") + if client_id is not None or client_secret is not None or client_secret_setting_name is not None: + existing_auth["identityProviders"]["gitHub"]["registration"] = registration + + updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize() + return updated_auth_settings["identityProviders"]["gitHub"] +# endregion + +# region webapp auth google + + +def get_google_settings(cmd, client, resource_group_name, name): + auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + if "identityProviders" not in auth_settings: + return {} + if "google" not in auth_settings["identityProviders"]: + return {} + return auth_settings["identityProviders"]["google"] + + +def update_google_settings(cmd, client, resource_group_name, name, # pylint: disable=unused-argument + client_id=None, client_secret_setting_name=None, # pylint: disable=unused-argument + scopes=None, allowed_token_audiences=None, client_secret=None, yes=False): # pylint: disable=unused-argument + if client_secret is not None and client_secret_setting_name is not None: + raise CLIError('Usage Error: --client-secret and --client-secret-setting-name cannot ' + 'both be configured to non empty strings') + + if client_secret is not None and not yes: + msg = 'Configuring --client-secret will add app settings to the web app. Are you sure you want to continue?' + if not prompt_y_n(msg, default="n"): + raise CLIError('Usage Error: --client-secret cannot be used without agreeing to add ' + 'app settings to the web app.') + + existing_auth = {} + try: + existing_auth = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + except: + existing_auth = {} + existing_auth["platform"] = {} + existing_auth["platform"]["enabled"] = True + existing_auth["globalValidation"] = {} + existing_auth["login"] = {} + + registration = {} + validation = {} + if "identityProviders" not in existing_auth: + existing_auth["identityProviders"] = {} + if "google" not in existing_auth["identityProviders"]: + existing_auth["identityProviders"]["google"] = {} + if client_id is not None or client_secret is not None or client_secret_setting_name is not None: + if "registration" not in existing_auth["identityProviders"]["google"]: + existing_auth["identityProviders"]["google"]["registration"] = {} + registration = existing_auth["identityProviders"]["google"]["registration"] + if scopes is not None: + if "login" not in existing_auth["identityProviders"]["google"]: + existing_auth["identityProviders"]["google"]["login"] = {} + if allowed_token_audiences is not None: + if "validation" not in existing_auth["identityProviders"]["google"]: + existing_auth["identityProviders"]["google"]["validation"] = {} + + if client_id is not None: + registration["clientId"] = client_id + if client_secret_setting_name is not None: + registration["clientSecretSettingName"] = client_secret_setting_name + if client_secret is not None: + registration["clientSecretSettingName"] = GOOGLE_SECRET_SETTING_NAME + set_secrets(cmd, name, resource_group_name, secrets=[f"{GOOGLE_SECRET_SETTING_NAME}={client_secret}"], no_wait=True) + if scopes is not None: + existing_auth["identityProviders"]["google"]["login"]["scopes"] = scopes.split(",") + if allowed_token_audiences is not None: + validation["allowedAudiences"] = allowed_token_audiences.split(",") + existing_auth["identityProviders"]["google"]["validation"] = validation + if client_id is not None or client_secret is not None or client_secret_setting_name is not None: + existing_auth["identityProviders"]["google"]["registration"] = registration + + updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize() + return updated_auth_settings["identityProviders"]["google"] +# endregion + +# region webapp auth twitter + + +def get_twitter_settings(cmd, client, resource_group_name, name): + auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + if "identityProviders" not in auth_settings: + return {} + if "twitter" not in auth_settings["identityProviders"]: + return {} + return auth_settings["identityProviders"]["twitter"] + + +def update_twitter_settings(cmd, client, resource_group_name, name, # pylint: disable=unused-argument + consumer_key=None, consumer_secret_setting_name=None, # pylint: disable=unused-argument + consumer_secret=None, yes=False): # pylint: disable=unused-argument + if consumer_secret is not None and consumer_secret_setting_name is not None: + raise CLIError('Usage Error: --consumer-secret and --consumer-secret-setting-name cannot ' + 'both be configured to non empty strings') + + if consumer_secret is not None and not yes: + msg = 'Configuring --consumer-secret will add app settings to the web app. Are you sure you want to continue?' + if not prompt_y_n(msg, default="n"): + raise CLIError('Usage Error: --consumer-secret cannot be used without agreeing ' + 'to add app settings to the web app.') + + existing_auth = {} + try: + existing_auth = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + except: + existing_auth = {} + existing_auth["platform"] = {} + existing_auth["platform"]["enabled"] = True + existing_auth["globalValidation"] = {} + existing_auth["login"] = {} + + registration = {} + if "identityProviders" not in existing_auth: + existing_auth["identityProviders"] = {} + if "twitter" not in existing_auth["identityProviders"]: + existing_auth["identityProviders"]["twitter"] = {} + if consumer_key is not None or consumer_secret is not None or consumer_secret_setting_name is not None: + if "registration" not in existing_auth["identityProviders"]["twitter"]: + existing_auth["identityProviders"]["twitter"]["registration"] = {} + registration = existing_auth["identityProviders"]["twitter"]["registration"] + + if consumer_key is not None: + registration["consumerKey"] = consumer_key + if consumer_secret_setting_name is not None: + registration["consumerSecretSettingName"] = consumer_secret_setting_name + if consumer_secret is not None: + registration["consumerSecretSettingName"] = TWITTER_SECRET_SETTING_NAME + set_secrets(cmd, name, resource_group_name, secrets=[f"{TWITTER_SECRET_SETTING_NAME}={consumer_secret}"], no_wait=True) + if consumer_key is not None or consumer_secret is not None or consumer_secret_setting_name is not None: + existing_auth["identityProviders"]["twitter"]["registration"] = registration + updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize() + return updated_auth_settings["identityProviders"]["twitter"] +# endregion + +# region webapp auth apple + + +def get_apple_settings(cmd, client, resource_group_name, name): + auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + if "identityProviders" not in auth_settings: + return {} + if "apple" not in auth_settings["identityProviders"]: + return {} + return auth_settings["identityProviders"]["apple"] + + +def update_apple_settings(cmd, client, resource_group_name, name, # pylint: disable=unused-argument + client_id=None, client_secret_setting_name=None, # pylint: disable=unused-argument + scopes=None, client_secret=None, yes=False): # pylint: disable=unused-argument + if client_secret is not None and client_secret_setting_name is not None: + raise CLIError('Usage Error: --client-secret and --client-secret-setting-name ' + 'cannot both be configured to non empty strings') + + if client_secret is not None and not yes: + msg = 'Configuring --client-secret will add app settings to the web app. ' \ + 'Are you sure you want to continue?' + if not prompt_y_n(msg, default="n"): + raise CLIError('Usage Error: --client-secret cannot be used without agreeing ' + 'to add app settings to the web app.') + + existing_auth = {} + try: + existing_auth = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + except: + existing_auth = {} + existing_auth["platform"] = {} + existing_auth["platform"]["enabled"] = True + existing_auth["globalValidation"] = {} + existing_auth["login"] = {} + + registration = {} + if "identityProviders" not in existing_auth: + existing_auth["identityProviders"] = {} + if "apple" not in existing_auth["identityProviders"]: + existing_auth["identityProviders"]["apple"] = {} + if client_id is not None or client_secret is not None or client_secret_setting_name is not None: + if "registration" not in existing_auth["identityProviders"]["apple"]: + existing_auth["identityProviders"]["apple"]["registration"] = {} + registration = existing_auth["identityProviders"]["apple"]["registration"] + if scopes is not None: + if "login" not in existing_auth["identityProviders"]["apple"]: + existing_auth["identityProviders"]["apple"]["login"] = {} + + if client_id is not None: + registration["clientId"] = client_id + if client_secret_setting_name is not None: + registration["clientSecretSettingName"] = client_secret_setting_name + if client_secret is not None: + registration["clientSecretSettingName"] = APPLE_SECRET_SETTING_NAME + set_secrets(cmd, name, resource_group_name, secrets=[f"{APPLE_SECRET_SETTING_NAME}={client_secret}"], no_wait=True) + if scopes is not None: + existing_auth["identityProviders"]["apple"]["login"]["scopes"] = scopes.split(",") + if client_id is not None or client_secret is not None or client_secret_setting_name is not None: + existing_auth["identityProviders"]["apple"]["registration"] = registration + + updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize() + return updated_auth_settings["identityProviders"]["apple"] +# endregion + +# region webapp auth openid-connect + +def get_openid_connect_provider_settings(cmd, client, resource_group_name, name, provider_name): # pylint: disable=unused-argument + auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + if "identityProviders" not in auth_settings: + raise CLIError('Usage Error: The following custom OpenID Connect provider ' + 'has not been configured: ' + provider_name) + if "customOpenIdConnectProviders" not in auth_settings["identityProviders"]: + raise CLIError('Usage Error: The following custom OpenID Connect provider ' + 'has not been configured: ' + provider_name) + if provider_name not in auth_settings["identityProviders"]["customOpenIdConnectProviders"]: + raise CLIError('Usage Error: The following custom OpenID Connect provider ' + 'has not been configured: ' + provider_name) + return auth_settings["identityProviders"]["customOpenIdConnectProviders"][provider_name] + + +def add_openid_connect_provider_settings(cmd, client, resource_group_name, name, provider_name, # pylint: disable=unused-argument + client_id=None, client_secret_setting_name=None, # pylint: disable=unused-argument + openid_configuration=None, scopes=None, # pylint: disable=unused-argument + client_secret=None, yes=False): # pylint: disable=unused-argument + if client_secret is not None and not yes: + msg = 'Configuring --client-secret will add app settings to the web app. ' \ + 'Are you sure you want to continue?' + if not prompt_y_n(msg, default="n"): + raise CLIError('Usage Error: --client-secret cannot be used without agreeing ' + 'to add app settings to the web app.') + + auth_settings = {} + try: + auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + except: + auth_settings = {} + auth_settings["platform"] = {} + auth_settings["platform"]["enabled"] = True + auth_settings["globalValidation"] = {} + auth_settings["login"] = {} + if "identityProviders" not in auth_settings: + auth_settings["identityProviders"] = {} + if "customOpenIdConnectProviders" not in auth_settings["identityProviders"]: + auth_settings["identityProviders"]["customOpenIdConnectProviders"] = {} + if provider_name in auth_settings["identityProviders"]["customOpenIdConnectProviders"]: + raise CLIError('Usage Error: The following custom OpenID Connect provider has already been ' + 'configured: ' + provider_name + '. Please use az webapp auth oidc update to ' + 'update the provider.') + + final_client_secret_setting_name = client_secret_setting_name + if client_secret is not None: + final_client_secret_setting_name = get_oidc_client_setting_app_setting_name(provider_name) + set_secrets(cmd, name, resource_group_name, secrets=[f"{final_client_secret_setting_name}={client_secret}"], no_wait=True) + + auth_settings["identityProviders"]["customOpenIdConnectProviders"][provider_name] = { + "registration": { + "clientId": client_id, + "clientCredential": { + "clientSecretSettingName": final_client_secret_setting_name + }, + "openIdConnectConfiguration": { + "wellKnownOpenIdConfiguration": openid_configuration + } + } + } + login = {} + if scopes is not None: + login["scopes"] = scopes.split(',') + else: + login["scopes"] = ["openid"] + + auth_settings["identityProviders"]["customOpenIdConnectProviders"][provider_name]["login"] = login + + updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=auth_settings).serialize() + return updated_auth_settings["identityProviders"]["customOpenIdConnectProviders"][provider_name] + + +def update_openid_connect_provider_settings(cmd, client, resource_group_name, name, provider_name, # pylint: disable=unused-argument + client_id=None, client_secret_setting_name=None, # pylint: disable=unused-argument + openid_configuration=None, scopes=None, # pylint: disable=unused-argument + client_secret=None, yes=False): # pylint: disable=unused-argument + if client_secret is not None and not yes: + msg = 'Configuring --client-secret will add app settings to the web app. ' \ + 'Are you sure you want to continue?' + if not prompt_y_n(msg, default="n"): + raise CLIError('Usage Error: --client-secret cannot be used without agreeing ' + 'to add app settings to the web app.') + + auth_settings = get_auth_settings_v2(cmd, resource_group_name, name, slot)["properties"] + if "identityProviders" not in auth_settings: + raise CLIError('Usage Error: The following custom OpenID Connect provider ' + 'has not been configured: ' + provider_name) + if "customOpenIdConnectProviders" not in auth_settings["identityProviders"]: + raise CLIError('Usage Error: The following custom OpenID Connect provider ' + 'has not been configured: ' + provider_name) + if provider_name not in auth_settings["identityProviders"]["customOpenIdConnectProviders"]: + raise CLIError('Usage Error: The following custom OpenID Connect provider ' + 'has not been configured: ' + provider_name) + + custom_open_id_connect_providers = auth_settings["identityProviders"]["customOpenIdConnectProviders"] + registration = {} + if client_id is not None or client_secret_setting_name is not None or openid_configuration is not None: + if "registration" not in custom_open_id_connect_providers[provider_name]: + custom_open_id_connect_providers[provider_name]["registration"] = {} + registration = custom_open_id_connect_providers[provider_name]["registration"] + + if client_secret_setting_name is not None or client_secret is not None: + if "clientCredential" not in custom_open_id_connect_providers[provider_name]["registration"]: + custom_open_id_connect_providers[provider_name]["registration"]["clientCredential"] = {} + + if openid_configuration is not None: + if "openIdConnectConfiguration" not in custom_open_id_connect_providers[provider_name]["registration"]: + custom_open_id_connect_providers[provider_name]["registration"]["openIdConnectConfiguration"] = {} + + if scopes is not None: + if "login" not in auth_settings["identityProviders"]["customOpenIdConnectProviders"][provider_name]: + custom_open_id_connect_providers[provider_name]["login"] = {} + + if client_id is not None: + registration["clientId"] = client_id + if client_secret_setting_name is not None: + registration["clientCredential"]["clientSecretSettingName"] = client_secret_setting_name + if client_secret is not None: + final_client_secret_setting_name = get_oidc_client_setting_app_setting_name(provider_name) + registration["clientSecretSettingName"] = final_client_secret_setting_name + set_secrets(cmd, name, resource_group_name, secrets=[f"{final_client_secret_setting_name}={client_secret}"], no_wait=True) + if openid_configuration is not None: + registration["openIdConnectConfiguration"]["wellKnownOpenIdConfiguration"] = openid_configuration + if scopes is not None: + custom_open_id_connect_providers[provider_name]["login"]["scopes"] = scopes.split(",") + if client_id is not None or client_secret_setting_name is not None or openid_configuration is not None: + custom_open_id_connect_providers[provider_name]["registration"] = registration + auth_settings["identityProviders"]["customOpenIdConnectProviders"] = custom_open_id_connect_providers + + updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=updated_auth_settings).serialize() + return updated_auth_settings["identityProviders"]["customOpenIdConnectProviders"][provider_name] + + +def remove_openid_connect_provider_settings(cmd, client, resource_group_name, name, provider_name): # pylint: disable=unused-argument + auth_settings = get_auth_settings_v2(cmd, resource_group_name, name, slot)["properties"] + if "identityProviders" not in auth_settings: + raise CLIError('Usage Error: The following custom OpenID Connect provider ' + 'has not been configured: ' + provider_name) + if "customOpenIdConnectProviders" not in auth_settings["identityProviders"]: + raise CLIError('Usage Error: The following custom OpenID Connect provider ' + 'has not been configured: ' + provider_name) + if provider_name not in auth_settings["identityProviders"]["customOpenIdConnectProviders"]: + raise CLIError('Usage Error: The following custom OpenID Connect provider ' + 'has not been configured: ' + provider_name) + auth_settings["identityProviders"]["customOpenIdConnectProviders"].pop(provider_name, None) + client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=auth_settings).serialize() + return {} +# endregion + + +def get_oidc_client_setting_app_setting_name(provider_name): + provider_name_prefix = provider_name.upper() + + # an appsetting name can be up to 64 characters, and the suffix _PROVIDER_AUTHENTICATION_SECRET is 31 characters so limitting this to 32 + if len(provider_name_prefix) > 32: + provider_name_prefix = provider_name_prefix[0:31] + return provider_name_prefix + "_PROVIDER_AUTHENTICATION_SECRET" From 522e6ddbddff435f3262bcc57eeb1ef13afc597f Mon Sep 17 00:00:00 2001 From: Haroon Feisal Date: Wed, 18 May 2022 19:20:14 -0400 Subject: [PATCH 03/10] Added param help. --- .../azext_containerapp/_params.py | 29 +++++-- .../azext_containerapp/commands.py | 34 +++++++- src/containerapp/azext_containerapp/custom.py | 85 +++++++++++-------- 3 files changed, 102 insertions(+), 46 deletions(-) diff --git a/src/containerapp/azext_containerapp/_params.py b/src/containerapp/azext_containerapp/_params.py index 58c5b7debc8..1052e6eb6a3 100644 --- a/src/containerapp/azext_containerapp/_params.py +++ b/src/containerapp/azext_containerapp/_params.py @@ -258,13 +258,10 @@ def load_arguments(self, _): c.argument('service_principal_client_secret', help='The service principal client secret. Used by Github Actions to authenticate with Azure.', options_list=["--service-principal-client-secret", "--sp-sec"]) c.argument('service_principal_tenant_id', help='The service principal tenant ID. Used by Github Actions to authenticate with Azure.', options_list=["--service-principal-tenant-id", "--sp-tid"]) - with self.argument_context('containerapp auth microsoft update') as c: - c.argument('client_id', options_list=['--client-id'], - help='The Client ID of this relying party application, known as the client_id.') - c.argument('client_secret', options_list=['--client-secret'], - help='AAD application secret') - c.argument('client_secret_setting_name', options_list=['--client-secret-setting-name', '--secret-setting'], - help='The app setting name that contains the client secret of the relying party application.') + with self.argument_context('containerapp auth') as c: + c.argument('client_id', options_list=['--client-id'], help='The Client ID of the app used for login.') + c.argument('client_secret', options_list=['--client-secret'], help='The client secret.') + c.argument('client_secret_setting_name', options_list=['--client-secret-name'], help='The app setting name that contains the client secret of the relying party application.') c.argument('issuer', options_list=['--issuer'], help='The OpenID Connect Issuer URI that represents the entity which issues access tokens for this application.') c.argument('allowed_token_audiences', options_list=['--allowed-token-audiences', '--allowed-audiences'], @@ -278,3 +275,21 @@ def load_arguments(self, _): c.argument('yes', options_list=['--yes', '-y'], help='Do not prompt for confirmation.', action='store_true') c.argument('tenant_id', options_list=['--tenant-id'], help='The tenant id of the application.') + c.argument('app_id', options_list=['--app-id'], + help='The App ID of the app used for login.') + c.argument('app_secret', options_list=['--app-secret'], + help='The app secret.') + c.argument('app_secret_setting_name', options_list=['--app-secret-setting-name', '--secret-setting'], + help='The app setting name that contains the app secret.') + c.argument('graph_api_version', options_list=['--graph-api-version'], + help='The version of the Facebook api to be used while logging in.') + c.argument('scopes', options_list=['--scopes'], + help='A list of the scopes that should be requested while authenticating.') + c.argument('consumer_key', options_list=['--consumer-key'], + help='The OAuth 1.0a consumer key of the Twitter application used for sign-in.') + c.argument('consumer_secret', options_list=['--consumer-secret'], + help='The consumer secret.') + c.argument('provider_name', options_list=['--provider-name'], required=True, + help='The name of the custom OpenID Connect provider.') + c.argument('openid_configuration', options_list=['--openid-configuration'], + help='The endpoint that contains all the configuration endpoints for the provider.') diff --git a/src/containerapp/azext_containerapp/commands.py b/src/containerapp/azext_containerapp/commands.py index 6ed1efeaf58..08782303d10 100644 --- a/src/containerapp/azext_containerapp/commands.py +++ b/src/containerapp/azext_containerapp/commands.py @@ -133,11 +133,39 @@ def load_command_table(self, _): g.custom_command('enable', 'enable_dapr', exception_handler=ex_handler_factory()) g.custom_command('disable', 'disable_dapr', exception_handler=ex_handler_factory()) - # with self.command_group('containerapp auth', client_factory=auth_config_client_factory) as g: - # g.custom_command('show', 'create_azure_static_webapps_config', exception_handler=ex_handler_factory()) + with self.command_group('containerapp auth', client_factory=auth_config_client_factory) as g: + g.custom_command('delete', 'delete_auth_config', exception_handler=ex_handler_factory()) + g.custom_command('show', 'show_auth_config', exception_handler=ex_handler_factory()) + # g.custom_command('list', 'create_azure_static_webapps_config', exception_handler=ex_handler_factory()) # g.custom_command('disable', 'disable_dapr', exception_handler=ex_handler_factory()) with self.command_group('containerapp auth microsoft', client_factory=auth_config_client_factory) as g: + g.custom_command('show', 'get_aad_settings') g.custom_command('update', 'update_aad_settings', exception_handler=ex_handler_factory()) - g.custom_command('show', 'get_aad_settings', exception_handler=ex_handler_factory()) + + with self.command_group('containerapp auth facebook', client_factory=auth_config_client_factory) as g: + g.custom_show_command('show', 'get_facebook_settings') + g.custom_command('update', 'update_facebook_settings', exception_handler=ex_handler_factory()) + + with self.command_group('containerapp auth github', client_factory=auth_config_client_factory) as g: + g.custom_show_command('show', 'get_github_settings') + g.custom_command('update', 'update_github_settings') + + with self.command_group('containerapp auth google', client_factory=auth_config_client_factory) as g: + g.custom_show_command('show', 'get_google_settings') + g.custom_command('update', 'update_google_settings', exception_handler=ex_handler_factory()) + + with self.command_group('containerapp auth twitter', client_factory=auth_config_client_factory) as g: + g.custom_show_command('show', 'get_twitter_settings') + g.custom_command('update', 'update_twitter_settings', exception_handler=ex_handler_factory()) + + with self.command_group('containerapp auth apple', client_factory=auth_config_client_factory) as g: + g.custom_show_command('show', 'get_apple_settings') + g.custom_command('update', 'update_apple_settings', exception_handler=ex_handler_factory()) + + with self.command_group('containerapp auth openid-connect', client_factory=auth_config_client_factory) as g: + g.custom_show_command('show', 'get_openid_connect_provider_settings') + g.custom_command('add', 'add_openid_connect_provider_settings', exception_handler=ex_handler_factory()) + g.custom_command('update', 'update_openid_connect_provider_settings', exception_handler=ex_handler_factory()) + g.custom_command('remove', 'remove_openid_connect_provider_settings') diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index bb6835a6381..04ac140be87 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -2611,9 +2611,8 @@ def update_aad_settings(cmd, client, resource_group_name, name, # pylint: disabl client_secret_certificate_issuer is not None): existing_auth["identityProviders"]["azureActiveDirectory"]["registration"] = registration - updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize() - - return updated_auth_settings["properties"]["identityProviders"]["azureActiveDirectory"] + updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize()["properties"] + return updated_auth_settings["identityProviders"]["azureActiveDirectory"] def get_aad_settings(cmd, client, resource_group_name, name): @@ -2642,10 +2641,10 @@ def update_facebook_settings(cmd, client, resource_group_name, name, # pylint: 'to non empty strings') if app_secret is not None and not yes: - msg = 'Configuring --app-secret will add app settings to the web app. Are you sure you want to continue?' + msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): - raise CLIError('Usage Error: --app-secret cannot be used without agreeing to add app ' - 'settings to the web app.') + raise CLIError('Usage Error: --client-secret cannot be used without agreeing to add secret ' + 'to the containerapp.') existing_auth = {} try: @@ -2684,7 +2683,7 @@ def update_facebook_settings(cmd, client, resource_group_name, name, # pylint: if app_id is not None or app_secret is not None or app_secret_setting_name is not None: existing_auth["identityProviders"]["facebook"]["registration"] = registration - updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize() + updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize()["properties"] return updated_auth_settings["identityProviders"]["facebook"] @@ -2705,10 +2704,10 @@ def update_github_settings(cmd, client, resource_group_name, name, # pylint: di 'both be configured to non empty strings') if client_secret is not None and not yes: - msg = 'Configuring --client-secret will add app settings to the web app. Are you sure you want to continue?' + msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): - raise CLIError('Usage Error: --client-secret cannot be used without agreeing to add ' - 'app settings to the web app.') + raise CLIError('Usage Error: --client-secret cannot be used without agreeing to add secret ' + 'to the containerapp.') existing_auth = {} try: @@ -2745,7 +2744,7 @@ def update_github_settings(cmd, client, resource_group_name, name, # pylint: di if client_id is not None or client_secret is not None or client_secret_setting_name is not None: existing_auth["identityProviders"]["gitHub"]["registration"] = registration - updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize() + updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize()["properties"] return updated_auth_settings["identityProviders"]["gitHub"] # endregion @@ -2769,10 +2768,10 @@ def update_google_settings(cmd, client, resource_group_name, name, # pylint: di 'both be configured to non empty strings') if client_secret is not None and not yes: - msg = 'Configuring --client-secret will add app settings to the web app. Are you sure you want to continue?' + msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): - raise CLIError('Usage Error: --client-secret cannot be used without agreeing to add ' - 'app settings to the web app.') + raise CLIError('Usage Error: --client-secret cannot be used without agreeing to add secret ' + 'to the containerapp.') existing_auth = {} try: @@ -2816,7 +2815,7 @@ def update_google_settings(cmd, client, resource_group_name, name, # pylint: di if client_id is not None or client_secret is not None or client_secret_setting_name is not None: existing_auth["identityProviders"]["google"]["registration"] = registration - updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize() + updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize()["properties"] return updated_auth_settings["identityProviders"]["google"] # endregion @@ -2840,10 +2839,10 @@ def update_twitter_settings(cmd, client, resource_group_name, name, # pylint: d 'both be configured to non empty strings') if consumer_secret is not None and not yes: - msg = 'Configuring --consumer-secret will add app settings to the web app. Are you sure you want to continue?' + msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): - raise CLIError('Usage Error: --consumer-secret cannot be used without agreeing ' - 'to add app settings to the web app.') + raise CLIError('Usage Error: --client-secret cannot be used without agreeing to add secret ' + 'to the containerapp.') existing_auth = {} try: @@ -2874,7 +2873,7 @@ def update_twitter_settings(cmd, client, resource_group_name, name, # pylint: d set_secrets(cmd, name, resource_group_name, secrets=[f"{TWITTER_SECRET_SETTING_NAME}={consumer_secret}"], no_wait=True) if consumer_key is not None or consumer_secret is not None or consumer_secret_setting_name is not None: existing_auth["identityProviders"]["twitter"]["registration"] = registration - updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize() + updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize()["properties"] return updated_auth_settings["identityProviders"]["twitter"] # endregion @@ -2898,11 +2897,10 @@ def update_apple_settings(cmd, client, resource_group_name, name, # pylint: dis 'cannot both be configured to non empty strings') if client_secret is not None and not yes: - msg = 'Configuring --client-secret will add app settings to the web app. ' \ - 'Are you sure you want to continue?' + msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): - raise CLIError('Usage Error: --client-secret cannot be used without agreeing ' - 'to add app settings to the web app.') + raise CLIError('Usage Error: --client-secret cannot be used without agreeing to add secret ' + 'to the containerapp.') existing_auth = {} try: @@ -2939,7 +2937,7 @@ def update_apple_settings(cmd, client, resource_group_name, name, # pylint: dis if client_id is not None or client_secret is not None or client_secret_setting_name is not None: existing_auth["identityProviders"]["apple"]["registration"] = registration - updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize() + updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize()["properties"] return updated_auth_settings["identityProviders"]["apple"] # endregion @@ -2964,11 +2962,10 @@ def add_openid_connect_provider_settings(cmd, client, resource_group_name, name, openid_configuration=None, scopes=None, # pylint: disable=unused-argument client_secret=None, yes=False): # pylint: disable=unused-argument if client_secret is not None and not yes: - msg = 'Configuring --client-secret will add app settings to the web app. ' \ - 'Are you sure you want to continue?' + msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): - raise CLIError('Usage Error: --client-secret cannot be used without agreeing ' - 'to add app settings to the web app.') + raise CLIError('Usage Error: --client-secret cannot be used without agreeing to add secret ' + 'to the containerapp.') auth_settings = {} try: @@ -2979,6 +2976,7 @@ def add_openid_connect_provider_settings(cmd, client, resource_group_name, name, auth_settings["platform"]["enabled"] = True auth_settings["globalValidation"] = {} auth_settings["login"] = {} + if "identityProviders" not in auth_settings: auth_settings["identityProviders"] = {} if "customOpenIdConnectProviders" not in auth_settings["identityProviders"]: @@ -3012,7 +3010,7 @@ def add_openid_connect_provider_settings(cmd, client, resource_group_name, name, auth_settings["identityProviders"]["customOpenIdConnectProviders"][provider_name]["login"] = login - updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=auth_settings).serialize() + updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=auth_settings).serialize()["properties"] return updated_auth_settings["identityProviders"]["customOpenIdConnectProviders"][provider_name] @@ -3021,13 +3019,21 @@ def update_openid_connect_provider_settings(cmd, client, resource_group_name, na openid_configuration=None, scopes=None, # pylint: disable=unused-argument client_secret=None, yes=False): # pylint: disable=unused-argument if client_secret is not None and not yes: - msg = 'Configuring --client-secret will add app settings to the web app. ' \ - 'Are you sure you want to continue?' + msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): - raise CLIError('Usage Error: --client-secret cannot be used without agreeing ' - 'to add app settings to the web app.') + raise CLIError('Usage Error: --client-secret cannot be used without agreeing to add secret ' + 'to the containerapp.') + + auth_settings = {} + try: + auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + except: + auth_settings = {} + auth_settings["platform"] = {} + auth_settings["platform"]["enabled"] = True + auth_settings["globalValidation"] = {} + auth_settings["login"] = {} - auth_settings = get_auth_settings_v2(cmd, resource_group_name, name, slot)["properties"] if "identityProviders" not in auth_settings: raise CLIError('Usage Error: The following custom OpenID Connect provider ' 'has not been configured: ' + provider_name) @@ -3073,12 +3079,12 @@ def update_openid_connect_provider_settings(cmd, client, resource_group_name, na custom_open_id_connect_providers[provider_name]["registration"] = registration auth_settings["identityProviders"]["customOpenIdConnectProviders"] = custom_open_id_connect_providers - updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=updated_auth_settings).serialize() + updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=updated_auth_settings).serialize()["properties"] return updated_auth_settings["identityProviders"]["customOpenIdConnectProviders"][provider_name] def remove_openid_connect_provider_settings(cmd, client, resource_group_name, name, provider_name): # pylint: disable=unused-argument - auth_settings = get_auth_settings_v2(cmd, resource_group_name, name, slot)["properties"] + auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] if "identityProviders" not in auth_settings: raise CLIError('Usage Error: The following custom OpenID Connect provider ' 'has not been configured: ' + provider_name) @@ -3101,3 +3107,10 @@ def get_oidc_client_setting_app_setting_name(provider_name): if len(provider_name_prefix) > 32: provider_name_prefix = provider_name_prefix[0:31] return provider_name_prefix + "_PROVIDER_AUTHENTICATION_SECRET" + + +def delete_auth_config(client, name, resource_group_name): + return client.delete(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current") + +def show_auth_config(client, name, resource_group_name): + return client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current") From 9be335b1a05a890f5562fa1d18ccd83d951c55dd Mon Sep 17 00:00:00 2001 From: Haroon Feisal Date: Wed, 18 May 2022 19:30:36 -0400 Subject: [PATCH 04/10] Added error handling for no easy auth case. --- src/containerapp/azext_containerapp/custom.py | 159 ++++++++---------- 1 file changed, 70 insertions(+), 89 deletions(-) diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index 04ac140be87..872d6767f81 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -2395,76 +2395,6 @@ def remove_storage(cmd, storage_name, name, resource_group_name, no_wait=False): handle_raw_exception(e) -# def update_auth_settings(cmd, resource_group_name, name, auth_name, enabled=None, action=None, # pylint: disable=unused-argument -# client_id=None, token_store_enabled=None, runtime_version=None, # pylint: disable=unused-argument -# token_refresh_extension_hours=None, # pylint: disable=unused-argument -# allowed_external_redirect_urls=None, client_secret=None, # pylint: disable=unused-argument -# client_secret_certificate_thumbprint=None, # pylint: disable=unused-argument -# allowed_audiences=None, issuer=None, facebook_app_id=None, # pylint: disable=unused-argument -# facebook_app_secret=None, facebook_oauth_scopes=None, # pylint: disable=unused-argument -# twitter_consumer_key=None, twitter_consumer_secret=None, # pylint: disable=unused-argument -# google_client_id=None, google_client_secret=None, # pylint: disable=unused-argument -# google_oauth_scopes=None, microsoft_account_client_id=None, # pylint: disable=unused-argument -# microsoft_account_client_secret=None, # pylint: disable=unused-argument -# microsoft_account_oauth_scopes=None, slot=None): # pylint: disable=unused-argument -# # auth_settings = get_auth_settings(cmd, resource_group_name, name, slot) - -# import inspect -# frame = inspect.currentframe() -# bool_flags = ['enabled', 'token_store_enabled'] -# # note: getargvalues is used already in azure.cli.core.commands. -# # and no simple functional replacement for this deprecating method for 3.5 -# args, _, _, values = inspect.getargvalues(frame) # pylint: disable=deprecated-method - -# for arg in args[2:]: -# if values.get(arg, None): -# setattr(auth_settings, arg, values[arg] if arg not in bool_flags else values[arg] == 'true') - -# # return _generic_site_operation(cmd.cli_ctx, resource_group_name, name, 'update_auth_settings', slot, auth_settings) - - -# def create_apple_config(cmd, enabled, client_id, client_secret_name, scopes): -# from azure.mgmt.appcontainers.models import Apple, AppleRegistration, LoginScopes -# apple_config = Apple(enabled=enabled, registration=AppleRegistration(client_id=client_id, client_secret_setting_name=client_secret_name), login=LoginScopes(scopes=scopes)) -# return apple_config - - -# def create_facebook_config(cmd, enabled, app_id, app_secret_name, graph_api_version, scopes): -# from azure.mgmt.appcontainers.models import Facebook, AppRegistration, LoginScopes -# facebook_config = Facebook(enabled=enabled, registration=AppRegistration(app_id=app_id, app_secret_setting_name=app_secret_name), graph_api_version=graph_api_version, login=LoginScopes(scopes=scopes)) -# return facebook_config - - -# def create_twitter_config(cmd, enabled, consumer_key, consumer_secret_name): -# from azure.mgmt.appcontainers.models import Twitter, TwitterRegistration -# twitter_config = Twitter(enabled=enabled, registration=TwitterRegistration(consumer_key=consumer_key, consumer_secret_setting_name=consumer_secret_name)) -# return twitter_config - - -# def create_google_config(cmd, enabled, client_id, client_secret_name, scopes, allowed_audiences): -# from azure.mgmt.appcontainers.models import Google, ClientRegistration, LoginScopes, AllowedAudiencesValidation -# google_config = Google(enabled=enabled, registration=ClientRegistration(client_id=client_id, client_secret_setting_name=client_secret_name), login=LoginScopes(scopes=scopes), validation=AllowedAudiencesValidation(allowed_audiences=allowed_audiences)) -# return google_config - - -# def create_github_config(cmd, enabled, client_id, client_secret_name, scopes): -# from azure.mgmt.appcontainers.models import GitHub, ClientRegistration, LoginScopes -# github_config = GitHub(enabled=enabled, registration=ClientRegistration(client_id=client_id, client_secret_setting_name=client_secret_name), login=LoginScopes(scopes=scopes)) -# return github_config - - -# def create_azure_static_webapps_config(cmd, enabled, client_id): -# from azure.mgmt.appcontainers.models import AzureStaticWebApps, AzureStaticWebAppsRegistration -# aswa_config = AzureStaticWebApps(enabled=enabled, registration=AzureStaticWebAppsRegistration(client_id=client_id)) -# return aswa_config - - -# def create_open_id_connect_provider_config(enabled, client_id, method, client_secret_name, authorization_endpoint, token_endpoint, issuer, certification_uri, well_known_open_id_configuration, name_claim_type, scopes): -# from azure.mgmt.appcontainers.models import CustomOpenIdConnectProvider, OpenIdConnectRegistration, OpenIdConnectClientCredential, OpenIdConnectConfig, OpenIdConnectLogin -# coicp_config = CustomOpenIdConnectProvider(enabled=enabled, registration=OpenIdConnectRegistration(client_id=client_id, client_credential=OpenIdConnectClientCredential(method=method, client_secret_setting_name=client_secret_name),open_id_connect_configuration=OpenIdConnectConfig(authorization_endpoint=authorization_endpoint, token_endpoint=token_endpoint, issuer=issuer, certification_uri=certification_uri, well_known_open_id_configuration=well_known_open_id_configuration)), login=OpenIdConnectLogin(name_claim_type=name_claim_type, scopes=scopes)) -# return coicp_config - - def update_aad_settings(cmd, client, resource_group_name, name, # pylint: disable=unused-argument client_id=None, client_secret_setting_name=None, # pylint: disable=unused-argument issuer=None, allowed_token_audiences=None, client_secret=None, # pylint: disable=unused-argument @@ -2616,7 +2546,11 @@ def update_aad_settings(cmd, client, resource_group_name, name, # pylint: disabl def get_aad_settings(cmd, client, resource_group_name, name): - auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + auth_settings = {} + try: + auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + except: + pass if "identityProviders" not in auth_settings: return {} if "azureActiveDirectory" not in auth_settings["identityProviders"]: @@ -2625,7 +2559,11 @@ def get_aad_settings(cmd, client, resource_group_name, name): def get_facebook_settings(cmd, client, resource_group_name, name): - auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + auth_settings = {} + try: + auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + except: + pass if "identityProviders" not in auth_settings(): return {} if "facebook" not in auth_settings["identityProviders"]: @@ -2636,6 +2574,11 @@ def get_facebook_settings(cmd, client, resource_group_name, name): def update_facebook_settings(cmd, client, resource_group_name, name, # pylint: disable=unused-argument app_id=None, app_secret_setting_name=None, # pylint: disable=unused-argument graph_api_version=None, scopes=None, app_secret=None, yes=False): # pylint: disable=unused-argument + try: + show_ingress(cmd, name, resource_group_name) + except: + raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") + if app_secret is not None and app_secret_setting_name is not None: raise CLIError('Usage Error: --app-secret and --app-secret-setting-name cannot both be configured ' 'to non empty strings') @@ -2688,7 +2631,11 @@ def update_facebook_settings(cmd, client, resource_group_name, name, # pylint: def get_github_settings(cmd, client, resource_group_name, name): - auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + auth_settings = {} + try: + auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + except: + pass if "identityProviders" not in auth_settings: return {} if "gitHub" not in auth_settings["identityProviders"]: @@ -2699,6 +2646,11 @@ def get_github_settings(cmd, client, resource_group_name, name): def update_github_settings(cmd, client, resource_group_name, name, # pylint: disable=unused-argument client_id=None, client_secret_setting_name=None, # pylint: disable=unused-argument scopes=None, client_secret=None, yes=False): # pylint: disable=unused-argument + try: + show_ingress(cmd, name, resource_group_name) + except: + raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") + if client_secret is not None and client_secret_setting_name is not None: raise CLIError('Usage Error: --client-secret and --client-secret-setting-name cannot ' 'both be configured to non empty strings') @@ -2746,13 +2698,14 @@ def update_github_settings(cmd, client, resource_group_name, name, # pylint: di updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize()["properties"] return updated_auth_settings["identityProviders"]["gitHub"] -# endregion - -# region webapp auth google def get_google_settings(cmd, client, resource_group_name, name): - auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + auth_settings = {} + try: + auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + except: + pass if "identityProviders" not in auth_settings: return {} if "google" not in auth_settings["identityProviders"]: @@ -2763,6 +2716,11 @@ def get_google_settings(cmd, client, resource_group_name, name): def update_google_settings(cmd, client, resource_group_name, name, # pylint: disable=unused-argument client_id=None, client_secret_setting_name=None, # pylint: disable=unused-argument scopes=None, allowed_token_audiences=None, client_secret=None, yes=False): # pylint: disable=unused-argument + try: + show_ingress(cmd, name, resource_group_name) + except: + raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") + if client_secret is not None and client_secret_setting_name is not None: raise CLIError('Usage Error: --client-secret and --client-secret-setting-name cannot ' 'both be configured to non empty strings') @@ -2817,13 +2775,14 @@ def update_google_settings(cmd, client, resource_group_name, name, # pylint: di updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize()["properties"] return updated_auth_settings["identityProviders"]["google"] -# endregion - -# region webapp auth twitter def get_twitter_settings(cmd, client, resource_group_name, name): - auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + auth_settings = {} + try: + auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + except: + pass if "identityProviders" not in auth_settings: return {} if "twitter" not in auth_settings["identityProviders"]: @@ -2834,6 +2793,11 @@ def get_twitter_settings(cmd, client, resource_group_name, name): def update_twitter_settings(cmd, client, resource_group_name, name, # pylint: disable=unused-argument consumer_key=None, consumer_secret_setting_name=None, # pylint: disable=unused-argument consumer_secret=None, yes=False): # pylint: disable=unused-argument + try: + show_ingress(cmd, name, resource_group_name) + except: + raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") + if consumer_secret is not None and consumer_secret_setting_name is not None: raise CLIError('Usage Error: --consumer-secret and --consumer-secret-setting-name cannot ' 'both be configured to non empty strings') @@ -2875,13 +2839,14 @@ def update_twitter_settings(cmd, client, resource_group_name, name, # pylint: d existing_auth["identityProviders"]["twitter"]["registration"] = registration updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize()["properties"] return updated_auth_settings["identityProviders"]["twitter"] -# endregion - -# region webapp auth apple def get_apple_settings(cmd, client, resource_group_name, name): - auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + auth_settings = {} + try: + auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + except: + pass if "identityProviders" not in auth_settings: return {} if "apple" not in auth_settings["identityProviders"]: @@ -2892,6 +2857,11 @@ def get_apple_settings(cmd, client, resource_group_name, name): def update_apple_settings(cmd, client, resource_group_name, name, # pylint: disable=unused-argument client_id=None, client_secret_setting_name=None, # pylint: disable=unused-argument scopes=None, client_secret=None, yes=False): # pylint: disable=unused-argument + try: + show_ingress(cmd, name, resource_group_name) + except: + raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") + if client_secret is not None and client_secret_setting_name is not None: raise CLIError('Usage Error: --client-secret and --client-secret-setting-name ' 'cannot both be configured to non empty strings') @@ -2939,12 +2909,14 @@ def update_apple_settings(cmd, client, resource_group_name, name, # pylint: dis updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize()["properties"] return updated_auth_settings["identityProviders"]["apple"] -# endregion -# region webapp auth openid-connect def get_openid_connect_provider_settings(cmd, client, resource_group_name, name, provider_name): # pylint: disable=unused-argument - auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + auth_settings = {} + try: + auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + except: + pass if "identityProviders" not in auth_settings: raise CLIError('Usage Error: The following custom OpenID Connect provider ' 'has not been configured: ' + provider_name) @@ -2961,6 +2933,11 @@ def add_openid_connect_provider_settings(cmd, client, resource_group_name, name, client_id=None, client_secret_setting_name=None, # pylint: disable=unused-argument openid_configuration=None, scopes=None, # pylint: disable=unused-argument client_secret=None, yes=False): # pylint: disable=unused-argument + try: + show_ingress(cmd, name, resource_group_name) + except: + raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") + if client_secret is not None and not yes: msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): @@ -3018,6 +2995,11 @@ def update_openid_connect_provider_settings(cmd, client, resource_group_name, na client_id=None, client_secret_setting_name=None, # pylint: disable=unused-argument openid_configuration=None, scopes=None, # pylint: disable=unused-argument client_secret=None, yes=False): # pylint: disable=unused-argument + try: + show_ingress(cmd, name, resource_group_name) + except: + raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") + if client_secret is not None and not yes: msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): @@ -3097,7 +3079,6 @@ def remove_openid_connect_provider_settings(cmd, client, resource_group_name, na auth_settings["identityProviders"]["customOpenIdConnectProviders"].pop(provider_name, None) client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=auth_settings).serialize() return {} -# endregion def get_oidc_client_setting_app_setting_name(provider_name): From 59369f744807fbedbbe732e04b59d0b7e46c8fce Mon Sep 17 00:00:00 2001 From: Haroon Feisal Date: Thu, 19 May 2022 15:03:54 -0400 Subject: [PATCH 05/10] Added auth update and show. --- .../azext_containerapp/_constants.py | 4 +- .../azext_containerapp/_params.py | 58 ++++---- .../azext_containerapp/commands.py | 2 +- src/containerapp/azext_containerapp/custom.py | 138 +++++++++++++++++- 4 files changed, 162 insertions(+), 40 deletions(-) diff --git a/src/containerapp/azext_containerapp/_constants.py b/src/containerapp/azext_containerapp/_constants.py index 9035db14672..5f8612a5982 100644 --- a/src/containerapp/azext_containerapp/_constants.py +++ b/src/containerapp/azext_containerapp/_constants.py @@ -20,5 +20,5 @@ MSA_SECRET_SETTING_NAME = "MSA_PROVIDER_AUTHENTICATION_SECRET".lower().replace("_", "-") TWITTER_SECRET_SETTING_NAME = "TWITTER_PROVIDER_AUTHENTICATION_SECRET".lower().replace("_", "-") APPLE_SECRET_SETTING_NAME = "APPLE_PROVIDER_AUTHENTICATION_SECRET".lower().replace("_", "-") -TRUE_STRING = "true" -FALSE_STRING = "false" \ No newline at end of file +UNAUTHENTICATED_CLIENT_ACTION = ['RedirectToLoginPage', 'AllowAnonymous', 'RejectWith401', 'RejectWith404'] +FORWARD_PROXY_CONVENTION = ['NoProxy', 'Standard', 'Custom'] diff --git a/src/containerapp/azext_containerapp/_params.py b/src/containerapp/azext_containerapp/_params.py index 1052e6eb6a3..7ec15647bc6 100644 --- a/src/containerapp/azext_containerapp/_params.py +++ b/src/containerapp/azext_containerapp/_params.py @@ -13,7 +13,7 @@ from ._validators import (validate_memory, validate_cpu, validate_managed_env_name_or_id, validate_registry_server, validate_registry_user, validate_registry_pass, validate_target_port, validate_ingress) - +from ._constants import UNAUTHENTICATED_CLIENT_ACTION, FORWARD_PROXY_CONVENTION def load_arguments(self, _): @@ -262,34 +262,30 @@ def load_arguments(self, _): c.argument('client_id', options_list=['--client-id'], help='The Client ID of the app used for login.') c.argument('client_secret', options_list=['--client-secret'], help='The client secret.') c.argument('client_secret_setting_name', options_list=['--client-secret-name'], help='The app setting name that contains the client secret of the relying party application.') - c.argument('issuer', options_list=['--issuer'], - help='The OpenID Connect Issuer URI that represents the entity which issues access tokens for this application.') - c.argument('allowed_token_audiences', options_list=['--allowed-token-audiences', '--allowed-audiences'], - help='The configuration settings of the allowed list of audiences from which to validate the JWT token.') - c.argument('client_secret_certificate_thumbprint', options_list=['--thumbprint', '--client-secret-certificate-thumbprint'], - help='Alternative to AAD Client Secret, thumbprint of a certificate used for signing purposes') - c.argument('client_secret_certificate_san', options_list=['--san', '--client-secret-certificate-san'], - help='Alternative to AAD Client Secret and thumbprint, subject alternative name of a certificate used for signing purposes') - c.argument('client_secret_certificate_issuer', options_list=['--certificate-issuer', '--client-secret-certificate-issuer'], - help='Alternative to AAD Client Secret and thumbprint, issuer of a certificate used for signing purposes') + c.argument('issuer', options_list=['--issuer'], help='The OpenID Connect Issuer URI that represents the entity which issues access tokens for this application.') + c.argument('allowed_token_audiences', options_list=['--allowed-token-audiences', '--allowed-audiences'], help='The configuration settings of the allowed list of audiences from which to validate the JWT token.') + c.argument('client_secret_certificate_thumbprint', options_list=['--thumbprint', '--client-secret-certificate-thumbprint'], help='Alternative to AAD Client Secret, thumbprint of a certificate used for signing purposes') + c.argument('client_secret_certificate_san', options_list=['--san', '--client-secret-certificate-san'], help='Alternative to AAD Client Secret and thumbprint, subject alternative name of a certificate used for signing purposes') + c.argument('client_secret_certificate_issuer', options_list=['--certificate-issuer', '--client-secret-certificate-issuer'], help='Alternative to AAD Client Secret and thumbprint, issuer of a certificate used for signing purposes') c.argument('yes', options_list=['--yes', '-y'], help='Do not prompt for confirmation.', action='store_true') - c.argument('tenant_id', options_list=['--tenant-id'], - help='The tenant id of the application.') - c.argument('app_id', options_list=['--app-id'], - help='The App ID of the app used for login.') - c.argument('app_secret', options_list=['--app-secret'], - help='The app secret.') - c.argument('app_secret_setting_name', options_list=['--app-secret-setting-name', '--secret-setting'], - help='The app setting name that contains the app secret.') - c.argument('graph_api_version', options_list=['--graph-api-version'], - help='The version of the Facebook api to be used while logging in.') - c.argument('scopes', options_list=['--scopes'], - help='A list of the scopes that should be requested while authenticating.') - c.argument('consumer_key', options_list=['--consumer-key'], - help='The OAuth 1.0a consumer key of the Twitter application used for sign-in.') - c.argument('consumer_secret', options_list=['--consumer-secret'], - help='The consumer secret.') - c.argument('provider_name', options_list=['--provider-name'], required=True, - help='The name of the custom OpenID Connect provider.') - c.argument('openid_configuration', options_list=['--openid-configuration'], - help='The endpoint that contains all the configuration endpoints for the provider.') + c.argument('tenant_id', options_list=['--tenant-id'], help='The tenant id of the application.') + c.argument('app_id', options_list=['--app-id'], help='The App ID of the app used for login.') + c.argument('app_secret', options_list=['--app-secret'], help='The app secret.') + c.argument('app_secret_setting_name', options_list=['--app-secret-setting-name', '--secret-setting'], help='The app setting name that contains the app secret.') + c.argument('graph_api_version', options_list=['--graph-api-version'], help='The version of the Facebook api to be used while logging in.') + c.argument('scopes', options_list=['--scopes'], help='A list of the scopes that should be requested while authenticating.') + c.argument('consumer_key', options_list=['--consumer-key'], help='The OAuth 1.0a consumer key of the Twitter application used for sign-in.') + c.argument('consumer_secret', options_list=['--consumer-secret'], help='The consumer secret.') + c.argument('provider_name', options_list=['--provider-name'], required=True, help='The name of the custom OpenID Connect provider.') + c.argument('openid_configuration', options_list=['--openid-configuration'], help='The endpoint that contains all the configuration endpoints for the provider.') + c.argument('set_string', options_list=['--set'], help='Value of a specific field within the configuration settings for the Azure App Service Authentication / Authorization V2 feature.') + c.argument('config_file_path', options_list=['--config-file-path'], help='The path of the config file containing auth settings if they come from a file.') + c.argument('unauthenticated_client_action', options_list=['--unauthenticated-client-action', '--action'], arg_type=get_enum_type(UNAUTHENTICATED_CLIENT_ACTION), help='The action to take when an unauthenticated client attempts to access the app.') + c.argument('redirect_provider', options_list=['--redirect-provider'], help='The default authentication provider to use when multiple providers are configured.') + c.argument('enable_token_store', options_list=['--enable-token-store'], arg_type=get_three_state_flag(return_label=True), help='true to durably store platform-specific security tokens that are obtained during login flows; otherwise, false.') + c.argument('require_https', options_list=['--require-https'], arg_type=get_three_state_flag(return_label=True), help='false if the authentication/authorization responses not having the HTTPS scheme are permissible; otherwise, true.') + c.argument('proxy_convention', options_list=['--proxy-convention'], arg_type=get_enum_type(FORWARD_PROXY_CONVENTION), help='The convention used to determine the url of the request made.') + c.argument('proxy_custom_host_header', options_list=['--proxy-custom-host-header', '--custom-host-header'], help='The name of the header containing the host of the request.') + c.argument('proxy_custom_proto_header', options_list=['--proxy-custom-proto-header', '--custom-proto-header'], help='The name of the header containing the scheme of the request.') + c.argument('excluded_paths', options_list=['--excluded-paths'], help='The list of paths that should be excluded from authentication rules.') + c.argument('enabled', options_list=['--enabled'], arg_type=get_three_state_flag(return_label=True), help='true if the Authentication / Authorization feature is enabled for the current app; otherwise, false.') diff --git a/src/containerapp/azext_containerapp/commands.py b/src/containerapp/azext_containerapp/commands.py index 08782303d10..048d857217f 100644 --- a/src/containerapp/azext_containerapp/commands.py +++ b/src/containerapp/azext_containerapp/commands.py @@ -134,8 +134,8 @@ def load_command_table(self, _): g.custom_command('disable', 'disable_dapr', exception_handler=ex_handler_factory()) with self.command_group('containerapp auth', client_factory=auth_config_client_factory) as g: - g.custom_command('delete', 'delete_auth_config', exception_handler=ex_handler_factory()) g.custom_command('show', 'show_auth_config', exception_handler=ex_handler_factory()) + g.custom_command('update', 'update_auth_config', exception_handler=ex_handler_factory()) # g.custom_command('list', 'create_azure_static_webapps_config', exception_handler=ex_handler_factory()) # g.custom_command('disable', 'disable_dapr', exception_handler=ex_handler_factory()) diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index 872d6767f81..eaacddfffc7 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -2960,7 +2960,7 @@ def add_openid_connect_provider_settings(cmd, client, resource_group_name, name, auth_settings["identityProviders"]["customOpenIdConnectProviders"] = {} if provider_name in auth_settings["identityProviders"]["customOpenIdConnectProviders"]: raise CLIError('Usage Error: The following custom OpenID Connect provider has already been ' - 'configured: ' + provider_name + '. Please use az webapp auth oidc update to ' + 'configured: ' + provider_name + '. Please use `az containerapp auth oidc update` to ' 'update the provider.') final_client_secret_setting_name = client_secret_setting_name @@ -3066,7 +3066,11 @@ def update_openid_connect_provider_settings(cmd, client, resource_group_name, na def remove_openid_connect_provider_settings(cmd, client, resource_group_name, name, provider_name): # pylint: disable=unused-argument - auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + auth_settings = {} + try: + auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + except: + pass if "identityProviders" not in auth_settings: raise CLIError('Usage Error: The following custom OpenID Connect provider ' 'has not been configured: ' + provider_name) @@ -3090,8 +3094,130 @@ def get_oidc_client_setting_app_setting_name(provider_name): return provider_name_prefix + "_PROVIDER_AUTHENTICATION_SECRET" -def delete_auth_config(client, name, resource_group_name): - return client.delete(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current") +def update_auth_config(cmd, client, resource_group_name, name, set_string=None, enabled=None, # pylint: disable=unused-argument + runtime_version=None, config_file_path=None, unauthenticated_client_action=None, # pylint: disable=unused-argument + redirect_provider=None, enable_token_store=None, require_https=None, # pylint: disable=unused-argument + proxy_convention=None, proxy_custom_host_header=None, # pylint: disable=unused-argument + proxy_custom_proto_header=None, excluded_paths=None, slot=None): # pylint: disable=unused-argument + existing_auth = {} + try: + existing_auth = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + except: + existing_auth["platform"] = {} + existing_auth["platform"]["enabled"] = True + existing_auth["globalValidation"] = {} + existing_auth["login"] = {} + + existing_auth = set_field_in_auth_settings(existing_auth, set_string) + + if enabled is not None: + if "platform" not in existing_auth: + existing_auth["platform"] = {} + existing_auth["platform"]["enabled"] = enabled + + if runtime_version is not None: + if "platform" not in existing_auth: + existing_auth["platform"] = {} + existing_auth["platform"]["runtimeVersion"] = runtime_version + + if config_file_path is not None: + if "platform" not in existing_auth: + existing_auth["platform"] = {} + existing_auth["platform"]["configFilePath"] = config_file_path -def show_auth_config(client, name, resource_group_name): - return client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current") + if unauthenticated_client_action is not None: + if "globalValidation" not in existing_auth: + existing_auth["globalValidation"] = {} + existing_auth["globalValidation"]["unauthenticatedClientAction"] = unauthenticated_client_action + + if redirect_provider is not None: + if "globalValidation" not in existing_auth: + existing_auth["globalValidation"] = {} + existing_auth["globalValidation"]["redirectToProvider"] = redirect_provider + + if enable_token_store is not None: + if "login" not in existing_auth: + existing_auth["login"] = {} + if "tokenStore" not in existing_auth["login"]: + existing_auth["login"]["tokenStore"] = {} + existing_auth["login"]["tokenStore"]["enabled"] = enable_token_store + + if excluded_paths is not None: + if "globalValidation" not in existing_auth: + existing_auth["globalValidation"] = {} + excluded_paths_list_string = excluded_paths[1:-1] + existing_auth["globalValidation"]["excludedPaths"] = excluded_paths_list_string.split(",") + + existing_auth = update_http_settings_in_auth_settings(existing_auth, require_https, + proxy_convention, proxy_custom_host_header, + proxy_custom_proto_header) + + return client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize() + + +def show_auth_config(cmd, client, resource_group_name, name): # pylint: disable=unused-argument + auth_settings = {} + try: + auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] + except: + pass + return auth_settings + + +def set_field_in_auth_settings(auth_settings, set_string): + if set_string is not None: + split1 = set_string.split("=") + fieldName = split1[0] + fieldValue = split1[1] + split2 = fieldName.split(".") + auth_settings = set_field_in_auth_settings_recursive(split2, fieldValue, auth_settings) + return auth_settings + + +def set_field_in_auth_settings_recursive(field_name_split, field_value, auth_settings): + if len(field_name_split) == 1: + if not field_value.startswith('[') or not field_value.endswith(']'): + auth_settings[field_name_split[0]] = field_value + else: + field_value_list_string = field_value[1:-1] + auth_settings[field_name_split[0]] = field_value_list_string.split(",") + return auth_settings + + remaining_field_names = field_name_split[1:] + if field_name_split[0] not in auth_settings: + auth_settings[field_name_split[0]] = {} + auth_settings[field_name_split[0]] = set_field_in_auth_settings_recursive(remaining_field_names, + field_value, + auth_settings[field_name_split[0]]) + return auth_settings + + +def update_http_settings_in_auth_settings(auth_settings, require_https, proxy_convention, + proxy_custom_host_header, proxy_custom_proto_header): + if require_https is not None: + if "httpSettings" not in auth_settings: + auth_settings["httpSettings"] = {} + auth_settings["httpSettings"]["requireHttps"] = require_https + + if proxy_convention is not None: + if "httpSettings" not in auth_settings: + auth_settings["httpSettings"] = {} + if "forwardProxy" not in auth_settings["httpSettings"]: + auth_settings["httpSettings"]["forwardProxy"] = {} + auth_settings["httpSettings"]["forwardProxy"]["convention"] = proxy_convention + + if proxy_custom_host_header is not None: + if "httpSettings" not in auth_settings: + auth_settings["httpSettings"] = {} + if "forwardProxy" not in auth_settings["httpSettings"]: + auth_settings["httpSettings"]["forwardProxy"] = {} + auth_settings["httpSettings"]["forwardProxy"]["customHostHeaderName"] = proxy_custom_host_header + + if proxy_custom_proto_header is not None: + if "httpSettings" not in auth_settings: + auth_settings["httpSettings"] = {} + if "forwardProxy" not in auth_settings["httpSettings"]: + auth_settings["httpSettings"]["forwardProxy"] = {} + auth_settings["httpSettings"]["forwardProxy"]["customProtoHeaderName"] = proxy_custom_proto_header + + return auth_settings From 1185d6954816509187e07468d0cac301a7948445 Mon Sep 17 00:00:00 2001 From: Haroon Feisal Date: Thu, 19 May 2022 15:23:17 -0400 Subject: [PATCH 06/10] Moved utils to utils. Removed CLIErrors from old code. Removed comments in commands. --- src/containerapp/azext_containerapp/_utils.py | 68 ++++++++++ .../azext_containerapp/commands.py | 3 - src/containerapp/azext_containerapp/custom.py | 119 ++++-------------- 3 files changed, 95 insertions(+), 95 deletions(-) diff --git a/src/containerapp/azext_containerapp/_utils.py b/src/containerapp/azext_containerapp/_utils.py index 441a630da41..4c75bd975fa 100644 --- a/src/containerapp/azext_containerapp/_utils.py +++ b/src/containerapp/azext_containerapp/_utils.py @@ -1176,3 +1176,71 @@ def create_new_acr(cmd, registry_name, resource_group_name, location=None, sku=" lro_poller = client.begin_create(resource_group_name, registry_name, registry) acr = LongRunningOperation(cmd.cli_ctx)(lro_poller) return acr + + +def set_field_in_auth_settings(auth_settings, set_string): + if set_string is not None: + split1 = set_string.split("=") + fieldName = split1[0] + fieldValue = split1[1] + split2 = fieldName.split(".") + auth_settings = set_field_in_auth_settings_recursive(split2, fieldValue, auth_settings) + return auth_settings + + +def set_field_in_auth_settings_recursive(field_name_split, field_value, auth_settings): + if len(field_name_split) == 1: + if not field_value.startswith('[') or not field_value.endswith(']'): + auth_settings[field_name_split[0]] = field_value + else: + field_value_list_string = field_value[1:-1] + auth_settings[field_name_split[0]] = field_value_list_string.split(",") + return auth_settings + + remaining_field_names = field_name_split[1:] + if field_name_split[0] not in auth_settings: + auth_settings[field_name_split[0]] = {} + auth_settings[field_name_split[0]] = set_field_in_auth_settings_recursive(remaining_field_names, + field_value, + auth_settings[field_name_split[0]]) + return auth_settings + + +def update_http_settings_in_auth_settings(auth_settings, require_https, proxy_convention, + proxy_custom_host_header, proxy_custom_proto_header): + if require_https is not None: + if "httpSettings" not in auth_settings: + auth_settings["httpSettings"] = {} + auth_settings["httpSettings"]["requireHttps"] = require_https + + if proxy_convention is not None: + if "httpSettings" not in auth_settings: + auth_settings["httpSettings"] = {} + if "forwardProxy" not in auth_settings["httpSettings"]: + auth_settings["httpSettings"]["forwardProxy"] = {} + auth_settings["httpSettings"]["forwardProxy"]["convention"] = proxy_convention + + if proxy_custom_host_header is not None: + if "httpSettings" not in auth_settings: + auth_settings["httpSettings"] = {} + if "forwardProxy" not in auth_settings["httpSettings"]: + auth_settings["httpSettings"]["forwardProxy"] = {} + auth_settings["httpSettings"]["forwardProxy"]["customHostHeaderName"] = proxy_custom_host_header + + if proxy_custom_proto_header is not None: + if "httpSettings" not in auth_settings: + auth_settings["httpSettings"] = {} + if "forwardProxy" not in auth_settings["httpSettings"]: + auth_settings["httpSettings"]["forwardProxy"] = {} + auth_settings["httpSettings"]["forwardProxy"]["customProtoHeaderName"] = proxy_custom_proto_header + + return auth_settings + + +def get_oidc_client_setting_app_setting_name(provider_name): + provider_name_prefix = provider_name.upper() + + # an appsetting name can be up to 64 characters, and the suffix _PROVIDER_AUTHENTICATION_SECRET is 31 characters so limitting this to 32 + if len(provider_name_prefix) > 32: + provider_name_prefix = provider_name_prefix[0:31] + return provider_name_prefix + "_PROVIDER_AUTHENTICATION_SECRET" diff --git a/src/containerapp/azext_containerapp/commands.py b/src/containerapp/azext_containerapp/commands.py index 048d857217f..e5e2202240e 100644 --- a/src/containerapp/azext_containerapp/commands.py +++ b/src/containerapp/azext_containerapp/commands.py @@ -137,9 +137,6 @@ def load_command_table(self, _): g.custom_command('show', 'show_auth_config', exception_handler=ex_handler_factory()) g.custom_command('update', 'update_auth_config', exception_handler=ex_handler_factory()) - # g.custom_command('list', 'create_azure_static_webapps_config', exception_handler=ex_handler_factory()) - # g.custom_command('disable', 'disable_dapr', exception_handler=ex_handler_factory()) - with self.command_group('containerapp auth microsoft', client_factory=auth_config_client_factory) as g: g.custom_command('show', 'get_aad_settings') g.custom_command('update', 'update_aad_settings', exception_handler=ex_handler_factory()) diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index eaacddfffc7..f6c496c96cd 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -2461,13 +2461,13 @@ def update_aad_settings(cmd, client, resource_group_name, name, # pylint: disabl is_new_aad_app = True if is_new_aad_app and issuer is None and tenant_id is None: - raise CLIError('Usage Error: Either --issuer or --tenant-id must be specified when configuring the ' + raise ArgumentUsageError('Usage Error: Either --issuer or --tenant-id must be specified when configuring the ' 'Microsoft auth registration.') if client_secret is not None and not yes: msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): - raise CLIError('Usage Error: --client-secret cannot be used without agreeing to add secret ' + raise ArgumentUsageError('Usage Error: --client-secret cannot be used without agreeing to add secret ' 'to the containerapp.') openid_issuer = issuer @@ -2580,13 +2580,13 @@ def update_facebook_settings(cmd, client, resource_group_name, name, # pylint: raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") if app_secret is not None and app_secret_setting_name is not None: - raise CLIError('Usage Error: --app-secret and --app-secret-setting-name cannot both be configured ' + raise ArgumentUsageError('Usage Error: --app-secret and --app-secret-setting-name cannot both be configured ' 'to non empty strings') if app_secret is not None and not yes: msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): - raise CLIError('Usage Error: --client-secret cannot be used without agreeing to add secret ' + raise ArgumentUsageError('Usage Error: --client-secret cannot be used without agreeing to add secret ' 'to the containerapp.') existing_auth = {} @@ -2652,13 +2652,13 @@ def update_github_settings(cmd, client, resource_group_name, name, # pylint: di raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") if client_secret is not None and client_secret_setting_name is not None: - raise CLIError('Usage Error: --client-secret and --client-secret-setting-name cannot ' + raise ArgumentUsageError('Usage Error: --client-secret and --client-secret-setting-name cannot ' 'both be configured to non empty strings') if client_secret is not None and not yes: msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): - raise CLIError('Usage Error: --client-secret cannot be used without agreeing to add secret ' + raise ArgumentUsageError('Usage Error: --client-secret cannot be used without agreeing to add secret ' 'to the containerapp.') existing_auth = {} @@ -2722,13 +2722,13 @@ def update_google_settings(cmd, client, resource_group_name, name, # pylint: di raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") if client_secret is not None and client_secret_setting_name is not None: - raise CLIError('Usage Error: --client-secret and --client-secret-setting-name cannot ' + raise ArgumentUsageError('Usage Error: --client-secret and --client-secret-setting-name cannot ' 'both be configured to non empty strings') if client_secret is not None and not yes: msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): - raise CLIError('Usage Error: --client-secret cannot be used without agreeing to add secret ' + raise ArgumentUsageError('Usage Error: --client-secret cannot be used without agreeing to add secret ' 'to the containerapp.') existing_auth = {} @@ -2799,13 +2799,13 @@ def update_twitter_settings(cmd, client, resource_group_name, name, # pylint: d raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") if consumer_secret is not None and consumer_secret_setting_name is not None: - raise CLIError('Usage Error: --consumer-secret and --consumer-secret-setting-name cannot ' + raise ArgumentUsageError('Usage Error: --consumer-secret and --consumer-secret-setting-name cannot ' 'both be configured to non empty strings') if consumer_secret is not None and not yes: msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): - raise CLIError('Usage Error: --client-secret cannot be used without agreeing to add secret ' + raise ArgumentUsageError('Usage Error: --client-secret cannot be used without agreeing to add secret ' 'to the containerapp.') existing_auth = {} @@ -2863,13 +2863,13 @@ def update_apple_settings(cmd, client, resource_group_name, name, # pylint: dis raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") if client_secret is not None and client_secret_setting_name is not None: - raise CLIError('Usage Error: --client-secret and --client-secret-setting-name ' + raise ArgumentUsageError('Usage Error: --client-secret and --client-secret-setting-name ' 'cannot both be configured to non empty strings') if client_secret is not None and not yes: msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): - raise CLIError('Usage Error: --client-secret cannot be used without agreeing to add secret ' + raise ArgumentUsageError('Usage Error: --client-secret cannot be used without agreeing to add secret ' 'to the containerapp.') existing_auth = {} @@ -2918,13 +2918,13 @@ def get_openid_connect_provider_settings(cmd, client, resource_group_name, name, except: pass if "identityProviders" not in auth_settings: - raise CLIError('Usage Error: The following custom OpenID Connect provider ' + raise ArgumentUsageError('Usage Error: The following custom OpenID Connect provider ' 'has not been configured: ' + provider_name) if "customOpenIdConnectProviders" not in auth_settings["identityProviders"]: - raise CLIError('Usage Error: The following custom OpenID Connect provider ' + raise ArgumentUsageError('Usage Error: The following custom OpenID Connect provider ' 'has not been configured: ' + provider_name) if provider_name not in auth_settings["identityProviders"]["customOpenIdConnectProviders"]: - raise CLIError('Usage Error: The following custom OpenID Connect provider ' + raise ArgumentUsageError('Usage Error: The following custom OpenID Connect provider ' 'has not been configured: ' + provider_name) return auth_settings["identityProviders"]["customOpenIdConnectProviders"][provider_name] @@ -2933,6 +2933,7 @@ def add_openid_connect_provider_settings(cmd, client, resource_group_name, name, client_id=None, client_secret_setting_name=None, # pylint: disable=unused-argument openid_configuration=None, scopes=None, # pylint: disable=unused-argument client_secret=None, yes=False): # pylint: disable=unused-argument + from ._utils import get_oidc_client_setting_app_setting_name try: show_ingress(cmd, name, resource_group_name) except: @@ -2941,7 +2942,7 @@ def add_openid_connect_provider_settings(cmd, client, resource_group_name, name, if client_secret is not None and not yes: msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): - raise CLIError('Usage Error: --client-secret cannot be used without agreeing to add secret ' + raise ArgumentUsageError('Usage Error: --client-secret cannot be used without agreeing to add secret ' 'to the containerapp.') auth_settings = {} @@ -2959,7 +2960,7 @@ def add_openid_connect_provider_settings(cmd, client, resource_group_name, name, if "customOpenIdConnectProviders" not in auth_settings["identityProviders"]: auth_settings["identityProviders"]["customOpenIdConnectProviders"] = {} if provider_name in auth_settings["identityProviders"]["customOpenIdConnectProviders"]: - raise CLIError('Usage Error: The following custom OpenID Connect provider has already been ' + raise ArgumentUsageError('Usage Error: The following custom OpenID Connect provider has already been ' 'configured: ' + provider_name + '. Please use `az containerapp auth oidc update` to ' 'update the provider.') @@ -2995,6 +2996,7 @@ def update_openid_connect_provider_settings(cmd, client, resource_group_name, na client_id=None, client_secret_setting_name=None, # pylint: disable=unused-argument openid_configuration=None, scopes=None, # pylint: disable=unused-argument client_secret=None, yes=False): # pylint: disable=unused-argument + from ._utils import get_oidc_client_setting_app_setting_name try: show_ingress(cmd, name, resource_group_name) except: @@ -3003,7 +3005,7 @@ def update_openid_connect_provider_settings(cmd, client, resource_group_name, na if client_secret is not None and not yes: msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): - raise CLIError('Usage Error: --client-secret cannot be used without agreeing to add secret ' + raise ArgumentUsageError('Usage Error: --client-secret cannot be used without agreeing to add secret ' 'to the containerapp.') auth_settings = {} @@ -3017,13 +3019,13 @@ def update_openid_connect_provider_settings(cmd, client, resource_group_name, na auth_settings["login"] = {} if "identityProviders" not in auth_settings: - raise CLIError('Usage Error: The following custom OpenID Connect provider ' + raise ArgumentUsageError('Usage Error: The following custom OpenID Connect provider ' 'has not been configured: ' + provider_name) if "customOpenIdConnectProviders" not in auth_settings["identityProviders"]: - raise CLIError('Usage Error: The following custom OpenID Connect provider ' + raise ArgumentUsageError('Usage Error: The following custom OpenID Connect provider ' 'has not been configured: ' + provider_name) if provider_name not in auth_settings["identityProviders"]["customOpenIdConnectProviders"]: - raise CLIError('Usage Error: The following custom OpenID Connect provider ' + raise ArgumentUsageError('Usage Error: The following custom OpenID Connect provider ' 'has not been configured: ' + provider_name) custom_open_id_connect_providers = auth_settings["identityProviders"]["customOpenIdConnectProviders"] @@ -3072,33 +3074,25 @@ def remove_openid_connect_provider_settings(cmd, client, resource_group_name, na except: pass if "identityProviders" not in auth_settings: - raise CLIError('Usage Error: The following custom OpenID Connect provider ' + raise ArgumentUsageError('Usage Error: The following custom OpenID Connect provider ' 'has not been configured: ' + provider_name) if "customOpenIdConnectProviders" not in auth_settings["identityProviders"]: - raise CLIError('Usage Error: The following custom OpenID Connect provider ' + raise ArgumentUsageError('Usage Error: The following custom OpenID Connect provider ' 'has not been configured: ' + provider_name) if provider_name not in auth_settings["identityProviders"]["customOpenIdConnectProviders"]: - raise CLIError('Usage Error: The following custom OpenID Connect provider ' + raise ArgumentUsageError('Usage Error: The following custom OpenID Connect provider ' 'has not been configured: ' + provider_name) auth_settings["identityProviders"]["customOpenIdConnectProviders"].pop(provider_name, None) client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=auth_settings).serialize() return {} -def get_oidc_client_setting_app_setting_name(provider_name): - provider_name_prefix = provider_name.upper() - - # an appsetting name can be up to 64 characters, and the suffix _PROVIDER_AUTHENTICATION_SECRET is 31 characters so limitting this to 32 - if len(provider_name_prefix) > 32: - provider_name_prefix = provider_name_prefix[0:31] - return provider_name_prefix + "_PROVIDER_AUTHENTICATION_SECRET" - - def update_auth_config(cmd, client, resource_group_name, name, set_string=None, enabled=None, # pylint: disable=unused-argument runtime_version=None, config_file_path=None, unauthenticated_client_action=None, # pylint: disable=unused-argument redirect_provider=None, enable_token_store=None, require_https=None, # pylint: disable=unused-argument proxy_convention=None, proxy_custom_host_header=None, # pylint: disable=unused-argument proxy_custom_proto_header=None, excluded_paths=None, slot=None): # pylint: disable=unused-argument + from ._utils import set_field_in_auth_settings, update_http_settings_in_auth_settings existing_auth = {} try: existing_auth = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] @@ -3162,62 +3156,3 @@ def show_auth_config(cmd, client, resource_group_name, name): # pylint: disable except: pass return auth_settings - - -def set_field_in_auth_settings(auth_settings, set_string): - if set_string is not None: - split1 = set_string.split("=") - fieldName = split1[0] - fieldValue = split1[1] - split2 = fieldName.split(".") - auth_settings = set_field_in_auth_settings_recursive(split2, fieldValue, auth_settings) - return auth_settings - - -def set_field_in_auth_settings_recursive(field_name_split, field_value, auth_settings): - if len(field_name_split) == 1: - if not field_value.startswith('[') or not field_value.endswith(']'): - auth_settings[field_name_split[0]] = field_value - else: - field_value_list_string = field_value[1:-1] - auth_settings[field_name_split[0]] = field_value_list_string.split(",") - return auth_settings - - remaining_field_names = field_name_split[1:] - if field_name_split[0] not in auth_settings: - auth_settings[field_name_split[0]] = {} - auth_settings[field_name_split[0]] = set_field_in_auth_settings_recursive(remaining_field_names, - field_value, - auth_settings[field_name_split[0]]) - return auth_settings - - -def update_http_settings_in_auth_settings(auth_settings, require_https, proxy_convention, - proxy_custom_host_header, proxy_custom_proto_header): - if require_https is not None: - if "httpSettings" not in auth_settings: - auth_settings["httpSettings"] = {} - auth_settings["httpSettings"]["requireHttps"] = require_https - - if proxy_convention is not None: - if "httpSettings" not in auth_settings: - auth_settings["httpSettings"] = {} - if "forwardProxy" not in auth_settings["httpSettings"]: - auth_settings["httpSettings"]["forwardProxy"] = {} - auth_settings["httpSettings"]["forwardProxy"]["convention"] = proxy_convention - - if proxy_custom_host_header is not None: - if "httpSettings" not in auth_settings: - auth_settings["httpSettings"] = {} - if "forwardProxy" not in auth_settings["httpSettings"]: - auth_settings["httpSettings"]["forwardProxy"] = {} - auth_settings["httpSettings"]["forwardProxy"]["customHostHeaderName"] = proxy_custom_host_header - - if proxy_custom_proto_header is not None: - if "httpSettings" not in auth_settings: - auth_settings["httpSettings"] = {} - if "forwardProxy" not in auth_settings["httpSettings"]: - auth_settings["httpSettings"]["forwardProxy"] = {} - auth_settings["httpSettings"]["forwardProxy"]["customProtoHeaderName"] = proxy_custom_proto_header - - return auth_settings From 873e531f811f7926df7e77a58f9fb26942c4634e Mon Sep 17 00:00:00 2001 From: Haroon Feisal Date: Thu, 19 May 2022 16:22:25 -0400 Subject: [PATCH 07/10] Fixed style issues. Fixed sdk version. --- .../azext_containerapp/_params.py | 1 + .../azext_containerapp/commands.py | 4 +- src/containerapp/azext_containerapp/custom.py | 108 +++++++++--------- src/containerapp/setup.py | 2 +- 4 files changed, 59 insertions(+), 56 deletions(-) diff --git a/src/containerapp/azext_containerapp/_params.py b/src/containerapp/azext_containerapp/_params.py index 7ec15647bc6..cbaea4de1db 100644 --- a/src/containerapp/azext_containerapp/_params.py +++ b/src/containerapp/azext_containerapp/_params.py @@ -15,6 +15,7 @@ validate_registry_user, validate_registry_pass, validate_target_port, validate_ingress) from ._constants import UNAUTHENTICATED_CLIENT_ACTION, FORWARD_PROXY_CONVENTION + def load_arguments(self, _): name_type = CLIArgumentType(options_list=['--name', '-n']) diff --git a/src/containerapp/azext_containerapp/commands.py b/src/containerapp/azext_containerapp/commands.py index e5e2202240e..5b869f33d6f 100644 --- a/src/containerapp/azext_containerapp/commands.py +++ b/src/containerapp/azext_containerapp/commands.py @@ -8,7 +8,7 @@ # from msrestazure.tools import is_valid_resource_id, parse_resource_id from azext_containerapp._client_factory import ex_handler_factory from ._validators import validate_ssh -from azure.cli.core.commands import CliCommandType + def transform_containerapp_output(app): props = ['name', 'location', 'resourceGroup', 'provisioningState'] @@ -42,12 +42,14 @@ def transform_revision_output(rev): def transform_revision_list_output(revs): return [transform_revision_output(r) for r in revs] + def auth_config_client_factory(cli_ctx, *_): from azure.cli.core.commands.client_factory import get_mgmt_service_client from azure.cli.core.profiles import CustomResourceType MGMT_APPCONTAINERS = CustomResourceType(import_prefix='azure.mgmt.appcontainers', client_name='ContainerAppsAPIClient') return get_mgmt_service_client(cli_ctx, MGMT_APPCONTAINERS, api_version='2022-03-01').container_apps_auth_configs + def load_command_table(self, _): with self.command_group('containerapp', is_preview=True) as g: diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index f6c496c96cd..120688482b7 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -63,7 +63,7 @@ from ._ssh_utils import (SSH_DEFAULT_ENCODING, WebSocketConnection, read_ssh, get_stdin_writer, SSH_CTRL_C_MSG, SSH_BACKUP_ENCODING) from ._constants import (MAXIMUM_SECRET_LENGTH, MICROSOFT_SECRET_SETTING_NAME, FACEBOOK_SECRET_SETTING_NAME, GITHUB_SECRET_SETTING_NAME, - GOOGLE_SECRET_SETTING_NAME, MSA_SECRET_SETTING_NAME, TWITTER_SECRET_SETTING_NAME, APPLE_SECRET_SETTING_NAME) + GOOGLE_SECRET_SETTING_NAME, TWITTER_SECRET_SETTING_NAME, APPLE_SECRET_SETTING_NAME) logger = get_logger(__name__) @@ -2395,7 +2395,7 @@ def remove_storage(cmd, storage_name, name, resource_group_name, no_wait=False): handle_raw_exception(e) -def update_aad_settings(cmd, client, resource_group_name, name, # pylint: disable=unused-argument +def update_aad_settings(cmd, client, resource_group_name, name, # pylint: disable=unused-argument client_id=None, client_secret_setting_name=None, # pylint: disable=unused-argument issuer=None, allowed_token_audiences=None, client_secret=None, # pylint: disable=unused-argument client_secret_certificate_thumbprint=None, # pylint: disable=unused-argument @@ -2405,8 +2405,8 @@ def update_aad_settings(cmd, client, resource_group_name, name, # pylint: disabl try: show_ingress(cmd, name, resource_group_name) - except: - raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") + except Exception as e: + raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") from e if client_secret is not None and client_secret_setting_name is not None: raise ArgumentUsageError('Usage Error: --client-secret and --client-secret-setting-name cannot both be ' @@ -2462,13 +2462,13 @@ def update_aad_settings(cmd, client, resource_group_name, name, # pylint: disabl if is_new_aad_app and issuer is None and tenant_id is None: raise ArgumentUsageError('Usage Error: Either --issuer or --tenant-id must be specified when configuring the ' - 'Microsoft auth registration.') + 'Microsoft auth registration.') if client_secret is not None and not yes: msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): raise ArgumentUsageError('Usage Error: --client-secret cannot be used without agreeing to add secret ' - 'to the containerapp.') + 'to the containerapp.') openid_issuer = issuer if openid_issuer is None: @@ -2545,7 +2545,7 @@ def update_aad_settings(cmd, client, resource_group_name, name, # pylint: disabl return updated_auth_settings["identityProviders"]["azureActiveDirectory"] -def get_aad_settings(cmd, client, resource_group_name, name): +def get_aad_settings(client, resource_group_name, name): auth_settings = {} try: auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] @@ -2558,7 +2558,7 @@ def get_aad_settings(cmd, client, resource_group_name, name): return auth_settings["identityProviders"]["azureActiveDirectory"] -def get_facebook_settings(cmd, client, resource_group_name, name): +def get_facebook_settings(client, resource_group_name, name): auth_settings = {} try: auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] @@ -2576,18 +2576,18 @@ def update_facebook_settings(cmd, client, resource_group_name, name, # pylint: graph_api_version=None, scopes=None, app_secret=None, yes=False): # pylint: disable=unused-argument try: show_ingress(cmd, name, resource_group_name) - except: - raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") + except Exception as e: + raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") from e if app_secret is not None and app_secret_setting_name is not None: raise ArgumentUsageError('Usage Error: --app-secret and --app-secret-setting-name cannot both be configured ' - 'to non empty strings') + 'to non empty strings') if app_secret is not None and not yes: msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): raise ArgumentUsageError('Usage Error: --client-secret cannot be used without agreeing to add secret ' - 'to the containerapp.') + 'to the containerapp.') existing_auth = {} try: @@ -2630,7 +2630,7 @@ def update_facebook_settings(cmd, client, resource_group_name, name, # pylint: return updated_auth_settings["identityProviders"]["facebook"] -def get_github_settings(cmd, client, resource_group_name, name): +def get_github_settings(client, resource_group_name, name): auth_settings = {} try: auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] @@ -2648,18 +2648,18 @@ def update_github_settings(cmd, client, resource_group_name, name, # pylint: di scopes=None, client_secret=None, yes=False): # pylint: disable=unused-argument try: show_ingress(cmd, name, resource_group_name) - except: - raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") + except Exception as e: + raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") from e if client_secret is not None and client_secret_setting_name is not None: raise ArgumentUsageError('Usage Error: --client-secret and --client-secret-setting-name cannot ' - 'both be configured to non empty strings') + 'both be configured to non empty strings') if client_secret is not None and not yes: msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): raise ArgumentUsageError('Usage Error: --client-secret cannot be used without agreeing to add secret ' - 'to the containerapp.') + 'to the containerapp.') existing_auth = {} try: @@ -2700,7 +2700,7 @@ def update_github_settings(cmd, client, resource_group_name, name, # pylint: di return updated_auth_settings["identityProviders"]["gitHub"] -def get_google_settings(cmd, client, resource_group_name, name): +def get_google_settings(client, resource_group_name, name): auth_settings = {} try: auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] @@ -2718,18 +2718,18 @@ def update_google_settings(cmd, client, resource_group_name, name, # pylint: di scopes=None, allowed_token_audiences=None, client_secret=None, yes=False): # pylint: disable=unused-argument try: show_ingress(cmd, name, resource_group_name) - except: - raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") + except Exception as e: + raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") from e if client_secret is not None and client_secret_setting_name is not None: raise ArgumentUsageError('Usage Error: --client-secret and --client-secret-setting-name cannot ' - 'both be configured to non empty strings') + 'both be configured to non empty strings') if client_secret is not None and not yes: msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): raise ArgumentUsageError('Usage Error: --client-secret cannot be used without agreeing to add secret ' - 'to the containerapp.') + 'to the containerapp.') existing_auth = {} try: @@ -2777,7 +2777,7 @@ def update_google_settings(cmd, client, resource_group_name, name, # pylint: di return updated_auth_settings["identityProviders"]["google"] -def get_twitter_settings(cmd, client, resource_group_name, name): +def get_twitter_settings(client, resource_group_name, name): auth_settings = {} try: auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] @@ -2795,18 +2795,18 @@ def update_twitter_settings(cmd, client, resource_group_name, name, # pylint: d consumer_secret=None, yes=False): # pylint: disable=unused-argument try: show_ingress(cmd, name, resource_group_name) - except: - raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") + except Exception as e: + raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") from e if consumer_secret is not None and consumer_secret_setting_name is not None: raise ArgumentUsageError('Usage Error: --consumer-secret and --consumer-secret-setting-name cannot ' - 'both be configured to non empty strings') + 'both be configured to non empty strings') if consumer_secret is not None and not yes: msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): raise ArgumentUsageError('Usage Error: --client-secret cannot be used without agreeing to add secret ' - 'to the containerapp.') + 'to the containerapp.') existing_auth = {} try: @@ -2841,7 +2841,7 @@ def update_twitter_settings(cmd, client, resource_group_name, name, # pylint: d return updated_auth_settings["identityProviders"]["twitter"] -def get_apple_settings(cmd, client, resource_group_name, name): +def get_apple_settings(client, resource_group_name, name): auth_settings = {} try: auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] @@ -2859,18 +2859,18 @@ def update_apple_settings(cmd, client, resource_group_name, name, # pylint: dis scopes=None, client_secret=None, yes=False): # pylint: disable=unused-argument try: show_ingress(cmd, name, resource_group_name) - except: - raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") + except Exception as e: + raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") from e if client_secret is not None and client_secret_setting_name is not None: raise ArgumentUsageError('Usage Error: --client-secret and --client-secret-setting-name ' - 'cannot both be configured to non empty strings') + 'cannot both be configured to non empty strings') if client_secret is not None and not yes: msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): raise ArgumentUsageError('Usage Error: --client-secret cannot be used without agreeing to add secret ' - 'to the containerapp.') + 'to the containerapp.') existing_auth = {} try: @@ -2911,7 +2911,7 @@ def update_apple_settings(cmd, client, resource_group_name, name, # pylint: dis return updated_auth_settings["identityProviders"]["apple"] -def get_openid_connect_provider_settings(cmd, client, resource_group_name, name, provider_name): # pylint: disable=unused-argument +def get_openid_connect_provider_settings(client, resource_group_name, name, provider_name): # pylint: disable=unused-argument auth_settings = {} try: auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] @@ -2919,13 +2919,13 @@ def get_openid_connect_provider_settings(cmd, client, resource_group_name, name, pass if "identityProviders" not in auth_settings: raise ArgumentUsageError('Usage Error: The following custom OpenID Connect provider ' - 'has not been configured: ' + provider_name) + 'has not been configured: ' + provider_name) if "customOpenIdConnectProviders" not in auth_settings["identityProviders"]: raise ArgumentUsageError('Usage Error: The following custom OpenID Connect provider ' - 'has not been configured: ' + provider_name) + 'has not been configured: ' + provider_name) if provider_name not in auth_settings["identityProviders"]["customOpenIdConnectProviders"]: raise ArgumentUsageError('Usage Error: The following custom OpenID Connect provider ' - 'has not been configured: ' + provider_name) + 'has not been configured: ' + provider_name) return auth_settings["identityProviders"]["customOpenIdConnectProviders"][provider_name] @@ -2936,14 +2936,14 @@ def add_openid_connect_provider_settings(cmd, client, resource_group_name, name, from ._utils import get_oidc_client_setting_app_setting_name try: show_ingress(cmd, name, resource_group_name) - except: - raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") + except Exception as e: + raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") from e if client_secret is not None and not yes: msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): raise ArgumentUsageError('Usage Error: --client-secret cannot be used without agreeing to add secret ' - 'to the containerapp.') + 'to the containerapp.') auth_settings = {} try: @@ -2961,8 +2961,8 @@ def add_openid_connect_provider_settings(cmd, client, resource_group_name, name, auth_settings["identityProviders"]["customOpenIdConnectProviders"] = {} if provider_name in auth_settings["identityProviders"]["customOpenIdConnectProviders"]: raise ArgumentUsageError('Usage Error: The following custom OpenID Connect provider has already been ' - 'configured: ' + provider_name + '. Please use `az containerapp auth oidc update` to ' - 'update the provider.') + 'configured: ' + provider_name + '. Please use `az containerapp auth oidc update` to ' + 'update the provider.') final_client_secret_setting_name = client_secret_setting_name if client_secret is not None: @@ -2999,14 +2999,14 @@ def update_openid_connect_provider_settings(cmd, client, resource_group_name, na from ._utils import get_oidc_client_setting_app_setting_name try: show_ingress(cmd, name, resource_group_name) - except: - raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") + except Exception as e: + raise ValidationError("Authentication requires ingress to be enabled for your containerapp.") from e if client_secret is not None and not yes: msg = 'Configuring --client-secret will add a secret to the containerapp. Are you sure you want to continue?' if not prompt_y_n(msg, default="n"): raise ArgumentUsageError('Usage Error: --client-secret cannot be used without agreeing to add secret ' - 'to the containerapp.') + 'to the containerapp.') auth_settings = {} try: @@ -3020,13 +3020,13 @@ def update_openid_connect_provider_settings(cmd, client, resource_group_name, na if "identityProviders" not in auth_settings: raise ArgumentUsageError('Usage Error: The following custom OpenID Connect provider ' - 'has not been configured: ' + provider_name) + 'has not been configured: ' + provider_name) if "customOpenIdConnectProviders" not in auth_settings["identityProviders"]: raise ArgumentUsageError('Usage Error: The following custom OpenID Connect provider ' - 'has not been configured: ' + provider_name) + 'has not been configured: ' + provider_name) if provider_name not in auth_settings["identityProviders"]["customOpenIdConnectProviders"]: raise ArgumentUsageError('Usage Error: The following custom OpenID Connect provider ' - 'has not been configured: ' + provider_name) + 'has not been configured: ' + provider_name) custom_open_id_connect_providers = auth_settings["identityProviders"]["customOpenIdConnectProviders"] registration = {} @@ -3063,11 +3063,11 @@ def update_openid_connect_provider_settings(cmd, client, resource_group_name, na custom_open_id_connect_providers[provider_name]["registration"] = registration auth_settings["identityProviders"]["customOpenIdConnectProviders"] = custom_open_id_connect_providers - updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=updated_auth_settings).serialize()["properties"] + updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=auth_settings).serialize()["properties"] return updated_auth_settings["identityProviders"]["customOpenIdConnectProviders"][provider_name] -def remove_openid_connect_provider_settings(cmd, client, resource_group_name, name, provider_name): # pylint: disable=unused-argument +def remove_openid_connect_provider_settings(client, resource_group_name, name, provider_name): # pylint: disable=unused-argument auth_settings = {} try: auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] @@ -3075,19 +3075,19 @@ def remove_openid_connect_provider_settings(cmd, client, resource_group_name, na pass if "identityProviders" not in auth_settings: raise ArgumentUsageError('Usage Error: The following custom OpenID Connect provider ' - 'has not been configured: ' + provider_name) + 'has not been configured: ' + provider_name) if "customOpenIdConnectProviders" not in auth_settings["identityProviders"]: raise ArgumentUsageError('Usage Error: The following custom OpenID Connect provider ' - 'has not been configured: ' + provider_name) + 'has not been configured: ' + provider_name) if provider_name not in auth_settings["identityProviders"]["customOpenIdConnectProviders"]: raise ArgumentUsageError('Usage Error: The following custom OpenID Connect provider ' - 'has not been configured: ' + provider_name) + 'has not been configured: ' + provider_name) auth_settings["identityProviders"]["customOpenIdConnectProviders"].pop(provider_name, None) client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=auth_settings).serialize() return {} -def update_auth_config(cmd, client, resource_group_name, name, set_string=None, enabled=None, # pylint: disable=unused-argument +def update_auth_config(client, resource_group_name, name, set_string=None, enabled=None, # pylint: disable=unused-argument runtime_version=None, config_file_path=None, unauthenticated_client_action=None, # pylint: disable=unused-argument redirect_provider=None, enable_token_store=None, require_https=None, # pylint: disable=unused-argument proxy_convention=None, proxy_custom_host_header=None, # pylint: disable=unused-argument @@ -3149,7 +3149,7 @@ def update_auth_config(cmd, client, resource_group_name, name, set_string=None, return client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize() -def show_auth_config(cmd, client, resource_group_name, name): # pylint: disable=unused-argument +def show_auth_config(client, resource_group_name, name): # pylint: disable=unused-argument auth_settings = {} try: auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] diff --git a/src/containerapp/setup.py b/src/containerapp/setup.py index 5418eb34abe..c5926c9c0ec 100644 --- a/src/containerapp/setup.py +++ b/src/containerapp/setup.py @@ -39,7 +39,7 @@ # TODO: Add any additional SDK dependencies here DEPENDENCIES = [ 'azure-cli-core', - 'azure-mgmt-appcontainers' + 'azure-mgmt-appcontainers==1.0.0' ] with open('README.rst', 'r', encoding='utf-8') as f: From 11aeb5ea4e5f58c0cfc8ae0ba275173d065c59f1 Mon Sep 17 00:00:00 2001 From: Haroon Feisal Date: Thu, 19 May 2022 18:31:46 -0400 Subject: [PATCH 08/10] Updated history. Changed constant values instead of using python methods. Added comments to separate auth params, ignored disable_max_length on secrets. Use stable_api_version for client instead of hardcoding. Remove is_preview from containerapp main command group. Added flag to disable max secret length. Removed pylint disable unused-argument comments. Refactored some code. --- src/containerapp/HISTORY.rst | 1 + .../azext_containerapp/_constants.py | 14 +- .../azext_containerapp/_params.py | 4 + .../azext_containerapp/commands.py | 5 +- src/containerapp/azext_containerapp/custom.py | 131 +++++++++--------- 5 files changed, 78 insertions(+), 77 deletions(-) diff --git a/src/containerapp/HISTORY.rst b/src/containerapp/HISTORY.rst index 8e61aac719d..12807a2bb9a 100644 --- a/src/containerapp/HISTORY.rst +++ b/src/containerapp/HISTORY.rst @@ -9,6 +9,7 @@ Release History * Added 'az containerapp env certificate' to manage certificates in a container app environment * Added 'az containerapp hostname' to manage hostnames in a container app * Added 'az containerapp ssl upload' to upload a certificate, add a hostname and the binding to a container app +* Added 'az containerapp auth' to manage AuthConfigs for a containerapp 0.3.4 ++++++ diff --git a/src/containerapp/azext_containerapp/_constants.py b/src/containerapp/azext_containerapp/_constants.py index 85ee196c0d5..1ba84998feb 100644 --- a/src/containerapp/azext_containerapp/_constants.py +++ b/src/containerapp/azext_containerapp/_constants.py @@ -14,13 +14,13 @@ MAX_ENV_PER_LOCATION = 2 -MICROSOFT_SECRET_SETTING_NAME = "MICROSOFT_PROVIDER_AUTHENTICATION_SECRET".lower().replace("_", "-") -FACEBOOK_SECRET_SETTING_NAME = "FACEBOOK_PROVIDER_AUTHENTICATION_SECRET".lower().replace("_", "-") -GITHUB_SECRET_SETTING_NAME = "GITHUB_PROVIDER_AUTHENTICATION_SECRET".lower().replace("_", "-") -GOOGLE_SECRET_SETTING_NAME = "GOOGLE_PROVIDER_AUTHENTICATION_SECRET".lower().replace("_", "-") -MSA_SECRET_SETTING_NAME = "MSA_PROVIDER_AUTHENTICATION_SECRET".lower().replace("_", "-") -TWITTER_SECRET_SETTING_NAME = "TWITTER_PROVIDER_AUTHENTICATION_SECRET".lower().replace("_", "-") -APPLE_SECRET_SETTING_NAME = "APPLE_PROVIDER_AUTHENTICATION_SECRET".lower().replace("_", "-") +MICROSOFT_SECRET_SETTING_NAME = "microsoft-provider-authentication-secret" +FACEBOOK_SECRET_SETTING_NAME = "facebook-provider-authentication-secret" +GITHUB_SECRET_SETTING_NAME = "github-provider-authentication-secret" +GOOGLE_SECRET_SETTING_NAME = "google-provider-authentication-secret" +MSA_SECRET_SETTING_NAME = "msa-provider-authentication-secret" +TWITTER_SECRET_SETTING_NAME = "twitter-provider-authentication-secret" +APPLE_SECRET_SETTING_NAME = "apple-provider-authentication-secret" UNAUTHENTICATED_CLIENT_ACTION = ['RedirectToLoginPage', 'AllowAnonymous', 'RejectWith401', 'RejectWith404'] FORWARD_PROXY_CONVENTION = ['NoProxy', 'Standard', 'Custom'] CHECK_CERTIFICATE_NAME_AVAILABILITY_TYPE = "Microsoft.App/managedEnvironments/certificates" diff --git a/src/containerapp/azext_containerapp/_params.py b/src/containerapp/azext_containerapp/_params.py index ed8a7136edf..32248e4cfd5 100644 --- a/src/containerapp/azext_containerapp/_params.py +++ b/src/containerapp/azext_containerapp/_params.py @@ -224,6 +224,7 @@ def load_arguments(self, _): c.argument('secret_name', help="The name of the secret to show.") c.argument('secret_names', nargs='+', help="A list of secret(s) for the container app. Space-separated secret values names.") c.argument('show_values', help='Show the secret values.') + c.ignore('disable_max_length') with self.argument_context('containerapp env dapr-component') as c: c.argument('dapr_app_id', help="The Dapr app ID.") @@ -274,6 +275,7 @@ def load_arguments(self, _): c.argument('service_principal_tenant_id', help='The service principal tenant ID. Used by Github Actions to authenticate with Azure.', options_list=["--service-principal-tenant-id", "--sp-tid"]) with self.argument_context('containerapp auth') as c: + # subgroup update c.argument('client_id', options_list=['--client-id'], help='The Client ID of the app used for login.') c.argument('client_secret', options_list=['--client-secret'], help='The client secret.') c.argument('client_secret_setting_name', options_list=['--client-secret-name'], help='The app setting name that contains the client secret of the relying party application.') @@ -293,6 +295,7 @@ def load_arguments(self, _): c.argument('consumer_secret', options_list=['--consumer-secret'], help='The consumer secret.') c.argument('provider_name', options_list=['--provider-name'], required=True, help='The name of the custom OpenID Connect provider.') c.argument('openid_configuration', options_list=['--openid-configuration'], help='The endpoint that contains all the configuration endpoints for the provider.') + # auth update c.argument('set_string', options_list=['--set'], help='Value of a specific field within the configuration settings for the Azure App Service Authentication / Authorization V2 feature.') c.argument('config_file_path', options_list=['--config-file-path'], help='The path of the config file containing auth settings if they come from a file.') c.argument('unauthenticated_client_action', options_list=['--unauthenticated-client-action', '--action'], arg_type=get_enum_type(UNAUTHENTICATED_CLIENT_ACTION), help='The action to take when an unauthenticated client attempts to access the app.') @@ -304,6 +307,7 @@ def load_arguments(self, _): c.argument('proxy_custom_proto_header', options_list=['--proxy-custom-proto-header', '--custom-proto-header'], help='The name of the header containing the scheme of the request.') c.argument('excluded_paths', options_list=['--excluded-paths'], help='The list of paths that should be excluded from authentication rules.') c.argument('enabled', options_list=['--enabled'], arg_type=get_three_state_flag(return_label=True), help='true if the Authentication / Authorization feature is enabled for the current app; otherwise, false.') + with self.argument_context('containerapp ssl upload') as c: c.argument('hostname', help='The custom domain name.') c.argument('environment', options_list=['--environment', '-e'], help='Name or resource id of the Container App environment.') diff --git a/src/containerapp/azext_containerapp/commands.py b/src/containerapp/azext_containerapp/commands.py index 97452c73d53..c9e6a44bfe1 100644 --- a/src/containerapp/azext_containerapp/commands.py +++ b/src/containerapp/azext_containerapp/commands.py @@ -8,6 +8,7 @@ # from msrestazure.tools import is_valid_resource_id, parse_resource_id from azext_containerapp._client_factory import ex_handler_factory from ._validators import validate_ssh +from ._clients import STABLE_API_VERSION def transform_containerapp_output(app): @@ -47,12 +48,12 @@ def auth_config_client_factory(cli_ctx, *_): from azure.cli.core.commands.client_factory import get_mgmt_service_client from azure.cli.core.profiles import CustomResourceType MGMT_APPCONTAINERS = CustomResourceType(import_prefix='azure.mgmt.appcontainers', client_name='ContainerAppsAPIClient') - return get_mgmt_service_client(cli_ctx, MGMT_APPCONTAINERS, api_version='2022-03-01').container_apps_auth_configs + return get_mgmt_service_client(cli_ctx, MGMT_APPCONTAINERS, api_version=STABLE_API_VERSION).container_apps_auth_configs def load_command_table(self, _): - with self.command_group('containerapp', is_preview=True) as g: + with self.command_group('containerapp') as g: g.custom_show_command('show', 'show_containerapp', table_transformer=transform_containerapp_output) g.custom_command('list', 'list_containerapp', table_transformer=transform_containerapp_list_output) g.custom_command('create', 'create_containerapp', supports_no_wait=True, exception_handler=ex_handler_factory(), table_transformer=transform_containerapp_output) diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index 04dbdb1ac2c..3e479b5b52f 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -1855,16 +1855,17 @@ def remove_secrets(cmd, name, resource_group_name, secret_names, no_wait=False): def set_secrets(cmd, name, resource_group_name, secrets, # yaml=None, + disable_max_length=False, no_wait=False): _validate_subscription_registered(cmd, CONTAINER_APPS_RP) - # for s in secrets: - # if s: - # parsed = s.split("=") - # if parsed: - # if len(parsed[0]) > MAXIMUM_SECRET_LENGTH: - # raise ValidationError(f"Secret names cannot be longer than {MAXIMUM_SECRET_LENGTH}. " - # f"Please shorten {parsed[0]}") + for s in secrets: + if s: + parsed = s.split("=") + if parsed: + if len(parsed[0]) > MAXIMUM_SECRET_LENGTH and not disable_max_length: + raise ValidationError(f"Secret names cannot be longer than {MAXIMUM_SECRET_LENGTH}. " + f"Please shorten {parsed[0]}") # if not yaml and not secrets: # raise RequiredArgumentMissingError('Usage error: --secrets is required if not using --yaml') @@ -2565,13 +2566,14 @@ def remove_storage(cmd, storage_name, name, resource_group_name, no_wait=False): handle_raw_exception(e) -def update_aad_settings(cmd, client, resource_group_name, name, # pylint: disable=unused-argument - client_id=None, client_secret_setting_name=None, # pylint: disable=unused-argument - issuer=None, allowed_token_audiences=None, client_secret=None, # pylint: disable=unused-argument - client_secret_certificate_thumbprint=None, # pylint: disable=unused-argument - client_secret_certificate_san=None, # pylint: disable=unused-argument - client_secret_certificate_issuer=None, # pylint: disable=unused-argument - yes=False, tenant_id=None): # pylint: disable=unused-argument +# TODO: Refactor provider code to make it cleaner +def update_aad_settings(cmd, client, resource_group_name, name, + client_id=None, client_secret_setting_name=None, + issuer=None, allowed_token_audiences=None, client_secret=None, + client_secret_certificate_thumbprint=None, + client_secret_certificate_san=None, + client_secret_certificate_issuer=None, + yes=False, tenant_id=None): try: show_ingress(cmd, name, resource_group_name) @@ -2648,7 +2650,6 @@ def update_aad_settings(cmd, client, resource_group_name, name, # pylint: disab if tenant_id is not None: openid_issuer = authority + "/" + tenant_id + "/v2.0" - # existing_auth = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] registration = {} validation = {} if "identityProviders" not in existing_auth: @@ -2674,30 +2675,24 @@ def update_aad_settings(cmd, client, resource_group_name, name, # pylint: disab registration["clientSecretSettingName"] = client_secret_setting_name if client_secret is not None: registration["clientSecretSettingName"] = MICROSOFT_SECRET_SETTING_NAME - set_secrets(cmd, name, resource_group_name, secrets=[f"{MICROSOFT_SECRET_SETTING_NAME}={client_secret}"], no_wait=True) + set_secrets(cmd, name, resource_group_name, secrets=[f"{MICROSOFT_SECRET_SETTING_NAME}={client_secret}"], no_wait=True, disable_max_length=True) if client_secret_setting_name is not None or client_secret is not None: - if "clientSecretCertificateThumbprint" in registration and registration["clientSecretCertificateThumbprint"] is not None: - registration["clientSecretCertificateThumbprint"] = None - if "clientSecretCertificateSubjectAlternativeName" in registration and registration["clientSecretCertificateSubjectAlternativeName"] is not None: - registration["clientSecretCertificateSubjectAlternativeName"] = None - if "clientSecretCertificateIssuer" in registration and registration["clientSecretCertificateIssuer"] is not None: - registration["clientSecretCertificateIssuer"] = None + fields = ["clientSecretCertificateThumbprint", "clientSecretCertificateSubjectAlternativeName", "clientSecretCertificateIssuer"] + for field in [f for f in fields if registration.get(f)]: + registration[field] = None if client_secret_certificate_thumbprint is not None: registration["clientSecretCertificateThumbprint"] = client_secret_certificate_thumbprint - if "clientSecretSettingName" in registration and registration["clientSecretSettingName"] is not None: - registration["clientSecretSettingName"] = None - if "clientSecretCertificateSubjectAlternativeName" in registration and registration["clientSecretCertificateSubjectAlternativeName"] is not None: - registration["clientSecretCertificateSubjectAlternativeName"] = None - if "clientSecretCertificateIssuer" in registration and registration["clientSecretCertificateIssuer"] is not None: - registration["clientSecretCertificateIssuer"] = None + fields = ["clientSecretSettingName", "clientSecretCertificateSubjectAlternativeName", "clientSecretCertificateIssuer"] + for field in [f for f in fields if registration.get(f)]: + registration[field] = None if client_secret_certificate_san is not None: registration["clientSecretCertificateSubjectAlternativeName"] = client_secret_certificate_san if client_secret_certificate_issuer is not None: registration["clientSecretCertificateIssuer"] = client_secret_certificate_issuer if client_secret_certificate_san is not None and client_secret_certificate_issuer is not None: - if "clientSecretSettingName" in registration and registration["clientSecretSettingName"] is not None: + if "clientSecretSettingName" in registration: registration["clientSecretSettingName"] = None - if "clientSecretCertificateThumbprint" in registration and registration["clientSecretCertificateThumbprint"] is not None: + if "clientSecretCertificateThumbprint" in registration: registration["clientSecretCertificateThumbprint"] = None if openid_issuer is not None: registration["openIdIssuer"] = openid_issuer @@ -2741,9 +2736,9 @@ def get_facebook_settings(client, resource_group_name, name): return auth_settings["identityProviders"]["facebook"] -def update_facebook_settings(cmd, client, resource_group_name, name, # pylint: disable=unused-argument - app_id=None, app_secret_setting_name=None, # pylint: disable=unused-argument - graph_api_version=None, scopes=None, app_secret=None, yes=False): # pylint: disable=unused-argument +def update_facebook_settings(cmd, client, resource_group_name, name, + app_id=None, app_secret_setting_name=None, + graph_api_version=None, scopes=None, app_secret=None, yes=False): try: show_ingress(cmd, name, resource_group_name) except Exception as e: @@ -2788,7 +2783,7 @@ def update_facebook_settings(cmd, client, resource_group_name, name, # pylint: registration["appSecretSettingName"] = app_secret_setting_name if app_secret is not None: registration["appSecretSettingName"] = FACEBOOK_SECRET_SETTING_NAME - set_secrets(cmd, name, resource_group_name, secrets=[f"{FACEBOOK_SECRET_SETTING_NAME}={app_secret}"], no_wait=True) + set_secrets(cmd, name, resource_group_name, secrets=[f"{FACEBOOK_SECRET_SETTING_NAME}={app_secret}"], no_wait=True, disable_max_length=True) if graph_api_version is not None: existing_auth["identityProviders"]["facebook"]["graphApiVersion"] = graph_api_version if scopes is not None: @@ -2813,9 +2808,9 @@ def get_github_settings(client, resource_group_name, name): return auth_settings["identityProviders"]["gitHub"] -def update_github_settings(cmd, client, resource_group_name, name, # pylint: disable=unused-argument - client_id=None, client_secret_setting_name=None, # pylint: disable=unused-argument - scopes=None, client_secret=None, yes=False): # pylint: disable=unused-argument +def update_github_settings(cmd, client, resource_group_name, name, + client_id=None, client_secret_setting_name=None, + scopes=None, client_secret=None, yes=False): try: show_ingress(cmd, name, resource_group_name) except Exception as e: @@ -2860,7 +2855,7 @@ def update_github_settings(cmd, client, resource_group_name, name, # pylint: di registration["clientSecretSettingName"] = client_secret_setting_name if client_secret is not None: registration["clientSecretSettingName"] = GITHUB_SECRET_SETTING_NAME - set_secrets(cmd, name, resource_group_name, secrets=[f"{GITHUB_SECRET_SETTING_NAME}={client_secret}"], no_wait=True) + set_secrets(cmd, name, resource_group_name, secrets=[f"{GITHUB_SECRET_SETTING_NAME}={client_secret}"], no_wait=True, disable_max_length=True) if scopes is not None: existing_auth["identityProviders"]["gitHub"]["login"]["scopes"] = scopes.split(",") if client_id is not None or client_secret is not None or client_secret_setting_name is not None: @@ -2883,9 +2878,9 @@ def get_google_settings(client, resource_group_name, name): return auth_settings["identityProviders"]["google"] -def update_google_settings(cmd, client, resource_group_name, name, # pylint: disable=unused-argument - client_id=None, client_secret_setting_name=None, # pylint: disable=unused-argument - scopes=None, allowed_token_audiences=None, client_secret=None, yes=False): # pylint: disable=unused-argument +def update_google_settings(cmd, client, resource_group_name, name, + client_id=None, client_secret_setting_name=None, + scopes=None, allowed_token_audiences=None, client_secret=None, yes=False): try: show_ingress(cmd, name, resource_group_name) except Exception as e: @@ -2934,7 +2929,7 @@ def update_google_settings(cmd, client, resource_group_name, name, # pylint: di registration["clientSecretSettingName"] = client_secret_setting_name if client_secret is not None: registration["clientSecretSettingName"] = GOOGLE_SECRET_SETTING_NAME - set_secrets(cmd, name, resource_group_name, secrets=[f"{GOOGLE_SECRET_SETTING_NAME}={client_secret}"], no_wait=True) + set_secrets(cmd, name, resource_group_name, secrets=[f"{GOOGLE_SECRET_SETTING_NAME}={client_secret}"], no_wait=True, disable_max_length=True) if scopes is not None: existing_auth["identityProviders"]["google"]["login"]["scopes"] = scopes.split(",") if allowed_token_audiences is not None: @@ -2960,9 +2955,9 @@ def get_twitter_settings(client, resource_group_name, name): return auth_settings["identityProviders"]["twitter"] -def update_twitter_settings(cmd, client, resource_group_name, name, # pylint: disable=unused-argument - consumer_key=None, consumer_secret_setting_name=None, # pylint: disable=unused-argument - consumer_secret=None, yes=False): # pylint: disable=unused-argument +def update_twitter_settings(cmd, client, resource_group_name, name, + consumer_key=None, consumer_secret_setting_name=None, + consumer_secret=None, yes=False): try: show_ingress(cmd, name, resource_group_name) except Exception as e: @@ -3004,7 +2999,7 @@ def update_twitter_settings(cmd, client, resource_group_name, name, # pylint: d registration["consumerSecretSettingName"] = consumer_secret_setting_name if consumer_secret is not None: registration["consumerSecretSettingName"] = TWITTER_SECRET_SETTING_NAME - set_secrets(cmd, name, resource_group_name, secrets=[f"{TWITTER_SECRET_SETTING_NAME}={consumer_secret}"], no_wait=True) + set_secrets(cmd, name, resource_group_name, secrets=[f"{TWITTER_SECRET_SETTING_NAME}={consumer_secret}"], no_wait=True, disable_max_length=True) if consumer_key is not None or consumer_secret is not None or consumer_secret_setting_name is not None: existing_auth["identityProviders"]["twitter"]["registration"] = registration updated_auth_settings = client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize()["properties"] @@ -3024,9 +3019,9 @@ def get_apple_settings(client, resource_group_name, name): return auth_settings["identityProviders"]["apple"] -def update_apple_settings(cmd, client, resource_group_name, name, # pylint: disable=unused-argument - client_id=None, client_secret_setting_name=None, # pylint: disable=unused-argument - scopes=None, client_secret=None, yes=False): # pylint: disable=unused-argument +def update_apple_settings(cmd, client, resource_group_name, name, + client_id=None, client_secret_setting_name=None, + scopes=None, client_secret=None, yes=False): try: show_ingress(cmd, name, resource_group_name) except Exception as e: @@ -3071,7 +3066,7 @@ def update_apple_settings(cmd, client, resource_group_name, name, # pylint: dis registration["clientSecretSettingName"] = client_secret_setting_name if client_secret is not None: registration["clientSecretSettingName"] = APPLE_SECRET_SETTING_NAME - set_secrets(cmd, name, resource_group_name, secrets=[f"{APPLE_SECRET_SETTING_NAME}={client_secret}"], no_wait=True) + set_secrets(cmd, name, resource_group_name, secrets=[f"{APPLE_SECRET_SETTING_NAME}={client_secret}"], no_wait=True, disable_max_length=True) if scopes is not None: existing_auth["identityProviders"]["apple"]["login"]["scopes"] = scopes.split(",") if client_id is not None or client_secret is not None or client_secret_setting_name is not None: @@ -3081,7 +3076,7 @@ def update_apple_settings(cmd, client, resource_group_name, name, # pylint: dis return updated_auth_settings["identityProviders"]["apple"] -def get_openid_connect_provider_settings(client, resource_group_name, name, provider_name): # pylint: disable=unused-argument +def get_openid_connect_provider_settings(client, resource_group_name, name, provider_name): auth_settings = {} try: auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] @@ -3099,10 +3094,10 @@ def get_openid_connect_provider_settings(client, resource_group_name, name, prov return auth_settings["identityProviders"]["customOpenIdConnectProviders"][provider_name] -def add_openid_connect_provider_settings(cmd, client, resource_group_name, name, provider_name, # pylint: disable=unused-argument - client_id=None, client_secret_setting_name=None, # pylint: disable=unused-argument - openid_configuration=None, scopes=None, # pylint: disable=unused-argument - client_secret=None, yes=False): # pylint: disable=unused-argument +def add_openid_connect_provider_settings(cmd, client, resource_group_name, name, provider_name, + client_id=None, client_secret_setting_name=None, + openid_configuration=None, scopes=None, + client_secret=None, yes=False): from ._utils import get_oidc_client_setting_app_setting_name try: show_ingress(cmd, name, resource_group_name) @@ -3137,7 +3132,7 @@ def add_openid_connect_provider_settings(cmd, client, resource_group_name, name, final_client_secret_setting_name = client_secret_setting_name if client_secret is not None: final_client_secret_setting_name = get_oidc_client_setting_app_setting_name(provider_name) - set_secrets(cmd, name, resource_group_name, secrets=[f"{final_client_secret_setting_name}={client_secret}"], no_wait=True) + set_secrets(cmd, name, resource_group_name, secrets=[f"{final_client_secret_setting_name}={client_secret}"], no_wait=True, disable_max_length=True) auth_settings["identityProviders"]["customOpenIdConnectProviders"][provider_name] = { "registration": { @@ -3162,10 +3157,10 @@ def add_openid_connect_provider_settings(cmd, client, resource_group_name, name, return updated_auth_settings["identityProviders"]["customOpenIdConnectProviders"][provider_name] -def update_openid_connect_provider_settings(cmd, client, resource_group_name, name, provider_name, # pylint: disable=unused-argument - client_id=None, client_secret_setting_name=None, # pylint: disable=unused-argument - openid_configuration=None, scopes=None, # pylint: disable=unused-argument - client_secret=None, yes=False): # pylint: disable=unused-argument +def update_openid_connect_provider_settings(cmd, client, resource_group_name, name, provider_name, + client_id=None, client_secret_setting_name=None, + openid_configuration=None, scopes=None, + client_secret=None, yes=False): from ._utils import get_oidc_client_setting_app_setting_name try: show_ingress(cmd, name, resource_group_name) @@ -3224,7 +3219,7 @@ def update_openid_connect_provider_settings(cmd, client, resource_group_name, na if client_secret is not None: final_client_secret_setting_name = get_oidc_client_setting_app_setting_name(provider_name) registration["clientSecretSettingName"] = final_client_secret_setting_name - set_secrets(cmd, name, resource_group_name, secrets=[f"{final_client_secret_setting_name}={client_secret}"], no_wait=True) + set_secrets(cmd, name, resource_group_name, secrets=[f"{final_client_secret_setting_name}={client_secret}"], no_wait=True, disable_max_length=True) if openid_configuration is not None: registration["openIdConnectConfiguration"]["wellKnownOpenIdConfiguration"] = openid_configuration if scopes is not None: @@ -3237,7 +3232,7 @@ def update_openid_connect_provider_settings(cmd, client, resource_group_name, na return updated_auth_settings["identityProviders"]["customOpenIdConnectProviders"][provider_name] -def remove_openid_connect_provider_settings(client, resource_group_name, name, provider_name): # pylint: disable=unused-argument +def remove_openid_connect_provider_settings(client, resource_group_name, name, provider_name): auth_settings = {} try: auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] @@ -3257,11 +3252,11 @@ def remove_openid_connect_provider_settings(client, resource_group_name, name, p return {} -def update_auth_config(client, resource_group_name, name, set_string=None, enabled=None, # pylint: disable=unused-argument - runtime_version=None, config_file_path=None, unauthenticated_client_action=None, # pylint: disable=unused-argument - redirect_provider=None, enable_token_store=None, require_https=None, # pylint: disable=unused-argument - proxy_convention=None, proxy_custom_host_header=None, # pylint: disable=unused-argument - proxy_custom_proto_header=None, excluded_paths=None, slot=None): # pylint: disable=unused-argument +def update_auth_config(client, resource_group_name, name, set_string=None, enabled=None, + runtime_version=None, config_file_path=None, unauthenticated_client_action=None, + redirect_provider=None, enable_token_store=None, require_https=None, + proxy_convention=None, proxy_custom_host_header=None, + proxy_custom_proto_header=None, excluded_paths=None, slot=None): from ._utils import set_field_in_auth_settings, update_http_settings_in_auth_settings existing_auth = {} try: @@ -3319,7 +3314,7 @@ def update_auth_config(client, resource_group_name, name, set_string=None, enabl return client.create_or_update(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current", auth_config_envelope=existing_auth).serialize() -def show_auth_config(client, resource_group_name, name): # pylint: disable=unused-argument +def show_auth_config(client, resource_group_name, name): auth_settings = {} try: auth_settings = client.get(resource_group_name=resource_group_name, container_app_name=name, auth_config_name="current").serialize()["properties"] From 873a15f2eac74e978821ff522590beab819892de Mon Sep 17 00:00:00 2001 From: Haroon Feisal Date: Thu, 19 May 2022 19:00:20 -0400 Subject: [PATCH 09/10] Added help text for subgroups with examples. --- src/containerapp/azext_containerapp/_help.py | 221 +++++++++++++++++++ 1 file changed, 221 insertions(+) diff --git a/src/containerapp/azext_containerapp/_help.py b/src/containerapp/azext_containerapp/_help.py index 4f19429f41f..b254033e21b 100644 --- a/src/containerapp/azext_containerapp/_help.py +++ b/src/containerapp/azext_containerapp/_help.py @@ -807,3 +807,224 @@ text: | az containerapp hostname list -n MyContainerapp -g MyResourceGroup """ + +# Auth commands +helps['containerapp auth'] = """ +type: group +short-summary: Manage containerapp authentication and authorization. +""" + +helps['containerapp auth show'] = """ +type: command +short-summary: Show the authentication settings for the containerapp. +examples: + - name: Show the authentication settings for the containerapp. + text: az containerapp auth show --name MyContainerapp --resource-group MyResourceGroup +""" + +helps['containerapp auth update'] = """ +type: command +short-summary: Update the authentication settings for the containerapp. +examples: + - name: Update the client ID of the AAD provider already configured. + text: | + az containerapp auth update -g myResourceGroup --name MyContainerapp --set identityProviders.azureActiveDirectory.registration.clientId=my-client-id + - name: Configure the app with file based authentication by setting the config file path. + text: | + az containerapp auth update -g myResourceGroup --name MyContainerapp --config-file-path D:\\home\\site\\wwwroot\\auth.json + - name: Configure the app to allow unauthenticated requests to hit the app. + text: | + az containerapp auth update -g myResourceGroup --name MyContainerapp --unauthenticated-client-action AllowAnonymous + - name: Configure the app to redirect unauthenticated requests to the Facebook provider. + text: | + az containerapp auth update -g myResourceGroup --name MyContainerapp --redirect-provider Facebook + - name: Configure the app to listen to the forward headers X-FORWARDED-HOST and X-FORWARDED-PROTO. + text: | + az containerapp auth update -g myResourceGroup --name MyContainerapp --proxy-convention Standard +""" + +helps['containerapp auth apple'] = """ +type: group +short-summary: Manage containerapp authentication and authorization of the Apple identity provider. +""" + +helps['containerapp auth apple show'] = """ +type: command +short-summary: Show the authentication settings for the Apple identity provider. +examples: + - name: Show the authentication settings for the Apple identity provider. + text: az containerapp auth apple show --name MyContainerapp --resource-group MyResourceGroup +""" + +helps['containerapp auth apple update'] = """ +type: command +short-summary: Update the client id and client secret for the Apple identity provider. +examples: + - name: Update the client id and client secret for the Apple identity provider. + text: | + az containerapp auth apple update -g myResourceGroup --name MyContainerapp \\ + --client-id my-client-id --client-secret very_secret_password +""" + +helps['containerapp auth facebook'] = """ +type: group +short-summary: Manage containerapp authentication and authorization of the Facebook identity provider. +""" + +helps['containerapp auth facebook show'] = """ +type: command +short-summary: Show the authentication settings for the Facebook identity provider. +examples: + - name: Show the authentication settings for the Facebook identity provider. + text: az containerapp auth facebook show --name MyContainerapp --resource-group MyResourceGroup +""" + +helps['containerapp auth facebook update'] = """ +type: command +short-summary: Update the app id and app secret for the Facebook identity provider. +examples: + - name: Update the app id and app secret for the Facebook identity provider. + text: | + az containerapp auth facebook update -g myResourceGroup --name MyContainerapp \\ + --app-id my-client-id --app-secret very_secret_password +""" + +helps['containerapp auth github'] = """ +type: group +short-summary: Manage containerapp authentication and authorization of the GitHub identity provider. +""" + +helps['containerapp auth github show'] = """ +type: command +short-summary: Show the authentication settings for the GitHub identity provider. +examples: + - name: Show the authentication settings for the GitHub identity provider. + text: az containerapp auth github show --name MyContainerapp --resource-group MyResourceGroup +""" + +helps['containerapp auth github update'] = """ +type: command +short-summary: Update the client id and client secret for the GitHub identity provider. +examples: + - name: Update the client id and client secret for the GitHub identity provider. + text: | + az containerapp auth github update -g myResourceGroup --name MyContainerapp \\ + --client-id my-client-id --client-secret very_secret_password +""" + +helps['containerapp auth google'] = """ +type: group +short-summary: Manage containerapp authentication and authorization of the Google identity provider. +""" + +helps['containerapp auth google show'] = """ +type: command +short-summary: Show the authentication settings for the Google identity provider. +examples: + - name: Show the authentication settings for the Google identity provider. + text: az containerapp auth google show --name MyContainerapp --resource-group MyResourceGroup +""" + +helps['containerapp auth google update'] = """ +type: command +short-summary: Update the client id and client secret for the Google identity provider. +examples: + - name: Update the client id and client secret for the Google identity provider. + text: | + az containerapp auth google update -g myResourceGroup --name MyContainerapp \\ + --client-id my-client-id --client-secret very_secret_password +""" + +helps['containerapp auth microsoft'] = """ +type: group +short-summary: Manage containerapp authentication and authorization of the Microsoft identity provider. +""" + +helps['containerapp auth microsoft show'] = """ +type: command +short-summary: Show the authentication settings for the Azure Active Directory identity provider. +examples: + - name: Show the authentication settings for the Azure Active Directory identity provider. + text: az containerapp auth microsoft show --name MyContainerapp --resource-group MyResourceGroup +""" + +helps['containerapp auth microsoft update'] = """ +type: command +short-summary: Update the client id and client secret for the Azure Active Directory identity provider. +examples: + - name: Update the open id issuer, client id and client secret for the Azure Active Directory identity provider. + text: | + az containerapp auth microsoft update -g myResourceGroup --name MyContainerapp \\ + --client-id my-client-id --client-secret very_secret_password \\ + --issuer https://sts.windows.net/54826b22-38d6-4fb2-bad9-b7983a3e9c5a/ +""" + +helps['containerapp auth openid-connect'] = """ +type: group +short-summary: Manage containerapp authentication and authorization of the custom OpenID Connect identity providers. +""" + +helps['containerapp auth openid-connect show'] = """ +type: command +short-summary: Show the authentication settings for the custom OpenID Connect identity provider. +examples: + - name: Show the authentication settings for the custom OpenID Connect identity provider. + text: az containerapp auth openid-connect show --name MyContainerapp --resource-group MyResourceGroup \\ + --provider-name myOpenIdConnectProvider +""" + +helps['containerapp auth openid-connect add'] = """ +type: command +short-summary: Configure a new custom OpenID Connect identity provider. +examples: + - name: Configure a new custom OpenID Connect identity provider. + text: | + az containerapp auth openid-connect add -g myResourceGroup --name MyContainerapp \\ + --provider-name myOpenIdConnectProvider --client-id my-client-id \\ + --client-secret-setting-name MY_SECRET_APP_SETTING \\ + --openid-configuration https://myopenidprovider.net/.well-known/openid-configuration +""" + +helps['containerapp auth openid-connect update'] = """ +type: command +short-summary: Update the client id and client secret setting name for an existing custom OpenID Connect identity provider. +examples: + - name: Update the client id and client secret setting name for an existing custom OpenID Connect identity provider. + text: | + az containerapp auth openid-connect update -g myResourceGroup --name MyContainerapp \\ + --provider-name myOpenIdConnectProvider --client-id my-client-id \\ + --client-secret-setting-name MY_SECRET_APP_SETTING +""" + +helps['containerapp auth openid-connect remove'] = """ +type: command +short-summary: Removes an existing custom OpenID Connect identity provider. +examples: + - name: Removes an existing custom OpenID Connect identity provider. + text: | + az containerapp auth openid-connect remove --name MyContainerapp --resource-group MyResourceGroup \\ + --provider-name myOpenIdConnectProvider +""" + +helps['containerapp auth twitter'] = """ +type: group +short-summary: Manage containerapp authentication and authorization of the Twitter identity provider. +""" + +helps['containerapp auth twitter show'] = """ +type: command +short-summary: Show the authentication settings for the Twitter identity provider. +examples: + - name: Show the authentication settings for the Twitter identity provider. + text: az containerapp auth twitter show --name MyContainerapp --resource-group MyResourceGroup +""" + +helps['containerapp auth twitter update'] = """ +type: command +short-summary: Update the consumer key and consumer secret for the Twitter identity provider. +examples: + - name: Update the consumer key and consumer secret for the Twitter identity provider. + text: | + az containerapp auth twitter update -g myResourceGroup --name MyContainerapp \\ + --consumer-key my-client-id --consumer-secret very_secret_password +""" From e7137ff63c7184d9f707040aa833e5a93198386c Mon Sep 17 00:00:00 2001 From: Haroon Feisal Date: Thu, 19 May 2022 19:29:38 -0400 Subject: [PATCH 10/10] Updated util. Fixed style issues. --- src/containerapp/azext_containerapp/_help.py | 4 ++-- .../azext_containerapp/_params.py | 8 ++++--- src/containerapp/azext_containerapp/_utils.py | 10 +++----- .../azext_containerapp/commands.py | 10 ++++---- src/containerapp/azext_containerapp/custom.py | 23 +++++++++---------- 5 files changed, 26 insertions(+), 29 deletions(-) diff --git a/src/containerapp/azext_containerapp/_help.py b/src/containerapp/azext_containerapp/_help.py index b254033e21b..bbe07960a82 100644 --- a/src/containerapp/azext_containerapp/_help.py +++ b/src/containerapp/azext_containerapp/_help.py @@ -981,7 +981,7 @@ text: | az containerapp auth openid-connect add -g myResourceGroup --name MyContainerapp \\ --provider-name myOpenIdConnectProvider --client-id my-client-id \\ - --client-secret-setting-name MY_SECRET_APP_SETTING \\ + --client-secret-name MY_SECRET_APP_SETTING \\ --openid-configuration https://myopenidprovider.net/.well-known/openid-configuration """ @@ -993,7 +993,7 @@ text: | az containerapp auth openid-connect update -g myResourceGroup --name MyContainerapp \\ --provider-name myOpenIdConnectProvider --client-id my-client-id \\ - --client-secret-setting-name MY_SECRET_APP_SETTING + --client-secret-name MY_SECRET_APP_SETTING """ helps['containerapp auth openid-connect remove'] = """ diff --git a/src/containerapp/azext_containerapp/_params.py b/src/containerapp/azext_containerapp/_params.py index 32248e4cfd5..adcfaddf145 100644 --- a/src/containerapp/azext_containerapp/_params.py +++ b/src/containerapp/azext_containerapp/_params.py @@ -278,7 +278,7 @@ def load_arguments(self, _): # subgroup update c.argument('client_id', options_list=['--client-id'], help='The Client ID of the app used for login.') c.argument('client_secret', options_list=['--client-secret'], help='The client secret.') - c.argument('client_secret_setting_name', options_list=['--client-secret-name'], help='The app setting name that contains the client secret of the relying party application.') + c.argument('client_secret_setting_name', options_list=['--client-secret-name'], help='The app secret name that contains the client secret of the relying party application.') c.argument('issuer', options_list=['--issuer'], help='The OpenID Connect Issuer URI that represents the entity which issues access tokens for this application.') c.argument('allowed_token_audiences', options_list=['--allowed-token-audiences', '--allowed-audiences'], help='The configuration settings of the allowed list of audiences from which to validate the JWT token.') c.argument('client_secret_certificate_thumbprint', options_list=['--thumbprint', '--client-secret-certificate-thumbprint'], help='Alternative to AAD Client Secret, thumbprint of a certificate used for signing purposes') @@ -288,15 +288,16 @@ def load_arguments(self, _): c.argument('tenant_id', options_list=['--tenant-id'], help='The tenant id of the application.') c.argument('app_id', options_list=['--app-id'], help='The App ID of the app used for login.') c.argument('app_secret', options_list=['--app-secret'], help='The app secret.') - c.argument('app_secret_setting_name', options_list=['--app-secret-setting-name', '--secret-setting'], help='The app setting name that contains the app secret.') + c.argument('app_secret_setting_name', options_list=['--app-secret-name', '--secret-name'], help='The app secret name that contains the app secret.') c.argument('graph_api_version', options_list=['--graph-api-version'], help='The version of the Facebook api to be used while logging in.') c.argument('scopes', options_list=['--scopes'], help='A list of the scopes that should be requested while authenticating.') c.argument('consumer_key', options_list=['--consumer-key'], help='The OAuth 1.0a consumer key of the Twitter application used for sign-in.') c.argument('consumer_secret', options_list=['--consumer-secret'], help='The consumer secret.') + c.argument('consumer_secret_setting_name', options_list=['--consumer-secret-name', '--secret-name'], help='The consumer secret name that contains the app secret.') c.argument('provider_name', options_list=['--provider-name'], required=True, help='The name of the custom OpenID Connect provider.') c.argument('openid_configuration', options_list=['--openid-configuration'], help='The endpoint that contains all the configuration endpoints for the provider.') # auth update - c.argument('set_string', options_list=['--set'], help='Value of a specific field within the configuration settings for the Azure App Service Authentication / Authorization V2 feature.') + c.argument('set_string', options_list=['--set'], help='Value of a specific field within the configuration settings for the Azure App Service Authentication / Authorization feature.') c.argument('config_file_path', options_list=['--config-file-path'], help='The path of the config file containing auth settings if they come from a file.') c.argument('unauthenticated_client_action', options_list=['--unauthenticated-client-action', '--action'], arg_type=get_enum_type(UNAUTHENTICATED_CLIENT_ACTION), help='The action to take when an unauthenticated client attempts to access the app.') c.argument('redirect_provider', options_list=['--redirect-provider'], help='The default authentication provider to use when multiple providers are configured.') @@ -307,6 +308,7 @@ def load_arguments(self, _): c.argument('proxy_custom_proto_header', options_list=['--proxy-custom-proto-header', '--custom-proto-header'], help='The name of the header containing the scheme of the request.') c.argument('excluded_paths', options_list=['--excluded-paths'], help='The list of paths that should be excluded from authentication rules.') c.argument('enabled', options_list=['--enabled'], arg_type=get_three_state_flag(return_label=True), help='true if the Authentication / Authorization feature is enabled for the current app; otherwise, false.') + c.argument('runtime_version', options_list=['--runtime-version'], help='The RuntimeVersion of the Authentication / Authorization feature in use for the current app.') with self.argument_context('containerapp ssl upload') as c: c.argument('hostname', help='The custom domain name.') diff --git a/src/containerapp/azext_containerapp/_utils.py b/src/containerapp/azext_containerapp/_utils.py index e49cec4dba2..5b27d3581c4 100644 --- a/src/containerapp/azext_containerapp/_utils.py +++ b/src/containerapp/azext_containerapp/_utils.py @@ -1253,12 +1253,8 @@ def update_http_settings_in_auth_settings(auth_settings, require_https, proxy_co def get_oidc_client_setting_app_setting_name(provider_name): - provider_name_prefix = provider_name.upper() - - # an appsetting name can be up to 64 characters, and the suffix _PROVIDER_AUTHENTICATION_SECRET is 31 characters so limitting this to 32 - if len(provider_name_prefix) > 32: - provider_name_prefix = provider_name_prefix[0:31] - return provider_name_prefix + "_PROVIDER_AUTHENTICATION_SECRET" + provider_name_prefix = provider_name.lower() + return provider_name_prefix + "-provider-authentication-secret" # only accept .pfx or .pem file @@ -1292,7 +1288,7 @@ def load_cert_file(file_path, cert_password=None): else: raise FileOperationError('Not a valid file type. Only .PFX and .PEM files are supported.') except Exception as e: - raise CLIInternalError(e) + raise CLIInternalError(e) from e return blob, thumbprint diff --git a/src/containerapp/azext_containerapp/commands.py b/src/containerapp/azext_containerapp/commands.py index c9e6a44bfe1..c1c355cdd5f 100644 --- a/src/containerapp/azext_containerapp/commands.py +++ b/src/containerapp/azext_containerapp/commands.py @@ -63,11 +63,11 @@ def load_command_table(self, _): g.custom_command('up', 'containerapp_up', supports_no_wait=False, exception_handler=ex_handler_factory()) g.custom_command('browse', 'open_containerapp_in_browser') - with self.command_group('containerapp replica', is_preview=True) as g: + with self.command_group('containerapp replica') as g: g.custom_show_command('show', 'get_replica') # TODO implement the table transformer g.custom_command('list', 'list_replicas') - with self.command_group('containerapp logs', is_preview=True) as g: + with self.command_group('containerapp logs') as g: g.custom_show_command('show', 'stream_containerapp_logs', validator=validate_ssh) with self.command_group('containerapp env') as g: @@ -87,7 +87,7 @@ def load_command_table(self, _): g.custom_command('upload', 'upload_certificate') g.custom_command('delete', 'delete_certificate', confirmation=True, exception_handler=ex_handler_factory()) - with self.command_group('containerapp env storage') as g: + with self.command_group('containerapp env storage', is_preview=True) as g: g.custom_show_command('show', 'show_storage') g.custom_command('list', 'list_storage') g.custom_command('set', 'create_or_update_storage', supports_no_wait=True, exception_handler=ex_handler_factory()) @@ -142,11 +142,11 @@ def load_command_table(self, _): g.custom_command('disable', 'disable_dapr', exception_handler=ex_handler_factory()) with self.command_group('containerapp auth', client_factory=auth_config_client_factory) as g: - g.custom_command('show', 'show_auth_config', exception_handler=ex_handler_factory()) + g.custom_show_command('show', 'show_auth_config', exception_handler=ex_handler_factory()) g.custom_command('update', 'update_auth_config', exception_handler=ex_handler_factory()) with self.command_group('containerapp auth microsoft', client_factory=auth_config_client_factory) as g: - g.custom_command('show', 'get_aad_settings') + g.custom_show_command('show', 'get_aad_settings') g.custom_command('update', 'update_aad_settings', exception_handler=ex_handler_factory()) with self.command_group('containerapp auth facebook', client_factory=auth_config_client_factory) as g: diff --git a/src/containerapp/azext_containerapp/custom.py b/src/containerapp/azext_containerapp/custom.py index 3e479b5b52f..c71570e4230 100644 --- a/src/containerapp/azext_containerapp/custom.py +++ b/src/containerapp/azext_containerapp/custom.py @@ -2378,7 +2378,6 @@ def upload_certificate(cmd, name, resource_group_name, certificate_file, certifi cert_name = None if certificate_name: if not check_cert_name_availability(cmd, resource_group_name, name, certificate_name): - from knack.prompting import prompt_y_n msg = 'A certificate with the name {} already exists in {}. If continue with this name, it will be overwritten by the new certificate file.\nOverwrite?' overwrite = prompt_y_n(msg.format(certificate_name, name)) if overwrite: @@ -2573,7 +2572,7 @@ def update_aad_settings(cmd, client, resource_group_name, name, client_secret_certificate_thumbprint=None, client_secret_certificate_san=None, client_secret_certificate_issuer=None, - yes=False, tenant_id=None): + yes=False, tenant_id=None): try: show_ingress(cmd, name, resource_group_name) @@ -2738,7 +2737,7 @@ def get_facebook_settings(client, resource_group_name, name): def update_facebook_settings(cmd, client, resource_group_name, name, app_id=None, app_secret_setting_name=None, - graph_api_version=None, scopes=None, app_secret=None, yes=False): + graph_api_version=None, scopes=None, app_secret=None, yes=False): try: show_ingress(cmd, name, resource_group_name) except Exception as e: @@ -2810,7 +2809,7 @@ def get_github_settings(client, resource_group_name, name): def update_github_settings(cmd, client, resource_group_name, name, client_id=None, client_secret_setting_name=None, - scopes=None, client_secret=None, yes=False): + scopes=None, client_secret=None, yes=False): try: show_ingress(cmd, name, resource_group_name) except Exception as e: @@ -2880,7 +2879,7 @@ def get_google_settings(client, resource_group_name, name): def update_google_settings(cmd, client, resource_group_name, name, client_id=None, client_secret_setting_name=None, - scopes=None, allowed_token_audiences=None, client_secret=None, yes=False): + scopes=None, allowed_token_audiences=None, client_secret=None, yes=False): try: show_ingress(cmd, name, resource_group_name) except Exception as e: @@ -2956,8 +2955,8 @@ def get_twitter_settings(client, resource_group_name, name): def update_twitter_settings(cmd, client, resource_group_name, name, - consumer_key=None, consumer_secret_setting_name=None, - consumer_secret=None, yes=False): + consumer_key=None, consumer_secret_setting_name=None, + consumer_secret=None, yes=False): try: show_ingress(cmd, name, resource_group_name) except Exception as e: @@ -3021,7 +3020,7 @@ def get_apple_settings(client, resource_group_name, name): def update_apple_settings(cmd, client, resource_group_name, name, client_id=None, client_secret_setting_name=None, - scopes=None, client_secret=None, yes=False): + scopes=None, client_secret=None, yes=False): try: show_ingress(cmd, name, resource_group_name) except Exception as e: @@ -3096,8 +3095,8 @@ def get_openid_connect_provider_settings(client, resource_group_name, name, prov def add_openid_connect_provider_settings(cmd, client, resource_group_name, name, provider_name, client_id=None, client_secret_setting_name=None, - openid_configuration=None, scopes=None, - client_secret=None, yes=False): + openid_configuration=None, scopes=None, + client_secret=None, yes=False): from ._utils import get_oidc_client_setting_app_setting_name try: show_ingress(cmd, name, resource_group_name) @@ -3160,7 +3159,7 @@ def add_openid_connect_provider_settings(cmd, client, resource_group_name, name, def update_openid_connect_provider_settings(cmd, client, resource_group_name, name, provider_name, client_id=None, client_secret_setting_name=None, openid_configuration=None, scopes=None, - client_secret=None, yes=False): + client_secret=None, yes=False): from ._utils import get_oidc_client_setting_app_setting_name try: show_ingress(cmd, name, resource_group_name) @@ -3256,7 +3255,7 @@ def update_auth_config(client, resource_group_name, name, set_string=None, enabl runtime_version=None, config_file_path=None, unauthenticated_client_action=None, redirect_provider=None, enable_token_store=None, require_https=None, proxy_convention=None, proxy_custom_host_header=None, - proxy_custom_proto_header=None, excluded_paths=None, slot=None): + proxy_custom_proto_header=None, excluded_paths=None): from ._utils import set_field_in_auth_settings, update_http_settings_in_auth_settings existing_auth = {} try: