From 86bad6e220d4c04a5eac94b4e3e68f82d5da6fe5 Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Tue, 12 Oct 2021 21:37:51 +0100 Subject: [PATCH 1/5] azuread_application: support the `features` block and the `tags` property --- docs/data-sources/application.md | 11 ++ docs/resources/application.md | 16 ++ internal/helpers/applications.go | 60 +++++++ .../applications/application_data_source.go | 46 ++++++ .../application_data_source_test.go | 6 + .../applications/application_resource.go | 68 ++++++++ .../applications/application_resource_test.go | 147 ++++++++++++++++++ .../service_principal_data_source.go | 2 +- .../service_principal_resource.go | 6 +- .../serviceprincipals/serviceprincipals.go | 61 +------- 10 files changed, 359 insertions(+), 64 deletions(-) diff --git a/docs/data-sources/application.md b/docs/data-sources/application.md index 448bb35ea9..89412e0a72 100644 --- a/docs/data-sources/application.md +++ b/docs/data-sources/application.md @@ -48,6 +48,7 @@ The following attributes are exported: * `disabled_by_microsoft` - Whether Microsoft has disabled the registered application. If the application is disabled, this will be a string indicating the status/reason, e.g. `DisabledDueToViolationOfServicesAgreement` * `display_name` - The display name for the application. * `fallback_public_client_enabled` - The fallback application type as public client, such as an installed application running on a mobile device. +* `features` - A `features` block as described below. * `group_membership_claims` - The `groups` claim issued in a user or OAuth 2.0 access token that the app expects. * `identifier_uris` - A list of user-defined URI(s) that uniquely identify a Web application within it's Azure AD tenant, or within a verified custom domain if the application is multi-tenant. * `logo_url` - CDN URL to the application's logo. @@ -64,6 +65,7 @@ The following attributes are exported: * `sign_in_audience` - The Microsoft account types that are supported for the current application. One of `AzureADMyOrg`, `AzureADMultipleOrgs`, `AzureADandPersonalMicrosoftAccount` or `PersonalMicrosoftAccount`. * `single_page_application` - A `single_page_application` block as documented below. * `support_url` - URL of the application's support page. +* `tags` - A list of tags applied to the application. * `terms_of_service_url` - URL of the application's terms of service statement. * `web` - A `web` block as documented below. @@ -102,6 +104,15 @@ The following attributes are exported: --- +`features` block exports the following: + +* `custom_single_sign_on_app` - Whether this application represents a custom SAML application for linked service principals. +* `enterprise_application` - Whether this application represents an Enterprise Application for linked service principals. +* `gallery_application` - Whether this application represents a gallery application for linked service principals. +* `visible_to_users` - Whether this app is visible to users in My Apps and Office 365 Launcher. + +--- + `optional_claims` block exports the following: * `access_token` - One or more `access_token` blocks as documented below. diff --git a/docs/resources/application.md b/docs/resources/application.md index 6331990401..e80d9f5b6c 100644 --- a/docs/resources/application.md +++ b/docs/resources/application.md @@ -78,6 +78,11 @@ resource "azuread_application" "example" { value = "User" } + features { + enterprise_application = true + gallery_application = true + } + optional_claims { access_token { name = "myclaim" @@ -162,6 +167,7 @@ The following arguments are supported: * `device_only_auth_enabled` - (Optional) Specifies whether this application supports device authentication without a user. Defaults to `false`. * `display_name` - (Required) The display name for the application. * `fallback_public_client_enabled` - (Optional) Specifies whether the application is a public client. Appropriate for apps using token grant flows that don't use a redirect URI. Defaults to `false`. +* `features` - (Optional) A `features` block as described below. Cannot be used together with the `tags` property. Features applied to this application will propagate to, and supplement any features/tags configured for, a linked service principal. * `group_membership_claims` - (Optional) Configures the `groups` claim issued in a user or OAuth 2.0 access token that the app expects. Possible values are `None`, `SecurityGroup`, `DirectoryRole`, `ApplicationGroup` or `All`. * `identifier_uris` - (Optional) A set of user-defined URI(s) that uniquely identify an application within its Azure AD tenant, or within a verified custom domain if the application is multi-tenant. * `logo_image` - (Optional) A logo image to upload for the application, as a raw base64-encoded string. The image should be in gif, jpeg or png format. Note that once an image has been uploaded, it is not possible to remove it without replacing it with another image. @@ -182,6 +188,7 @@ The following arguments are supported: * `single_page_application` - (Optional) A `single_page_application` block as documented below, which configures single-page application (SPA) related settings for this application. * `support_url` - (Optional) URL of the application's support page. +* `tags` - (Optional) A set of tags to apply to the application. Cannot be used together with the `features` block. Tags configured for this application will propagate to, and supplement any features/tags configured for, a linked service principal. * `template_id` - (Optional) Unique ID for a templated application in the Azure AD App Gallery, from which to create the application. Changing this forces a new resource to be created. * `terms_of_service_url` - (Optional) URL of the application's terms of service statement. * `web` - (Optional) A `web` block as documented below, which configures web related settings for this application. @@ -235,6 +242,15 @@ The following arguments are supported: --- +`features` block supports the following: + +* `custom_single_sign_on_app` - (Optional) Whether this application represents a custom SAML application for linked service principals. Defaults to `false`. +* `enterprise_application` - (Optional) Whether this application represents an Enterprise Application for linked service principals. Defaults to `false`. +* `gallery_application` - (Optional) Whether this application represents a gallery application for linked service principals. Defaults to `false`. +* `visible_to_users` - (Optional) Whether this app is visible to users in My Apps and Office 365 Launcher. Defaults to `true`. + +--- + `optional_claims` block supports the following: * `access_token` - (Optional) One or more `access_token` blocks as documented below. diff --git a/internal/helpers/applications.go b/internal/helpers/applications.go index dd054bd48e..19381f5c13 100644 --- a/internal/helpers/applications.go +++ b/internal/helpers/applications.go @@ -1,9 +1,39 @@ package helpers import ( + "strings" + "github.com/manicminer/hamilton/msgraph" ) +func ApplicationExpandFeatures(in []interface{}) []string { + out := make([]string, 0) + + if len(in) == 0 || in[0] == nil { + return out + } + + features := in[0].(map[string]interface{}) + + if v, ok := features["custom_single_sign_on_app"]; ok && v.(bool) { + out = append(out, "WindowsAzureActiveDirectoryCustomSingleSignOnApplication") + } + + if v, ok := features["enterprise_application"]; ok && v.(bool) { + out = append(out, "WindowsAzureActiveDirectoryIntegratedApp") + } + + if v, ok := features["gallery_application"]; ok && v.(bool) { + out = append(out, "WindowsAzureActiveDirectoryGalleryApplicationNonPrimaryV1") + } + + if v, ok := features["visible_to_users"]; ok && !v.(bool) { + out = append(out, "HideApp") + } + + return out +} + func ApplicationFlattenAppRoleIDs(in *[]msgraph.AppRole) map[string]string { result := make(map[string]string) if in != nil { @@ -61,6 +91,36 @@ func ApplicationFlattenAppRoles(in *[]msgraph.AppRole) (result []map[string]inte return //nolint:nakedret } +func ApplicationFlattenFeatures(tags *[]string) []interface{} { + result := map[string]bool{ + "custom_single_sign_on_app": false, + "enterprise_application": false, + "gallery_application": false, + "visible_to_users": true, + } + + if tags == nil || len(*tags) == 0 { + return []interface{}{result} + } + + for _, tag := range *tags { + if strings.EqualFold(tag, "WindowsAzureActiveDirectoryCustomSingleSignOnApplication") { + result["custom_single_sign_on_app"] = true + } + if strings.EqualFold(tag, "WindowsAzureActiveDirectoryIntegratedApp") { + result["enterprise_application"] = true + } + if strings.EqualFold(tag, "WindowsAzureActiveDirectoryGalleryApplicationNonPrimaryV1") { + result["gallery_application"] = true + } + if strings.EqualFold(tag, "HideApp") { + result["visible_to_users"] = false + } + } + + return []interface{}{result} +} + func ApplicationFlattenOAuth2PermissionScopeIDs(in *[]msgraph.PermissionScope) map[string]string { result := make(map[string]string) if in != nil { diff --git a/internal/services/applications/application_data_source.go b/internal/services/applications/application_data_source.go index 4dae8ea964..faa71e2597 100644 --- a/internal/services/applications/application_data_source.go +++ b/internal/services/applications/application_data_source.go @@ -13,6 +13,7 @@ import ( "github.com/manicminer/hamilton/odata" "github.com/hashicorp/terraform-provider-azuread/internal/clients" + "github.com/hashicorp/terraform-provider-azuread/internal/helpers" "github.com/hashicorp/terraform-provider-azuread/internal/tf" "github.com/hashicorp/terraform-provider-azuread/internal/validate" ) @@ -214,6 +215,40 @@ func applicationDataSource() *schema.Resource { Computed: true, }, + "features": { + Description: "Block of features configured for this application using tags", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "custom_single_sign_on_app": { + Description: "Whether this application principal represents a custom SAML application for linked service principals", + Type: schema.TypeBool, + Optional: true, + }, + + "enterprise_application": { + Description: "Whether this application represents an Enterprise Application for linked service principals", + Type: schema.TypeBool, + Optional: true, + }, + + "gallery_application": { + Description: "Whether this application represents a gallery application for linked service principals", + Type: schema.TypeBool, + Optional: true, + }, + + "visible_to_users": { + Description: "Whether this app is visible to users in My Apps and Office 365 Launcher", + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + }, + }, + }, + "group_membership_claims": { Description: "The `groups` claim issued in a user or OAuth 2.0 access token that the app expects", Type: schema.TypeList, @@ -373,6 +408,15 @@ func applicationDataSource() *schema.Resource { Computed: true, }, + "tags": { + Description: "A set of tags applied to the application", + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "terms_of_service_url": { Description: "URL of the application's terms of service statement", Type: schema.TypeString, @@ -511,6 +555,7 @@ func applicationDataSourceRead(ctx context.Context, d *schema.ResourceData, meta tf.Set(d, "disabled_by_microsoft", fmt.Sprintf("%v", app.DisabledByMicrosoftStatus)) tf.Set(d, "display_name", app.DisplayName) tf.Set(d, "fallback_public_client_enabled", app.IsFallbackPublicClient) + tf.Set(d, "features", helpers.ApplicationFlattenFeatures(app.Tags)) tf.Set(d, "group_membership_claims", tf.FlattenStringSlicePtr(app.GroupMembershipClaims)) tf.Set(d, "identifier_uris", tf.FlattenStringSlicePtr(app.IdentifierUris)) tf.Set(d, "oauth2_post_response_required", app.Oauth2RequirePostResponse) @@ -521,6 +566,7 @@ func applicationDataSourceRead(ctx context.Context, d *schema.ResourceData, meta tf.Set(d, "required_resource_access", flattenApplicationRequiredResourceAccess(app.RequiredResourceAccess)) tf.Set(d, "sign_in_audience", app.SignInAudience) tf.Set(d, "single_page_application", flattenApplicationSpa(app.Spa)) + tf.Set(d, "tags", app.Tags) tf.Set(d, "web", flattenApplicationWeb(app.Web)) if app.Api != nil { diff --git a/internal/services/applications/application_data_source_test.go b/internal/services/applications/application_data_source_test.go index 5d88ee4466..12028fba2d 100644 --- a/internal/services/applications/application_data_source_test.go +++ b/internal/services/applications/application_data_source_test.go @@ -56,6 +56,11 @@ func (ApplicationDataSource) testCheck(data acceptance.TestData) resource.TestCh check.That(data.ResourceName).Key("app_roles.#").HasValue("2"), check.That(data.ResourceName).Key("app_role_ids.%").HasValue("2"), check.That(data.ResourceName).Key("display_name").HasValue(fmt.Sprintf("acctest-APP-complete-%d", data.RandomInteger)), + check.That(data.ResourceName).Key("features.#").HasValue("1"), + check.That(data.ResourceName).Key("features.0.custom_single_sign_on_app").HasValue("true"), + check.That(data.ResourceName).Key("features.0.enterprise_application").HasValue("true"), + check.That(data.ResourceName).Key("features.0.gallery_application").HasValue("true"), + check.That(data.ResourceName).Key("features.0.visible_to_users").HasValue("true"), check.That(data.ResourceName).Key("group_membership_claims.#").HasValue("1"), check.That(data.ResourceName).Key("group_membership_claims.0").HasValue("All"), check.That(data.ResourceName).Key("identifier_uris.#").HasValue("2"), @@ -65,6 +70,7 @@ func (ApplicationDataSource) testCheck(data acceptance.TestData) resource.TestCh check.That(data.ResourceName).Key("optional_claims.0.id_token.#").HasValue("1"), check.That(data.ResourceName).Key("required_resource_access.#").HasValue("2"), check.That(data.ResourceName).Key("sign_in_audience").HasValue("AzureADandPersonalMicrosoftAccount"), + check.That(data.ResourceName).Key("tags.#").HasValue("3"), check.That(data.ResourceName).Key("web.0.homepage_url").HasValue(fmt.Sprintf("https://app.hashitown-%d.com/", data.RandomInteger)), check.That(data.ResourceName).Key("web.0.logout_url").HasValue(fmt.Sprintf("https://app.hashitown-%[1]d.com/logout", data.RandomInteger)), check.That(data.ResourceName).Key("web.0.redirect_uris.#").HasValue("3"), diff --git a/internal/services/applications/application_resource.go b/internal/services/applications/application_resource.go index fbb6732702..666d06e40d 100644 --- a/internal/services/applications/application_resource.go +++ b/internal/services/applications/application_resource.go @@ -19,6 +19,7 @@ import ( "github.com/manicminer/hamilton/odata" "github.com/hashicorp/terraform-provider-azuread/internal/clients" + "github.com/hashicorp/terraform-provider-azuread/internal/helpers" "github.com/hashicorp/terraform-provider-azuread/internal/services/applications/migrations" applicationsValidate "github.com/hashicorp/terraform-provider-azuread/internal/services/applications/validate" "github.com/hashicorp/terraform-provider-azuread/internal/tf" @@ -269,6 +270,42 @@ func applicationResource() *schema.Resource { Optional: true, }, + "features": { + Description: "Block of features to configure for this application using tags", + Type: schema.TypeList, + Optional: true, + Computed: true, + ConflictsWith: []string{"tags"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "custom_single_sign_on_app": { + Description: "Whether this application principal represents a custom SAML application for linked service principals", + Type: schema.TypeBool, + Optional: true, + }, + + "enterprise_application": { + Description: "Whether this application represents an Enterprise Application for linked service principals", + Type: schema.TypeBool, + Optional: true, + }, + + "gallery_application": { + Description: "Whether this application represents a gallery application for linked service principals", + Type: schema.TypeBool, + Optional: true, + }, + + "visible_to_users": { + Description: "Whether this app is visible to users in My Apps and Office 365 Launcher", + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + }, + }, + }, + "group_membership_claims": { Description: "Configures the `groups` claim issued in a user or OAuth 2.0 access token that the app expects", Type: schema.TypeSet, @@ -460,6 +497,18 @@ func applicationResource() *schema.Resource { Optional: true, }, + "tags": { + Description: "A set of tags to apply to the application", + Type: schema.TypeSet, + Optional: true, + Computed: true, + Set: schema.HashString, + ConflictsWith: []string{"features"}, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "template_id": { Description: "Unique ID of the application template from which this application is created", Type: schema.TypeString, @@ -860,6 +909,13 @@ func applicationResourceCreate(ctx context.Context, d *schema.ResourceData, meta } } + var tags []string + if v, ok := d.GetOk("features"); ok { + tags = helpers.ApplicationExpandFeatures(v.([]interface{})) + } else { + tags = tf.ExpandStringSlice(d.Get("tags").(*schema.Set).List()) + } + if templateId != "" { // Instantiate application from template gallery and return via the update function properties := msgraph.ApplicationTemplate{ @@ -906,6 +962,7 @@ func applicationResourceCreate(ctx context.Context, d *schema.ResourceData, meta RequiredResourceAccess: expandApplicationRequiredResourceAccess(d.Get("required_resource_access").(*schema.Set).List()), SignInAudience: utils.String(d.Get("sign_in_audience").(string)), Spa: expandApplicationSpa(d.Get("single_page_application").([]interface{})), + Tags: &tags, Web: expandApplicationWeb(d.Get("web").([]interface{})), } @@ -1037,6 +1094,14 @@ func applicationResourceUpdate(ctx context.Context, d *schema.ResourceData, meta } } + var tags []string + featuresChanged := d.HasChange("features") + if v, ok := d.GetOk("features"); ok && len(v.([]interface{})) > 0 && featuresChanged { + tags = helpers.ApplicationExpandFeatures(v.([]interface{})) + } else { + tags = tf.ExpandStringSlice(d.Get("tags").(*schema.Set).List()) + } + properties := msgraph.Application{ DirectoryObject: msgraph.DirectoryObject{ ID: utils.String(applicationId), @@ -1060,6 +1125,7 @@ func applicationResourceUpdate(ctx context.Context, d *schema.ResourceData, meta RequiredResourceAccess: expandApplicationRequiredResourceAccess(d.Get("required_resource_access").(*schema.Set).List()), SignInAudience: utils.String(d.Get("sign_in_audience").(string)), Spa: expandApplicationSpa(d.Get("single_page_application").([]interface{})), + Tags: &tags, Web: expandApplicationWeb(d.Get("web").([]interface{})), } @@ -1152,6 +1218,7 @@ func applicationResourceRead(ctx context.Context, d *schema.ResourceData, meta i tf.Set(d, "disabled_by_microsoft", fmt.Sprintf("%v", app.DisabledByMicrosoftStatus)) tf.Set(d, "display_name", app.DisplayName) tf.Set(d, "fallback_public_client_enabled", app.IsFallbackPublicClient) + tf.Set(d, "features", helpers.ApplicationFlattenFeatures(app.Tags)) tf.Set(d, "group_membership_claims", tf.FlattenStringSlicePtr(app.GroupMembershipClaims)) tf.Set(d, "identifier_uris", tf.FlattenStringSlicePtr(app.IdentifierUris)) tf.Set(d, "oauth2_post_response_required", app.Oauth2RequirePostResponse) @@ -1162,6 +1229,7 @@ func applicationResourceRead(ctx context.Context, d *schema.ResourceData, meta i tf.Set(d, "required_resource_access", flattenApplicationRequiredResourceAccess(app.RequiredResourceAccess)) tf.Set(d, "sign_in_audience", app.SignInAudience) tf.Set(d, "single_page_application", flattenApplicationSpa(app.Spa)) + tf.Set(d, "tags", app.Tags) tf.Set(d, "template_id", app.ApplicationTemplateId) tf.Set(d, "web", flattenApplicationWeb(app.Web)) diff --git a/internal/services/applications/application_resource_test.go b/internal/services/applications/application_resource_test.go index 842df7ae53..1e9e8c2286 100644 --- a/internal/services/applications/application_resource_test.go +++ b/internal/services/applications/application_resource_test.go @@ -441,6 +441,92 @@ func TestAccApplication_related(t *testing.T) { }) } +func TestAccApplication_features(t *testing.T) { + data := acceptance.BuildTestData(t, "azuread_application", "test") + r := ApplicationResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.features(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccApplication_featuresUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azuread_application", "test") + r := ApplicationResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.noFeatures(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.features(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.features(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.tags(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.features(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.noFeatures(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.features(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func TestAccApplication_logo(t *testing.T) { data := acceptance.BuildTestData(t, "azuread_application", "test") r := ApplicationResource{} @@ -668,6 +754,12 @@ resource "azuread_application" "test" { ] } + tags = [ + "WindowsAzureActiveDirectoryCustomSingleSignOnApplication", + "WindowsAzureActiveDirectoryIntegratedApp", + "WindowsAzureActiveDirectoryGalleryApplicationNonPrimaryV1", + ] + web { homepage_url = "https://app.hashitown-%[1]d.com/" logout_url = "https://app.hashitown-%[1]d.com/logout" @@ -824,6 +916,12 @@ resource "azuread_application" "test" { ] } + tags = [ + "WindowsAzureActiveDirectoryCustomSingleSignOnApplication", + "WindowsAzureActiveDirectoryIntegratedApp", + "WindowsAzureActiveDirectoryGalleryApplicationNonPrimaryV1", + ] + web { homepage_url = "https://app.templatetown-%[1]d.com/" logout_url = "https://app.templatetown-%[1]d.com/logout" @@ -1302,6 +1400,55 @@ resource "azuread_application" "test" { `, data.RandomInteger) } +func (r ApplicationResource) features(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azuread" {} + +resource "azuread_application" "test" { + display_name = "acctest-APP-%[1]d" + + features { + custom_single_sign_on_app = true + enterprise_application = true + gallery_application = true + visible_to_users = false + } +} +`, data.RandomInteger) +} + +func (r ApplicationResource) noFeatures(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azuread" {} + +resource "azuread_application" "test" { + display_name = "acctest-APP-%[1]d" + + features { + custom_single_sign_on_app = false + enterprise_application = false + gallery_application = false + } +} +`, data.RandomInteger) +} + +func (r ApplicationResource) tags(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azuread" {} + +resource "azuread_application" "test" { + display_name = "acctest-APP-%[1]d" + + tags = [ + "WindowsAzureActiveDirectoryCustomSingleSignOnApplication", + "WindowsAzureActiveDirectoryIntegratedApp", + "WindowsAzureActiveDirectoryGalleryApplicationNonPrimaryV1", + ] +} +`, data.RandomInteger) +} + func (r ApplicationResource) logo(data acceptance.TestData) string { return fmt.Sprintf(` provider "azuread" {} diff --git a/internal/services/serviceprincipals/service_principal_data_source.go b/internal/services/serviceprincipals/service_principal_data_source.go index 508ee63ae3..a74213a5a5 100644 --- a/internal/services/serviceprincipals/service_principal_data_source.go +++ b/internal/services/serviceprincipals/service_principal_data_source.go @@ -346,7 +346,7 @@ func servicePrincipalDataSourceRead(ctx context.Context, d *schema.ResourceData, tf.Set(d, "application_tenant_id", servicePrincipal.AppOwnerOrganizationId) tf.Set(d, "description", servicePrincipal.Description) tf.Set(d, "display_name", servicePrincipal.DisplayName) - tf.Set(d, "features", flattenFeatures(servicePrincipal.Tags)) + tf.Set(d, "features", helpers.ApplicationFlattenFeatures(servicePrincipal.Tags)) tf.Set(d, "homepage_url", servicePrincipal.Homepage) tf.Set(d, "logout_url", servicePrincipal.LogoutUrl) tf.Set(d, "login_url", servicePrincipal.LoginUrl) diff --git a/internal/services/serviceprincipals/service_principal_resource.go b/internal/services/serviceprincipals/service_principal_resource.go index ef7ae84b77..4c6f44dd31 100644 --- a/internal/services/serviceprincipals/service_principal_resource.go +++ b/internal/services/serviceprincipals/service_principal_resource.go @@ -348,7 +348,7 @@ func servicePrincipalResourceCreate(ctx context.Context, d *schema.ResourceData, var tags []string if v, ok := d.GetOk("features"); ok { - tags = expandFeatures(v.([]interface{})) + tags = helpers.ApplicationExpandFeatures(v.([]interface{})) } else { tags = tf.ExpandStringSlice(d.Get("tags").(*schema.Set).List()) } @@ -458,7 +458,7 @@ func servicePrincipalResourceUpdate(ctx context.Context, d *schema.ResourceData, var tags []string featuresChanged := d.HasChange("features") if v, ok := d.GetOk("features"); ok && len(v.([]interface{})) > 0 && featuresChanged { - tags = expandFeatures(v.([]interface{})) + tags = helpers.ApplicationExpandFeatures(v.([]interface{})) } else { tags = tf.ExpandStringSlice(d.Get("tags").(*schema.Set).List()) } @@ -563,7 +563,7 @@ func servicePrincipalResourceRead(ctx context.Context, d *schema.ResourceData, m tf.Set(d, "application_tenant_id", servicePrincipal.AppOwnerOrganizationId) tf.Set(d, "description", servicePrincipal.Description) tf.Set(d, "display_name", servicePrincipal.DisplayName) - tf.Set(d, "features", flattenFeatures(servicePrincipal.Tags)) + tf.Set(d, "features", helpers.ApplicationFlattenFeatures(servicePrincipal.Tags)) tf.Set(d, "homepage_url", servicePrincipal.Homepage) tf.Set(d, "logout_url", servicePrincipal.LogoutUrl) tf.Set(d, "login_url", servicePrincipal.LoginUrl) diff --git a/internal/services/serviceprincipals/serviceprincipals.go b/internal/services/serviceprincipals/serviceprincipals.go index 761142e4e5..7d6fbf24ae 100644 --- a/internal/services/serviceprincipals/serviceprincipals.go +++ b/internal/services/serviceprincipals/serviceprincipals.go @@ -1,40 +1,11 @@ package serviceprincipals import ( - "strings" + "github.com/manicminer/hamilton/msgraph" "github.com/hashicorp/terraform-provider-azuread/internal/utils" - "github.com/manicminer/hamilton/msgraph" ) -func expandFeatures(in []interface{}) []string { - out := make([]string, 0) - - if len(in) == 0 || in[0] == nil { - return out - } - - features := in[0].(map[string]interface{}) - - if v, ok := features["custom_single_sign_on_app"]; ok && v.(bool) { - out = append(out, "WindowsAzureActiveDirectoryCustomSingleSignOnApplication") - } - - if v, ok := features["enterprise_application"]; ok && v.(bool) { - out = append(out, "WindowsAzureActiveDirectoryIntegratedApp") - } - - if v, ok := features["gallery_application"]; ok && v.(bool) { - out = append(out, "WindowsAzureActiveDirectoryGalleryApplicationNonPrimaryV1") - } - - if v, ok := features["visible_to_users"]; ok && !v.(bool) { - out = append(out, "HideApp") - } - - return out -} - func expandSamlSingleSignOn(in []interface{}) *msgraph.SamlSingleSignOnSettings { result := msgraph.SamlSingleSignOnSettings{} if len(in) == 0 || in[0] == nil { @@ -48,36 +19,6 @@ func expandSamlSingleSignOn(in []interface{}) *msgraph.SamlSingleSignOnSettings return &result } -func flattenFeatures(tags *[]string) []interface{} { - result := map[string]bool{ - "custom_single_sign_on_app": false, - "enterprise_application": false, - "gallery_application": false, - "visible_to_users": true, - } - - if tags == nil || len(*tags) == 0 { - return []interface{}{result} - } - - for _, tag := range *tags { - if strings.EqualFold(tag, "WindowsAzureActiveDirectoryCustomSingleSignOnApplication") { - result["custom_single_sign_on_app"] = true - } - if strings.EqualFold(tag, "WindowsAzureActiveDirectoryIntegratedApp") { - result["enterprise_application"] = true - } - if strings.EqualFold(tag, "WindowsAzureActiveDirectoryGalleryApplicationNonPrimaryV1") { - result["gallery_application"] = true - } - if strings.EqualFold(tag, "HideApp") { - result["visible_to_users"] = false - } - } - - return []interface{}{result} -} - func flattenSamlSingleSignOn(in *msgraph.SamlSingleSignOnSettings) []map[string]interface{} { if in == nil { return []map[string]interface{}{} From 30a449805766999067f43a6f247748f6964a4f73 Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Thu, 14 Oct 2021 19:03:23 +0100 Subject: [PATCH 2/5] Info boxes for features/tags properties --- docs/resources/application.md | 18 ++++++++++++------ docs/resources/service_principal.md | 14 ++++++++++---- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/docs/resources/application.md b/docs/resources/application.md index e80d9f5b6c..c0d708f34d 100644 --- a/docs/resources/application.md +++ b/docs/resources/application.md @@ -167,7 +167,10 @@ The following arguments are supported: * `device_only_auth_enabled` - (Optional) Specifies whether this application supports device authentication without a user. Defaults to `false`. * `display_name` - (Required) The display name for the application. * `fallback_public_client_enabled` - (Optional) Specifies whether the application is a public client. Appropriate for apps using token grant flows that don't use a redirect URI. Defaults to `false`. -* `features` - (Optional) A `features` block as described below. Cannot be used together with the `tags` property. Features applied to this application will propagate to, and supplement any features/tags configured for, a linked service principal. +* `features` - (Optional) A `features` block as described below. Cannot be used together with the `tags` property. + +-> **Features and Tags** Features are configured for an application using tags, and are provided as a shortcut to set the corresponding magic tag value for each feature. You cannot configure features and tags for an application at the same time, so if you need to assign additional custom tags it's recommended to use the `tags` property instead. Tag values also propagate to any linked service principals. + * `group_membership_claims` - (Optional) Configures the `groups` claim issued in a user or OAuth 2.0 access token that the app expects. Possible values are `None`, `SecurityGroup`, `DirectoryRole`, `ApplicationGroup` or `All`. * `identifier_uris` - (Optional) A set of user-defined URI(s) that uniquely identify an application within its Azure AD tenant, or within a verified custom domain if the application is multi-tenant. * `logo_image` - (Optional) A logo image to upload for the application, as a raw base64-encoded string. The image should be in gif, jpeg or png format. Note that once an image has been uploaded, it is not possible to remove it without replacing it with another image. @@ -188,7 +191,10 @@ The following arguments are supported: * `single_page_application` - (Optional) A `single_page_application` block as documented below, which configures single-page application (SPA) related settings for this application. * `support_url` - (Optional) URL of the application's support page. -* `tags` - (Optional) A set of tags to apply to the application. Cannot be used together with the `features` block. Tags configured for this application will propagate to, and supplement any features/tags configured for, a linked service principal. +* `tags` - (Optional) A set of tags to apply to the application. Cannot be used together with the `features` block. + +-> **Tags and Features** Azure Active Directory uses special tag values to configure the behavior of applications. These can be specified using either the `tags` property or with the `features` block. If you need to set any custom tag values not supported by the `features` block, it's recommended to use the `tags` property. Tag values also propagate to any linked service principals. + * `template_id` - (Optional) Unique ID for a templated application in the Azure AD App Gallery, from which to create the application. Changing this forces a new resource to be created. * `terms_of_service_url` - (Optional) URL of the application's terms of service statement. * `web` - (Optional) A `web` block as documented below, which configures web related settings for this application. @@ -244,10 +250,10 @@ The following arguments are supported: `features` block supports the following: -* `custom_single_sign_on_app` - (Optional) Whether this application represents a custom SAML application for linked service principals. Defaults to `false`. -* `enterprise_application` - (Optional) Whether this application represents an Enterprise Application for linked service principals. Defaults to `false`. -* `gallery_application` - (Optional) Whether this application represents a gallery application for linked service principals. Defaults to `false`. -* `visible_to_users` - (Optional) Whether this app is visible to users in My Apps and Office 365 Launcher. Defaults to `true`. +* `custom_single_sign_on_app` - (Optional) Whether this application represents a custom SAML application for linked service principals. Enabling this will assign the `WindowsAzureActiveDirectoryCustomSingleSignOnApplication` tag. Defaults to `false`. +* `enterprise_application` - (Optional) Whether this application represents an Enterprise Application for linked service principals. Enabling this will assign the `WindowsAzureActiveDirectoryIntegratedApp` tag. Defaults to `false`. +* `gallery_application` - (Optional) Whether this application represents a gallery application for linked service principals. Enabling this will assign the `WindowsAzureActiveDirectoryGalleryApplicationNonPrimaryV1` tag. Defaults to `false`. +* `visible_to_users` - (Optional) Whether this app is visible to users in My Apps and Office 365 Launcher. Disabling this will assign the `HideApp` tag. Defaults to `true`. --- diff --git a/docs/resources/service_principal.md b/docs/resources/service_principal.md index 2b8259d1d1..8dc68b9478 100644 --- a/docs/resources/service_principal.md +++ b/docs/resources/service_principal.md @@ -96,6 +96,9 @@ The following arguments are supported: * `application_id` - (Required) The application ID (client ID) of the application for which to create a service principal. * `description` - (Optional) A description of the service principal provided for internal end-users. * `features` - (Optional) A `features` block as described below. Cannot be used together with the `tags` property. + +-> **Features and Tags** Features are configured for a service principal using tags, and are provided as a shortcut to set the corresponding magic tag value for each feature. You cannot configure features and tags for a service principal at the same time, so if you need to assign additional custom tags it's recommended to use the `tags` property instead. Any features or tags configured for the linked application will propagate to this service principal. + * `login_url` - (Optional) The URL where the service provider redirects the user to Azure AD to authenticate. Azure AD uses the URL to launch the application from Microsoft 365 or the Azure AD My Apps. When blank, Azure AD performs IdP-initiated sign-on for applications configured with SAML-based single sign-on. * `notes` - (Optional) A free text field to capture information about the service principal, typically used for operational purposes. * `notification_email_addresses` - (Optional) A set of email addresses where Azure AD sends a notification when the active certificate is near the expiration date. This is only for the certificates used to sign the SAML token issued for Azure AD Gallery applications. @@ -106,6 +109,9 @@ The following arguments are supported: * `preferred_single_sign_on_mode` - (Optional) The single sign-on mode configured for this application. Azure AD uses the preferred single sign-on mode to launch the application from Microsoft 365 or the Azure AD My Apps. Supported values are `oidc`, `password`, `saml` or `notSupported`. Omit this property or specify a blank string to unset. * `saml_single_sign_on` - (Optional) A `saml_single_sign_on` block as documented below. * `tags` - (Optional) A set of tags to apply to the service principal. Cannot be used together with the `features` block. + +-> **Tags and Features** Azure Active Directory uses special tag values to configure the behavior of service principals. These can be specified using either the `tags` property or with the `features` block. If you need to set any custom tag values not supported by the `features` block, it's recommended to use the `tags` property. Tag values set for the linked application will also propagate to this service principal. + * `use_existing` - (Optional) When true, any existing service principal linked to the same application will be automatically imported. When false, an import error will be raised for any pre-existing service principal. -> **Caveats of `use_existing`** Enabling this behaviour is useful for managing existing service principals that may already be installed in your tenant for Microsoft-published APIs, as it allows you to make changes where permitted, and then also reference them in your Terraform configuration. However, the behaviour of delete operations is also affected - when `use_existing` is `true`, Terraform will still attempt to delete the service principal on destroy, although it will not raise an error if the deletion fails (as it often the case for first-party Microsoft applications). @@ -114,10 +120,10 @@ The following arguments are supported: `features` block supports the following: -* `custom_single_sign_on_app` - (Optional) Whether this service principal represents a custom SAML application. Defaults to `false`. -* `enterprise_application` - (Optional) Whether this service principal represents an Enterprise Application. Defaults to `false`. -* `gallery_application` - (Optional) Whether this service principal represents a gallery application. Defaults to `false`. -* `visible_to_users` - (Optional) Whether this app is visible to users in My Apps and Office 365 Launcher. Defaults to `true`. +* `custom_single_sign_on_app` - (Optional) Whether this service principal represents a custom SAML application. Enabling this will assign the `WindowsAzureActiveDirectoryCustomSingleSignOnApplication` tag. Defaults to `false`. +* `enterprise_application` - (Optional) Whether this service principal represents an Enterprise Application. Enabling this will assign the `WindowsAzureActiveDirectoryIntegratedApp` tag. Defaults to `false`. +* `gallery_application` - (Optional) Whether this service principal represents a gallery application. Enabling this will assign the `WindowsAzureActiveDirectoryGalleryApplicationNonPrimaryV1` tag. Defaults to `false`. +* `visible_to_users` - (Optional) Whether this app is visible to users in My Apps and Office 365 Launcher. Disabling this will assign the `HideApp` tag. Defaults to `true`. --- From e7b45411442e8d8b590a05121c6ea76eae474c8e Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Thu, 14 Oct 2021 23:34:12 +0100 Subject: [PATCH 3/5] Rename `features` to `feature_tags` - Deprecate the `features` block for the `azuread_service_principal` resource / data source - Make all features enabling (change `visible_to_users` to `hide`) so that tag values are always set by enabling a feature --- docs/resources/application.md | 24 +++---- docs/resources/service_principal.md | 24 +++---- internal/helpers/applications.go | 65 +++++++++++++++---- .../applications/application_data_source.go | 14 ++-- .../application_data_source_test.go | 12 ++-- .../applications/application_resource.go | 24 ++++--- .../applications/application_resource_test.go | 42 ++++++------ .../service_principal_data_source.go | 37 ++++++++++- .../service_principal_data_source_test.go | 12 ++-- .../service_principal_resource.go | 52 +++++++++++++-- .../service_principal_resource_test.go | 54 +++++++-------- 11 files changed, 232 insertions(+), 128 deletions(-) diff --git a/docs/resources/application.md b/docs/resources/application.md index c0d708f34d..5b0fabbf8a 100644 --- a/docs/resources/application.md +++ b/docs/resources/application.md @@ -78,9 +78,9 @@ resource "azuread_application" "example" { value = "User" } - features { - enterprise_application = true - gallery_application = true + feature_tags { + enterprise = true + gallery = true } optional_claims { @@ -167,9 +167,9 @@ The following arguments are supported: * `device_only_auth_enabled` - (Optional) Specifies whether this application supports device authentication without a user. Defaults to `false`. * `display_name` - (Required) The display name for the application. * `fallback_public_client_enabled` - (Optional) Specifies whether the application is a public client. Appropriate for apps using token grant flows that don't use a redirect URI. Defaults to `false`. -* `features` - (Optional) A `features` block as described below. Cannot be used together with the `tags` property. +* `feature_tags` - (Optional) A `feature_tags` block as described below. Cannot be used together with the `tags` property. --> **Features and Tags** Features are configured for an application using tags, and are provided as a shortcut to set the corresponding magic tag value for each feature. You cannot configure features and tags for an application at the same time, so if you need to assign additional custom tags it's recommended to use the `tags` property instead. Tag values also propagate to any linked service principals. +-> **Features and Tags** Features are configured for an application using tags, and are provided as a shortcut to set the corresponding magic tag value for each feature. You cannot configure `feature_tags` and `tags` for an application at the same time, so if you need to assign additional custom tags it's recommended to use the `tags` property instead. Tag values also propagate to any linked service principals. * `group_membership_claims` - (Optional) Configures the `groups` claim issued in a user or OAuth 2.0 access token that the app expects. Possible values are `None`, `SecurityGroup`, `DirectoryRole`, `ApplicationGroup` or `All`. * `identifier_uris` - (Optional) A set of user-defined URI(s) that uniquely identify an application within its Azure AD tenant, or within a verified custom domain if the application is multi-tenant. @@ -191,9 +191,9 @@ The following arguments are supported: * `single_page_application` - (Optional) A `single_page_application` block as documented below, which configures single-page application (SPA) related settings for this application. * `support_url` - (Optional) URL of the application's support page. -* `tags` - (Optional) A set of tags to apply to the application. Cannot be used together with the `features` block. +* `tags` - (Optional) A set of tags to apply to the application. Cannot be used together with the `feature_tags` block. --> **Tags and Features** Azure Active Directory uses special tag values to configure the behavior of applications. These can be specified using either the `tags` property or with the `features` block. If you need to set any custom tag values not supported by the `features` block, it's recommended to use the `tags` property. Tag values also propagate to any linked service principals. +-> **Tags and Features** Azure Active Directory uses special tag values to configure the behavior of applications. These can be specified using either the `tags` property or with the `feature_tags` block. If you need to set any custom tag values not supported by the `feature_tags` block, it's recommended to use the `tags` property. Tag values also propagate to any linked service principals. * `template_id` - (Optional) Unique ID for a templated application in the Azure AD App Gallery, from which to create the application. Changing this forces a new resource to be created. * `terms_of_service_url` - (Optional) URL of the application's terms of service statement. @@ -248,12 +248,12 @@ The following arguments are supported: --- -`features` block supports the following: +`feature_tags` block supports the following: -* `custom_single_sign_on_app` - (Optional) Whether this application represents a custom SAML application for linked service principals. Enabling this will assign the `WindowsAzureActiveDirectoryCustomSingleSignOnApplication` tag. Defaults to `false`. -* `enterprise_application` - (Optional) Whether this application represents an Enterprise Application for linked service principals. Enabling this will assign the `WindowsAzureActiveDirectoryIntegratedApp` tag. Defaults to `false`. -* `gallery_application` - (Optional) Whether this application represents a gallery application for linked service principals. Enabling this will assign the `WindowsAzureActiveDirectoryGalleryApplicationNonPrimaryV1` tag. Defaults to `false`. -* `visible_to_users` - (Optional) Whether this app is visible to users in My Apps and Office 365 Launcher. Disabling this will assign the `HideApp` tag. Defaults to `true`. +* `custom_single_sign_on` - (Optional) Whether this application represents a custom SAML application for linked service principals. Enabling this will assign the `WindowsAzureActiveDirectoryCustomSingleSignOnApplication` tag. Defaults to `false`. +* `enterprise` - (Optional) Whether this application represents an Enterprise Application for linked service principals. Enabling this will assign the `WindowsAzureActiveDirectoryIntegratedApp` tag. Defaults to `false`. +* `gallery` - (Optional) Whether this application represents a gallery application for linked service principals. Enabling this will assign the `WindowsAzureActiveDirectoryGalleryApplicationNonPrimaryV1` tag. Defaults to `false`. +* `hide` - (Optional) Whether this app is invisible to users in My Apps and Office 365 Launcher. Enabling this will assign the `HideApp` tag. Defaults to `false`. --- diff --git a/docs/resources/service_principal.md b/docs/resources/service_principal.md index 8dc68b9478..c195c4365e 100644 --- a/docs/resources/service_principal.md +++ b/docs/resources/service_principal.md @@ -50,9 +50,9 @@ resource "azuread_service_principal" "example" { app_role_assignment_required = false owners = [data.azuread_client_config.current.object_id] - features { - enterprise_application = true - gallery_application = true + feature_tags { + enterprise = true + gallery = true } } ``` @@ -95,9 +95,9 @@ The following arguments are supported: * `app_role_assignment_required` - (Optional) Whether this service principal requires an app role assignment to a user or group before Azure AD will issue a user or access token to the application. Defaults to `false`. * `application_id` - (Required) The application ID (client ID) of the application for which to create a service principal. * `description` - (Optional) A description of the service principal provided for internal end-users. -* `features` - (Optional) A `features` block as described below. Cannot be used together with the `tags` property. +* `feature_tags` - (Optional) A `feature_tags` block as described below. Cannot be used together with the `tags` property. --> **Features and Tags** Features are configured for a service principal using tags, and are provided as a shortcut to set the corresponding magic tag value for each feature. You cannot configure features and tags for a service principal at the same time, so if you need to assign additional custom tags it's recommended to use the `tags` property instead. Any features or tags configured for the linked application will propagate to this service principal. +-> **Features and Tags** Features are configured for a service principal using tags, and are provided as a shortcut to set the corresponding magic tag value for each feature. You cannot configure `feature_tags` and `tags` for a service principal at the same time, so if you need to assign additional custom tags it's recommended to use the `tags` property instead. Any tags configured for the linked application will propagate to this service principal. * `login_url` - (Optional) The URL where the service provider redirects the user to Azure AD to authenticate. Azure AD uses the URL to launch the application from Microsoft 365 or the Azure AD My Apps. When blank, Azure AD performs IdP-initiated sign-on for applications configured with SAML-based single sign-on. * `notes` - (Optional) A free text field to capture information about the service principal, typically used for operational purposes. @@ -108,9 +108,9 @@ The following arguments are supported: * `preferred_single_sign_on_mode` - (Optional) The single sign-on mode configured for this application. Azure AD uses the preferred single sign-on mode to launch the application from Microsoft 365 or the Azure AD My Apps. Supported values are `oidc`, `password`, `saml` or `notSupported`. Omit this property or specify a blank string to unset. * `saml_single_sign_on` - (Optional) A `saml_single_sign_on` block as documented below. -* `tags` - (Optional) A set of tags to apply to the service principal. Cannot be used together with the `features` block. +* `tags` - (Optional) A set of tags to apply to the service principal. Cannot be used together with the `feature_tags` block. --> **Tags and Features** Azure Active Directory uses special tag values to configure the behavior of service principals. These can be specified using either the `tags` property or with the `features` block. If you need to set any custom tag values not supported by the `features` block, it's recommended to use the `tags` property. Tag values set for the linked application will also propagate to this service principal. +-> **Tags and Features** Azure Active Directory uses special tag values to configure the behavior of service principals. These can be specified using either the `tags` property or with the `feature_tags` block. If you need to set any custom tag values not supported by the `feature_tags` block, it's recommended to use the `tags` property. Tag values set for the linked application will also propagate to this service principal. * `use_existing` - (Optional) When true, any existing service principal linked to the same application will be automatically imported. When false, an import error will be raised for any pre-existing service principal. @@ -118,12 +118,12 @@ The following arguments are supported: --- -`features` block supports the following: +`feature_tags` block supports the following: -* `custom_single_sign_on_app` - (Optional) Whether this service principal represents a custom SAML application. Enabling this will assign the `WindowsAzureActiveDirectoryCustomSingleSignOnApplication` tag. Defaults to `false`. -* `enterprise_application` - (Optional) Whether this service principal represents an Enterprise Application. Enabling this will assign the `WindowsAzureActiveDirectoryIntegratedApp` tag. Defaults to `false`. -* `gallery_application` - (Optional) Whether this service principal represents a gallery application. Enabling this will assign the `WindowsAzureActiveDirectoryGalleryApplicationNonPrimaryV1` tag. Defaults to `false`. -* `visible_to_users` - (Optional) Whether this app is visible to users in My Apps and Office 365 Launcher. Disabling this will assign the `HideApp` tag. Defaults to `true`. +* `custom_single_sign_on` - (Optional) Whether this service principal represents a custom SAML application. Enabling this will assign the `WindowsAzureActiveDirectoryCustomSingleSignOnApplication` tag. Defaults to `false`. +* `enterprise` - (Optional) Whether this service principal represents an Enterprise Application. Enabling this will assign the `WindowsAzureActiveDirectoryIntegratedApp` tag. Defaults to `false`. +* `gallery` - (Optional) Whether this service principal represents a gallery application. Enabling this will assign the `WindowsAzureActiveDirectoryGalleryApplicationNonPrimaryV1` tag. Defaults to `false`. +* `hide` - (Optional) Whether this app is invisible to users in My Apps and Office 365 Launcher. Enabling this will assign the `HideApp` tag. Defaults to `false`. --- diff --git a/internal/helpers/applications.go b/internal/helpers/applications.go index 19381f5c13..3408e03866 100644 --- a/internal/helpers/applications.go +++ b/internal/helpers/applications.go @@ -15,19 +15,27 @@ func ApplicationExpandFeatures(in []interface{}) []string { features := in[0].(map[string]interface{}) - if v, ok := features["custom_single_sign_on_app"]; ok && v.(bool) { + if v, ok := features["custom_single_sign_on"]; ok && v.(bool) { + out = append(out, "WindowsAzureActiveDirectoryCustomSingleSignOnApplication") + } else if v, ok := features["custom_single_sign_on_app"]; ok && v.(bool) { out = append(out, "WindowsAzureActiveDirectoryCustomSingleSignOnApplication") } - if v, ok := features["enterprise_application"]; ok && v.(bool) { + if v, ok := features["enterprise"]; ok && v.(bool) { + out = append(out, "WindowsAzureActiveDirectoryIntegratedApp") + } else if v, ok := features["enterprise_application"]; ok && v.(bool) { // TODO: remove in v3.0 out = append(out, "WindowsAzureActiveDirectoryIntegratedApp") } - if v, ok := features["gallery_application"]; ok && v.(bool) { + if v, ok := features["gallery"]; ok && v.(bool) { + out = append(out, "WindowsAzureActiveDirectoryGalleryApplicationNonPrimaryV1") + } else if v, ok := features["gallery_application"]; ok && v.(bool) { // TODO: remove in v3.0 out = append(out, "WindowsAzureActiveDirectoryGalleryApplicationNonPrimaryV1") } - if v, ok := features["visible_to_users"]; ok && !v.(bool) { + if v, ok := features["hide"]; ok && v.(bool) { + out = append(out, "HideApp") + } else if v, ok := features["visible_to_users"]; ok && !v.(bool) { // TODO: remove in v3.0 out = append(out, "HideApp") } @@ -91,12 +99,43 @@ func ApplicationFlattenAppRoles(in *[]msgraph.AppRole) (result []map[string]inte return //nolint:nakedret } -func ApplicationFlattenFeatures(tags *[]string) []interface{} { +func ApplicationFlattenFeatures(tags *[]string, deprecated bool) []interface{} { + // TODO: remove this in v3.0 + if deprecated { + result := map[string]bool{ + "custom_single_sign_on_app": false, + "enterprise_application": false, + "gallery_application": false, + "visible_to_users": true, + } + + if tags == nil || len(*tags) == 0 { + return []interface{}{result} + } + + for _, tag := range *tags { + if strings.EqualFold(tag, "WindowsAzureActiveDirectoryCustomSingleSignOnApplication") { + result["custom_single_sign_on_app"] = true + } + if strings.EqualFold(tag, "WindowsAzureActiveDirectoryIntegratedApp") { + result["enterprise_application"] = true + } + if strings.EqualFold(tag, "WindowsAzureActiveDirectoryGalleryApplicationNonPrimaryV1") { + result["gallery_application"] = true + } + if strings.EqualFold(tag, "HideApp") { + result["visible_to_users"] = false + } + } + + return []interface{}{result} + } + result := map[string]bool{ - "custom_single_sign_on_app": false, - "enterprise_application": false, - "gallery_application": false, - "visible_to_users": true, + "custom_single_sign_on": false, + "enterprise": false, + "gallery": false, + "hide": false, } if tags == nil || len(*tags) == 0 { @@ -105,16 +144,16 @@ func ApplicationFlattenFeatures(tags *[]string) []interface{} { for _, tag := range *tags { if strings.EqualFold(tag, "WindowsAzureActiveDirectoryCustomSingleSignOnApplication") { - result["custom_single_sign_on_app"] = true + result["custom_single_sign_on"] = true } if strings.EqualFold(tag, "WindowsAzureActiveDirectoryIntegratedApp") { - result["enterprise_application"] = true + result["enterprise"] = true } if strings.EqualFold(tag, "WindowsAzureActiveDirectoryGalleryApplicationNonPrimaryV1") { - result["gallery_application"] = true + result["gallery"] = true } if strings.EqualFold(tag, "HideApp") { - result["visible_to_users"] = false + result["hide"] = true } } diff --git a/internal/services/applications/application_data_source.go b/internal/services/applications/application_data_source.go index faa71e2597..2279e34dde 100644 --- a/internal/services/applications/application_data_source.go +++ b/internal/services/applications/application_data_source.go @@ -215,32 +215,32 @@ func applicationDataSource() *schema.Resource { Computed: true, }, - "features": { + "feature_tags": { Description: "Block of features configured for this application using tags", Type: schema.TypeList, Computed: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "custom_single_sign_on_app": { + "custom_single_sign_on": { Description: "Whether this application principal represents a custom SAML application for linked service principals", Type: schema.TypeBool, Optional: true, }, - "enterprise_application": { + "enterprise": { Description: "Whether this application represents an Enterprise Application for linked service principals", Type: schema.TypeBool, Optional: true, }, - "gallery_application": { + "gallery": { Description: "Whether this application represents a gallery application for linked service principals", Type: schema.TypeBool, Optional: true, }, - "visible_to_users": { - Description: "Whether this app is visible to users in My Apps and Office 365 Launcher", + "hide": { + Description: "Whether this app is invisible to users in My Apps and Office 365 Launcher", Type: schema.TypeBool, Optional: true, Default: true, @@ -555,7 +555,7 @@ func applicationDataSourceRead(ctx context.Context, d *schema.ResourceData, meta tf.Set(d, "disabled_by_microsoft", fmt.Sprintf("%v", app.DisabledByMicrosoftStatus)) tf.Set(d, "display_name", app.DisplayName) tf.Set(d, "fallback_public_client_enabled", app.IsFallbackPublicClient) - tf.Set(d, "features", helpers.ApplicationFlattenFeatures(app.Tags)) + tf.Set(d, "feature_tags", helpers.ApplicationFlattenFeatures(app.Tags, false)) tf.Set(d, "group_membership_claims", tf.FlattenStringSlicePtr(app.GroupMembershipClaims)) tf.Set(d, "identifier_uris", tf.FlattenStringSlicePtr(app.IdentifierUris)) tf.Set(d, "oauth2_post_response_required", app.Oauth2RequirePostResponse) diff --git a/internal/services/applications/application_data_source_test.go b/internal/services/applications/application_data_source_test.go index 12028fba2d..579428dbb4 100644 --- a/internal/services/applications/application_data_source_test.go +++ b/internal/services/applications/application_data_source_test.go @@ -56,11 +56,11 @@ func (ApplicationDataSource) testCheck(data acceptance.TestData) resource.TestCh check.That(data.ResourceName).Key("app_roles.#").HasValue("2"), check.That(data.ResourceName).Key("app_role_ids.%").HasValue("2"), check.That(data.ResourceName).Key("display_name").HasValue(fmt.Sprintf("acctest-APP-complete-%d", data.RandomInteger)), - check.That(data.ResourceName).Key("features.#").HasValue("1"), - check.That(data.ResourceName).Key("features.0.custom_single_sign_on_app").HasValue("true"), - check.That(data.ResourceName).Key("features.0.enterprise_application").HasValue("true"), - check.That(data.ResourceName).Key("features.0.gallery_application").HasValue("true"), - check.That(data.ResourceName).Key("features.0.visible_to_users").HasValue("true"), + check.That(data.ResourceName).Key("feature_tags.#").HasValue("1"), + check.That(data.ResourceName).Key("feature_tags.0.custom_single_sign_on").HasValue("true"), + check.That(data.ResourceName).Key("feature_tags.0.enterprise").HasValue("true"), + check.That(data.ResourceName).Key("feature_tags.0.gallery").HasValue("true"), + check.That(data.ResourceName).Key("feature_tags.0.hide").HasValue("true"), check.That(data.ResourceName).Key("group_membership_claims.#").HasValue("1"), check.That(data.ResourceName).Key("group_membership_claims.0").HasValue("All"), check.That(data.ResourceName).Key("identifier_uris.#").HasValue("2"), @@ -70,7 +70,7 @@ func (ApplicationDataSource) testCheck(data acceptance.TestData) resource.TestCh check.That(data.ResourceName).Key("optional_claims.0.id_token.#").HasValue("1"), check.That(data.ResourceName).Key("required_resource_access.#").HasValue("2"), check.That(data.ResourceName).Key("sign_in_audience").HasValue("AzureADandPersonalMicrosoftAccount"), - check.That(data.ResourceName).Key("tags.#").HasValue("3"), + check.That(data.ResourceName).Key("tags.#").HasValue("4"), check.That(data.ResourceName).Key("web.0.homepage_url").HasValue(fmt.Sprintf("https://app.hashitown-%d.com/", data.RandomInteger)), check.That(data.ResourceName).Key("web.0.logout_url").HasValue(fmt.Sprintf("https://app.hashitown-%[1]d.com/logout", data.RandomInteger)), check.That(data.ResourceName).Key("web.0.redirect_uris.#").HasValue("3"), diff --git a/internal/services/applications/application_resource.go b/internal/services/applications/application_resource.go index 666d06e40d..2967076950 100644 --- a/internal/services/applications/application_resource.go +++ b/internal/services/applications/application_resource.go @@ -270,7 +270,7 @@ func applicationResource() *schema.Resource { Optional: true, }, - "features": { + "feature_tags": { Description: "Block of features to configure for this application using tags", Type: schema.TypeList, Optional: true, @@ -278,29 +278,28 @@ func applicationResource() *schema.Resource { ConflictsWith: []string{"tags"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "custom_single_sign_on_app": { - Description: "Whether this application principal represents a custom SAML application for linked service principals", + "custom_single_sign_on": { + Description: "Whether this application represents a custom SAML application for linked service principals", Type: schema.TypeBool, Optional: true, }, - "enterprise_application": { + "enterprise": { Description: "Whether this application represents an Enterprise Application for linked service principals", Type: schema.TypeBool, Optional: true, }, - "gallery_application": { + "gallery": { Description: "Whether this application represents a gallery application for linked service principals", Type: schema.TypeBool, Optional: true, }, - "visible_to_users": { - Description: "Whether this app is visible to users in My Apps and Office 365 Launcher", + "hide": { + Description: "Whether this application is invisible to users in My Apps and Office 365 Launcher", Type: schema.TypeBool, Optional: true, - Default: true, }, }, }, @@ -503,7 +502,7 @@ func applicationResource() *schema.Resource { Optional: true, Computed: true, Set: schema.HashString, - ConflictsWith: []string{"features"}, + ConflictsWith: []string{"feature_tags"}, Elem: &schema.Schema{ Type: schema.TypeString, }, @@ -910,7 +909,7 @@ func applicationResourceCreate(ctx context.Context, d *schema.ResourceData, meta } var tags []string - if v, ok := d.GetOk("features"); ok { + if v, ok := d.GetOk("feature_tags"); ok { tags = helpers.ApplicationExpandFeatures(v.([]interface{})) } else { tags = tf.ExpandStringSlice(d.Get("tags").(*schema.Set).List()) @@ -1095,8 +1094,7 @@ func applicationResourceUpdate(ctx context.Context, d *schema.ResourceData, meta } var tags []string - featuresChanged := d.HasChange("features") - if v, ok := d.GetOk("features"); ok && len(v.([]interface{})) > 0 && featuresChanged { + if v, ok := d.GetOk("feature_tags"); ok && len(v.([]interface{})) > 0 && d.HasChange("feature_tags") { tags = helpers.ApplicationExpandFeatures(v.([]interface{})) } else { tags = tf.ExpandStringSlice(d.Get("tags").(*schema.Set).List()) @@ -1218,7 +1216,7 @@ func applicationResourceRead(ctx context.Context, d *schema.ResourceData, meta i tf.Set(d, "disabled_by_microsoft", fmt.Sprintf("%v", app.DisabledByMicrosoftStatus)) tf.Set(d, "display_name", app.DisplayName) tf.Set(d, "fallback_public_client_enabled", app.IsFallbackPublicClient) - tf.Set(d, "features", helpers.ApplicationFlattenFeatures(app.Tags)) + tf.Set(d, "feature_tags", helpers.ApplicationFlattenFeatures(app.Tags, false)) tf.Set(d, "group_membership_claims", tf.FlattenStringSlicePtr(app.GroupMembershipClaims)) tf.Set(d, "identifier_uris", tf.FlattenStringSlicePtr(app.IdentifierUris)) tf.Set(d, "oauth2_post_response_required", app.Oauth2RequirePostResponse) diff --git a/internal/services/applications/application_resource_test.go b/internal/services/applications/application_resource_test.go index 1e9e8c2286..d3d1d6dccd 100644 --- a/internal/services/applications/application_resource_test.go +++ b/internal/services/applications/application_resource_test.go @@ -441,13 +441,13 @@ func TestAccApplication_related(t *testing.T) { }) } -func TestAccApplication_features(t *testing.T) { +func TestAccApplication_featureTags(t *testing.T) { data := acceptance.BuildTestData(t, "azuread_application", "test") r := ApplicationResource{} data.ResourceTest(t, r, []resource.TestStep{ { - Config: r.features(data), + Config: r.featureTags(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -456,13 +456,13 @@ func TestAccApplication_features(t *testing.T) { }) } -func TestAccApplication_featuresUpdate(t *testing.T) { +func TestAccApplication_featureTagsUpdate(t *testing.T) { data := acceptance.BuildTestData(t, "azuread_application", "test") r := ApplicationResource{} data.ResourceTest(t, r, []resource.TestStep{ { - Config: r.noFeatures(data), + Config: r.noFeatureTags(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -476,7 +476,7 @@ func TestAccApplication_featuresUpdate(t *testing.T) { }, data.ImportStep(), { - Config: r.features(data), + Config: r.featureTags(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -490,7 +490,7 @@ func TestAccApplication_featuresUpdate(t *testing.T) { }, data.ImportStep(), { - Config: r.features(data), + Config: r.featureTags(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -504,21 +504,21 @@ func TestAccApplication_featuresUpdate(t *testing.T) { }, data.ImportStep(), { - Config: r.features(data), + Config: r.featureTags(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), }, data.ImportStep(), { - Config: r.noFeatures(data), + Config: r.noFeatureTags(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), }, data.ImportStep(), { - Config: r.features(data), + Config: r.featureTags(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -755,6 +755,7 @@ resource "azuread_application" "test" { } tags = [ + "HideApp", "WindowsAzureActiveDirectoryCustomSingleSignOnApplication", "WindowsAzureActiveDirectoryIntegratedApp", "WindowsAzureActiveDirectoryGalleryApplicationNonPrimaryV1", @@ -1400,34 +1401,35 @@ resource "azuread_application" "test" { `, data.RandomInteger) } -func (r ApplicationResource) features(data acceptance.TestData) string { +func (r ApplicationResource) featureTags(data acceptance.TestData) string { return fmt.Sprintf(` provider "azuread" {} resource "azuread_application" "test" { display_name = "acctest-APP-%[1]d" - features { - custom_single_sign_on_app = true - enterprise_application = true - gallery_application = true - visible_to_users = false + feature_tags { + custom_single_sign_on = true + enterprise = true + gallery = true + hide = true } } `, data.RandomInteger) } -func (r ApplicationResource) noFeatures(data acceptance.TestData) string { +func (r ApplicationResource) noFeatureTags(data acceptance.TestData) string { return fmt.Sprintf(` provider "azuread" {} resource "azuread_application" "test" { display_name = "acctest-APP-%[1]d" - features { - custom_single_sign_on_app = false - enterprise_application = false - gallery_application = false + feature_tags { + custom_single_sign_on = false + enterprise = false + gallery = false + hide = false } } `, data.RandomInteger) diff --git a/internal/services/serviceprincipals/service_principal_data_source.go b/internal/services/serviceprincipals/service_principal_data_source.go index a74213a5a5..e56caf8bc7 100644 --- a/internal/services/serviceprincipals/service_principal_data_source.go +++ b/internal/services/serviceprincipals/service_principal_data_source.go @@ -99,7 +99,41 @@ func servicePrincipalData() *schema.Resource { Computed: true, }, + "feature_tags": { + Description: "Block of features configured for this service principal using tags", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "custom_single_sign_on": { + Description: "Whether this service principal represents a custom SAML application", + Type: schema.TypeBool, + Computed: true, + }, + + "enterprise": { + Description: "Whether this service principal represents an Enterprise Application", + Type: schema.TypeBool, + Computed: true, + }, + + "gallery": { + Description: "Whether this service principal represents a gallery application", + Type: schema.TypeBool, + Computed: true, + }, + + "hide": { + Description: "Whether this app is invisible to users in My Apps and Office 365 Launcher", + Type: schema.TypeBool, + Computed: true, + }, + }, + }, + }, + "features": { + Deprecated: "This block has been renamed to `feature_tags` and will be removed in version 3.0 of the provider", Description: "Block of features configured for this service principal using tags", Type: schema.TypeList, Computed: true, @@ -346,7 +380,8 @@ func servicePrincipalDataSourceRead(ctx context.Context, d *schema.ResourceData, tf.Set(d, "application_tenant_id", servicePrincipal.AppOwnerOrganizationId) tf.Set(d, "description", servicePrincipal.Description) tf.Set(d, "display_name", servicePrincipal.DisplayName) - tf.Set(d, "features", helpers.ApplicationFlattenFeatures(servicePrincipal.Tags)) + tf.Set(d, "feature_tags", helpers.ApplicationFlattenFeatures(servicePrincipal.Tags, false)) + tf.Set(d, "features", helpers.ApplicationFlattenFeatures(servicePrincipal.Tags, true)) tf.Set(d, "homepage_url", servicePrincipal.Homepage) tf.Set(d, "logout_url", servicePrincipal.LogoutUrl) tf.Set(d, "login_url", servicePrincipal.LoginUrl) diff --git a/internal/services/serviceprincipals/service_principal_data_source_test.go b/internal/services/serviceprincipals/service_principal_data_source_test.go index 99970ebf06..5b9b4e55f3 100644 --- a/internal/services/serviceprincipals/service_principal_data_source_test.go +++ b/internal/services/serviceprincipals/service_principal_data_source_test.go @@ -61,11 +61,11 @@ func (ServicePrincipalDataSource) testCheckFunc(data acceptance.TestData) resour check.That(data.ResourceName).Key("application_tenant_id").HasValue(tenantId), check.That(data.ResourceName).Key("description").HasValue("An internal app for testing"), check.That(data.ResourceName).Key("display_name").Exists(), - check.That(data.ResourceName).Key("features.#").HasValue("1"), - check.That(data.ResourceName).Key("features.0.custom_single_sign_on_app").HasValue("true"), - check.That(data.ResourceName).Key("features.0.enterprise_application").HasValue("true"), - check.That(data.ResourceName).Key("features.0.gallery_application").HasValue("true"), - check.That(data.ResourceName).Key("features.0.visible_to_users").HasValue("true"), + check.That(data.ResourceName).Key("feature_tags.#").HasValue("1"), + check.That(data.ResourceName).Key("feature_tags.0.custom_single_sign_on").HasValue("true"), + check.That(data.ResourceName).Key("feature_tags.0.enterprise").HasValue("true"), + check.That(data.ResourceName).Key("feature_tags.0.gallery").HasValue("true"), + check.That(data.ResourceName).Key("feature_tags.0.hide").HasValue("true"), check.That(data.ResourceName).Key("homepage_url").HasValue(fmt.Sprintf("https://test-%d.internal", data.RandomInteger)), check.That(data.ResourceName).Key("login_url").HasValue(fmt.Sprintf("https://test-%d.internal/login", data.RandomInteger)), check.That(data.ResourceName).Key("logout_url").HasValue(fmt.Sprintf("https://test-%d.internal/logout", data.RandomInteger)), @@ -79,7 +79,7 @@ func (ServicePrincipalDataSource) testCheckFunc(data acceptance.TestData) resour check.That(data.ResourceName).Key("saml_single_sign_on.0.relay_state").HasValue("/samlHome"), check.That(data.ResourceName).Key("service_principal_names.#").HasValue("2"), check.That(data.ResourceName).Key("sign_in_audience").HasValue("AzureADMyOrg"), - check.That(data.ResourceName).Key("tags.#").HasValue("3"), + check.That(data.ResourceName).Key("tags.#").HasValue("4"), check.That(data.ResourceName).Key("type").HasValue("Application"), ) } diff --git a/internal/services/serviceprincipals/service_principal_resource.go b/internal/services/serviceprincipals/service_principal_resource.go index 4c6f44dd31..6eda3a3731 100644 --- a/internal/services/serviceprincipals/service_principal_resource.go +++ b/internal/services/serviceprincipals/service_principal_resource.go @@ -85,12 +85,48 @@ func servicePrincipalResource() *schema.Resource { ValidateFunc: validation.StringLenBetween(0, 1024), }, + "feature_tags": { + Description: "Block of features to configure for this service principal using tags", + Type: schema.TypeList, + Optional: true, + Computed: true, + ConflictsWith: []string{"features", "tags"}, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "custom_single_sign_on": { + Description: "Whether this service principal represents a custom SAML application", + Type: schema.TypeBool, + Optional: true, + }, + + "enterprise": { + Description: "Whether this service principal represents an Enterprise Application", + Type: schema.TypeBool, + Optional: true, + }, + + "gallery": { + Description: "Whether this service principal represents a gallery application", + Type: schema.TypeBool, + Optional: true, + }, + + "hide": { + Description: "Whether this app is invisible to users in My Apps and Office 365 Launcher", + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + }, + "features": { + Deprecated: "This block has been renamed to `feature_tags` and will be removed in version 3.0 of the provider", Description: "Block of features to configure for this service principal using tags", Type: schema.TypeList, Optional: true, Computed: true, - ConflictsWith: []string{"tags"}, + ConflictsWith: []string{"feature_tags", "tags"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "custom_single_sign_on_app": { @@ -175,7 +211,7 @@ func servicePrincipalResource() *schema.Resource { Optional: true, Computed: true, Set: schema.HashString, - ConflictsWith: []string{"features"}, + ConflictsWith: []string{"features", "feature_tags"}, Elem: &schema.Schema{ Type: schema.TypeString, }, @@ -347,7 +383,9 @@ func servicePrincipalResourceCreate(ctx context.Context, d *schema.ResourceData, } var tags []string - if v, ok := d.GetOk("features"); ok { + if v, ok := d.GetOk("feature_tags"); ok { + tags = helpers.ApplicationExpandFeatures(v.([]interface{})) + } else if v, ok := d.GetOk("features"); ok { tags = helpers.ApplicationExpandFeatures(v.([]interface{})) } else { tags = tf.ExpandStringSlice(d.Get("tags").(*schema.Set).List()) @@ -456,8 +494,9 @@ func servicePrincipalResourceUpdate(ctx context.Context, d *schema.ResourceData, directoryObjectsClient := meta.(*clients.Client).ServicePrincipals.DirectoryObjectsClient var tags []string - featuresChanged := d.HasChange("features") - if v, ok := d.GetOk("features"); ok && len(v.([]interface{})) > 0 && featuresChanged { + if v, ok := d.GetOk("feature_tags"); ok && len(v.([]interface{})) > 0 && d.HasChange("feature_tags") { + tags = helpers.ApplicationExpandFeatures(v.([]interface{})) + } else if v, ok := d.GetOk("features"); ok && len(v.([]interface{})) > 0 && d.HasChange("features") { tags = helpers.ApplicationExpandFeatures(v.([]interface{})) } else { tags = tf.ExpandStringSlice(d.Get("tags").(*schema.Set).List()) @@ -563,7 +602,8 @@ func servicePrincipalResourceRead(ctx context.Context, d *schema.ResourceData, m tf.Set(d, "application_tenant_id", servicePrincipal.AppOwnerOrganizationId) tf.Set(d, "description", servicePrincipal.Description) tf.Set(d, "display_name", servicePrincipal.DisplayName) - tf.Set(d, "features", helpers.ApplicationFlattenFeatures(servicePrincipal.Tags)) + tf.Set(d, "feature_tags", helpers.ApplicationFlattenFeatures(servicePrincipal.Tags, false)) + tf.Set(d, "features", helpers.ApplicationFlattenFeatures(servicePrincipal.Tags, true)) tf.Set(d, "homepage_url", servicePrincipal.Homepage) tf.Set(d, "logout_url", servicePrincipal.LogoutUrl) tf.Set(d, "login_url", servicePrincipal.LoginUrl) diff --git a/internal/services/serviceprincipals/service_principal_resource_test.go b/internal/services/serviceprincipals/service_principal_resource_test.go index bddb7e16e5..f310d919f0 100644 --- a/internal/services/serviceprincipals/service_principal_resource_test.go +++ b/internal/services/serviceprincipals/service_principal_resource_test.go @@ -104,40 +104,28 @@ func TestAccServicePrincipal_completeUpdate(t *testing.T) { }) } -func TestAccServicePrincipal_features(t *testing.T) { +func TestAccServicePrincipal_featureTags(t *testing.T) { data := acceptance.BuildTestData(t, "azuread_service_principal", "test") r := ServicePrincipalResource{} - tenantId := os.Getenv("ARM_TENANT_ID") data.ResourceTest(t, r, []resource.TestStep{ { - Config: r.features(data), + Config: r.featureTags(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), - check.That(data.ResourceName).Key("app_role_ids.%").HasValue("2"), - check.That(data.ResourceName).Key("app_roles.#").HasValue("2"), - check.That(data.ResourceName).Key("application_tenant_id").HasValue(tenantId), - check.That(data.ResourceName).Key("homepage_url").HasValue(fmt.Sprintf("https://test-%d.internal", data.RandomInteger)), - check.That(data.ResourceName).Key("logout_url").HasValue(fmt.Sprintf("https://test-%d.internal/logout", data.RandomInteger)), - check.That(data.ResourceName).Key("oauth2_permission_scope_ids.%").HasValue("2"), - check.That(data.ResourceName).Key("oauth2_permission_scopes.#").HasValue("2"), - check.That(data.ResourceName).Key("service_principal_names.#").HasValue("2"), - check.That(data.ResourceName).Key("redirect_uris.#").HasValue("2"), - check.That(data.ResourceName).Key("sign_in_audience").HasValue("AzureADMyOrg"), - check.That(data.ResourceName).Key("type").HasValue("Application"), ), }, data.ImportStep("use_existing"), }) } -func TestAccServicePrincipal_featuresUpdate(t *testing.T) { +func TestAccServicePrincipal_featureTagsUpdate(t *testing.T) { data := acceptance.BuildTestData(t, "azuread_service_principal", "test") r := ServicePrincipalResource{} data.ResourceTest(t, r, []resource.TestStep{ { - Config: r.noFeatures(data), + Config: r.noFeatureTags(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -151,7 +139,7 @@ func TestAccServicePrincipal_featuresUpdate(t *testing.T) { }, data.ImportStep("use_existing"), { - Config: r.features(data), + Config: r.featureTags(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -165,7 +153,7 @@ func TestAccServicePrincipal_featuresUpdate(t *testing.T) { }, data.ImportStep("use_existing"), { - Config: r.features(data), + Config: r.featureTags(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -179,21 +167,21 @@ func TestAccServicePrincipal_featuresUpdate(t *testing.T) { }, data.ImportStep("use_existing"), { - Config: r.features(data), + Config: r.featureTags(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), }, data.ImportStep("use_existing"), { - Config: r.noFeatures(data), + Config: r.noFeatureTags(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), }, data.ImportStep("use_existing"), { - Config: r.features(data), + Config: r.featureTags(data), Check: resource.ComposeTestCheckFunc( check.That(data.ResourceName).ExistsInAzure(r), ), @@ -443,6 +431,7 @@ resource "azuread_service_principal" "test" { } tags = [ + "HideApp", "WindowsAzureActiveDirectoryCustomSingleSignOnApplication", "WindowsAzureActiveDirectoryIntegratedApp", "WindowsAzureActiveDirectoryGalleryApplicationNonPrimaryV1", @@ -451,7 +440,7 @@ resource "azuread_service_principal" "test" { `, r.templateComplete(data), data.RandomInteger) } -func (r ServicePrincipalResource) features(data acceptance.TestData) string { +func (r ServicePrincipalResource) featureTags(data acceptance.TestData) string { return fmt.Sprintf(` %[1]s @@ -466,11 +455,11 @@ resource "azuread_service_principal" "test" { notes = "Just testing something" preferred_single_sign_on_mode = "saml" - features { - custom_single_sign_on_app = true - enterprise_application = true - gallery_application = true - visible_to_users = false + feature_tags { + custom_single_sign_on = true + enterprise = true + gallery = true + hide = true } notification_email_addresses = [ @@ -485,7 +474,7 @@ resource "azuread_service_principal" "test" { `, r.templateComplete(data), data.RandomInteger) } -func (r ServicePrincipalResource) noFeatures(data acceptance.TestData) string { +func (r ServicePrincipalResource) noFeatureTags(data acceptance.TestData) string { return fmt.Sprintf(` %[1]s @@ -500,10 +489,11 @@ resource "azuread_service_principal" "test" { notes = "Just testing something" preferred_single_sign_on_mode = "saml" - features { - custom_single_sign_on_app = false - enterprise_application = false - gallery_application = false + feature_tags { + custom_single_sign_on = false + enterprise = false + gallery = false + hide = false } notification_email_addresses = [ From 6c306cae62c7c0b9a3a7067fcbed0747c725e4f0 Mon Sep 17 00:00:00 2001 From: kt Date: Thu, 14 Oct 2021 18:17:06 -0700 Subject: [PATCH 4/5] Update docs/data-sources/application.md --- docs/data-sources/application.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data-sources/application.md b/docs/data-sources/application.md index 89412e0a72..19c45baacd 100644 --- a/docs/data-sources/application.md +++ b/docs/data-sources/application.md @@ -48,7 +48,7 @@ The following attributes are exported: * `disabled_by_microsoft` - Whether Microsoft has disabled the registered application. If the application is disabled, this will be a string indicating the status/reason, e.g. `DisabledDueToViolationOfServicesAgreement` * `display_name` - The display name for the application. * `fallback_public_client_enabled` - The fallback application type as public client, such as an installed application running on a mobile device. -* `features` - A `features` block as described below. +* `feature_tags` - A `features` block as described below. * `group_membership_claims` - The `groups` claim issued in a user or OAuth 2.0 access token that the app expects. * `identifier_uris` - A list of user-defined URI(s) that uniquely identify a Web application within it's Azure AD tenant, or within a verified custom domain if the application is multi-tenant. * `logo_url` - CDN URL to the application's logo. From 05163e0113d34d27adca309d87a0c857abaafb86 Mon Sep 17 00:00:00 2001 From: kt Date: Thu, 14 Oct 2021 18:17:31 -0700 Subject: [PATCH 5/5] Update docs/data-sources/application.md --- docs/data-sources/application.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/data-sources/application.md b/docs/data-sources/application.md index 19c45baacd..cdf16f9682 100644 --- a/docs/data-sources/application.md +++ b/docs/data-sources/application.md @@ -106,10 +106,10 @@ The following attributes are exported: `features` block exports the following: -* `custom_single_sign_on_app` - Whether this application represents a custom SAML application for linked service principals. -* `enterprise_application` - Whether this application represents an Enterprise Application for linked service principals. -* `gallery_application` - Whether this application represents a gallery application for linked service principals. -* `visible_to_users` - Whether this app is visible to users in My Apps and Office 365 Launcher. +* `custom_single_sign_on` - Whether this application represents a custom SAML application for linked service principals. +* `enterprise` - Whether this application represents an Enterprise Application for linked service principals. +* `gallery` - Whether this application represents a gallery application for linked service principals. +* `hide` - Whether this app is visible to users in My Apps and Office 365 Launcher. ---