Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

azuread_application: support the features block and the tags property #630

Merged
merged 6 commits into from
Oct 15, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions docs/data-sources/application.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
katbyte marked this conversation as resolved.
Show resolved Hide resolved
* `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.
Expand All @@ -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.

Expand Down Expand Up @@ -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.
katbyte marked this conversation as resolved.
Show resolved Hide resolved
* `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.
katbyte marked this conversation as resolved.
Show resolved Hide resolved
* `visible_to_users` - Whether this app is visible to users in My Apps and Office 365 Launcher.
katbyte marked this conversation as resolved.
Show resolved Hide resolved

---

`optional_claims` block exports the following:

* `access_token` - One or more `access_token` blocks as documented below.
Expand Down
16 changes: 16 additions & 0 deletions docs/resources/application.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ resource "azuread_application" "example" {
value = "User"
}

features {
enterprise_application = true
gallery_application = true
}

optional_claims {
access_token {
name = "myclaim"
Expand Down Expand Up @@ -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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we be explicit that this is basically a shortcut to setting specific tags?

* `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.
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
60 changes: 60 additions & 0 deletions internal/helpers/applications.go
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
46 changes: 46 additions & 0 deletions internal/services/applications/application_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -214,6 +215,40 @@ func applicationDataSource() *schema.Resource {
Computed: true,
},

"features": {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we be explicit and call this

Suggested change
"features": {
"tags_features": {

or

Suggested change
"features": {
"tag_enabled_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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -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"),
Expand Down
68 changes: 68 additions & 0 deletions internal/services/applications/application_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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{})),
}

Expand Down Expand Up @@ -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),
Expand All @@ -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{})),
}

Expand Down Expand Up @@ -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)
Expand All @@ -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))

Expand Down
Loading