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_service_principal: support the features block #571

Merged
merged 1 commit into from
Sep 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
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
10 changes: 10 additions & 0 deletions docs/data-sources/service_principal.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ The following attributes are exported:
* `application_tenant_id` - The tenant ID where the associated application is registered.
* `description` - A description of the service principal provided for internal end-users.
* `display_name` - The display name of the application associated with this service principal.
* `features` - A `features` block as described below.
* `homepage_url` - Home page or landing page of the associated application.
* `login_url` - 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.
* `logout_url` - The URL that will be used by Microsoft's authorization service to logout an user using OpenId Connect front-channel, back-channel or SAML logout protocols, taken from the associated application.
Expand Down Expand Up @@ -93,6 +94,15 @@ The following attributes are exported:

---

`features` block exports the following:

* `custom_single_sign_on_app` - Whether this service principal represents a custom SAML application.
* `enterprise_application` - Whether this service principal represents an Enterprise Application.
* `gallery_application` - Whether this service principal represents a gallery application.
* `visible_to_users` - Whether this app is visible to users in My Apps and Office 365 Launcher.

---

`oauth2_permission_scopes` block exports the following:

* `admin_consent_description` - Delegated permission description that appears in all tenant-wide admin consent experiences, intended to be read by an administrator granting the permission on behalf of all users.
Expand Down
34 changes: 32 additions & 2 deletions docs/resources/service_principal.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,32 @@ resource "azuread_application" "example" {
owners = [data.azuread_client_config.current.object_id]
}

resource "azuread_service_principal" "example" {
application_id = azuread_application.example.application_id
app_role_assignment_required = false
owners = [data.azuread_client_config.current.object_id]
}
```

*Create a service principal for an enterprise application*

```terraform
data "azuread_client_config" "current" {}

resource "azuread_application" "example" {
display_name = "example"
owners = [data.azuread_client_config.current.object_id]
}

resource "azuread_service_principal" "example" {
application_id = azuread_application.example.application_id
app_role_assignment_required = false
owners = [data.azuread_client_config.current.object_id]

tags = ["example", "tags", "here"]
features {
enterprise_application = true
gallery_application = true
}
}
```

Expand Down Expand Up @@ -75,6 +95,7 @@ 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.
* `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.
Expand All @@ -84,13 +105,22 @@ 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.
* `tags` - (Optional) A set of tags to apply to the service principal. Cannot be used together with the `features` block.
* `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).

---

`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`.

---

`saml_single_sign_on` supports the following:

* `relay_state` - (Optional) The relative URI the service provider would redirect to after completion of the single sign-on flow.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,39 @@ func servicePrincipalData() *schema.Resource {
Computed: true,
},

"features": {
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_app": {
Description: "Whether this service principal represents a custom SAML application",
Type: schema.TypeBool,
Computed: true,
},

"enterprise_application": {
Description: "Whether this service principal represents an Enterprise Application",
Type: schema.TypeBool,
Computed: true,
},

"gallery_application": {
Description: "Whether this service principal represents a gallery application",
Type: schema.TypeBool,
Computed: true,
},

"visible_to_users": {
Description: "Whether this app is visible to users in My Apps and Office 365 Launcher",
Type: schema.TypeBool,
Computed: true,
},
},
},
},

"homepage_url": {
Description: "Home page or landing page of the application",
Type: schema.TypeString,
Expand Down Expand Up @@ -313,6 +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, "homepage_url", servicePrincipal.Homepage)
tf.Set(d, "logout_url", servicePrincipal.LogoutUrl)
tf.Set(d, "login_url", servicePrincipal.LoginUrl)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +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("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)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,42 @@ func servicePrincipalResource() *schema.Resource {
ValidateFunc: validation.StringLenBetween(0, 1024),
},

"features": {
Description: "Block of features to configure for this service principal 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 service principal represents a custom SAML application",
Type: schema.TypeBool,
Optional: true,
},

"enterprise_application": {
Description: "Whether this service principal represents an Enterprise Application",
Type: schema.TypeBool,
Optional: true,
},

"gallery_application": {
Description: "Whether this service principal represents a gallery application",
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,
},
},
},
},

"login_url": {
Description: "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",
Type: schema.TypeString,
Expand Down Expand Up @@ -134,10 +170,12 @@ func servicePrincipalResource() *schema.Resource {
},

"tags": {
Description: "A set of tags to apply to the service principal",
Type: schema.TypeSet,
Optional: true,
Set: schema.HashString,
Description: "A set of tags to apply to the service principal",
Type: schema.TypeSet,
Optional: true,
Computed: true,
Set: schema.HashString,
ConflictsWith: []string{"features"},
Elem: &schema.Schema{
Type: schema.TypeString,
},
Expand Down Expand Up @@ -308,6 +346,13 @@ func servicePrincipalResourceCreate(ctx context.Context, d *schema.ResourceData,
return servicePrincipalResourceUpdate(ctx, d, meta)
}

var tags []string
if v, ok := d.GetOk("features"); ok {
tags = expandFeatures(v.([]interface{}))
} else {
tags = tf.ExpandStringSlice(d.Get("tags").(*schema.Set).List())
}

properties := msgraph.ServicePrincipal{
AccountEnabled: utils.Bool(d.Get("account_enabled").(bool)),
AlternativeNames: tf.ExpandStringSlicePtr(d.Get("alternative_names").(*schema.Set).List()),
Expand All @@ -319,7 +364,7 @@ func servicePrincipalResourceCreate(ctx context.Context, d *schema.ResourceData,
NotificationEmailAddresses: tf.ExpandStringSlicePtr(d.Get("notification_email_addresses").(*schema.Set).List()),
PreferredSingleSignOnMode: utils.NullableString(d.Get("preferred_single_sign_on_mode").(string)),
SamlSingleSignOnSettings: expandSamlSingleSignOn(d.Get("saml_single_sign_on").([]interface{})),
Tags: tf.ExpandStringSlicePtr(d.Get("tags").(*schema.Set).List()),
Tags: &tags,
}

// Sort the owners into two slices, the first containing up to 20 and the rest overflowing to the second slice
Expand Down Expand Up @@ -399,6 +444,14 @@ func servicePrincipalResourceUpdate(ctx context.Context, d *schema.ResourceData,
client := meta.(*clients.Client).ServicePrincipals.ServicePrincipalsClient
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 {
tags = expandFeatures(v.([]interface{}))
} else {
tags = tf.ExpandStringSlice(d.Get("tags").(*schema.Set).List())
}

properties := msgraph.ServicePrincipal{
DirectoryObject: msgraph.DirectoryObject{
ID: utils.String(d.Id()),
Expand All @@ -412,7 +465,7 @@ func servicePrincipalResourceUpdate(ctx context.Context, d *schema.ResourceData,
NotificationEmailAddresses: tf.ExpandStringSlicePtr(d.Get("notification_email_addresses").(*schema.Set).List()),
PreferredSingleSignOnMode: utils.NullableString(d.Get("preferred_single_sign_on_mode").(string)),
SamlSingleSignOnSettings: expandSamlSingleSignOn(d.Get("saml_single_sign_on").([]interface{})),
Tags: tf.ExpandStringSlicePtr(d.Get("tags").(*schema.Set).List()),
Tags: &tags,
}

if _, err := client.Update(ctx, properties); err != nil {
Expand Down Expand Up @@ -493,6 +546,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, "homepage_url", servicePrincipal.Homepage)
tf.Set(d, "logout_url", servicePrincipal.LogoutUrl)
tf.Set(d, "login_url", servicePrincipal.LoginUrl)
Expand Down
Loading