Skip to content

Commit

Permalink
service_principal: Support the saml_single_sign_on block with the `…
Browse files Browse the repository at this point in the history
…relay_state` property (resource and data source)
  • Loading branch information
manicminer committed Sep 7, 2021
1 parent 6cac1e0 commit d057983
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 0 deletions.
7 changes: 7 additions & 0 deletions docs/data-sources/service_principal.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ The following attributes are exported:
* `preferred_single_sign_on_mode` - 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.
* `redirect_uris` - A list of URLs where user tokens are sent for sign-in with the associated application, or the redirect URIs where OAuth 2.0 authorization codes and access tokens are sent for the associated application.
* `saml_metadata_url` - The URL where the service exposes SAML metadata for federation.
* `saml_single_sign_on` - A `saml_single_sign_on` block as documented below.
* `service_principal_names` - A list of identifier URI(s), copied over from the associated application.
* `sign_in_audience` - The Microsoft account types that are supported for the associated application. Possible values include `AzureADMyOrg`, `AzureADMultipleOrgs`, `AzureADandPersonalMicrosoftAccount` or `PersonalMicrosoftAccount`.
* `tags` - A list of tags applied to the service principal.
Expand Down Expand Up @@ -102,3 +103,9 @@ The following attributes are exported:
* `user_consent_description` - Delegated permission description that appears in the end user consent experience, intended to be read by a user consenting on their own behalf.
* `user_consent_display_name` - Display name for the delegated permission that appears in the end user consent experience.
* `value` - The value that is used for the `scp` claim in OAuth 2.0 access tokens.

---

`saml_single_sign_on` exports the following:

* `relay_state` - The relative URI the service provider would redirect to after completion of the single sign-on flow.
7 changes: 7 additions & 0 deletions docs/resources/service_principal.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,18 @@ The following arguments are supported:
-> **Ownership of Service Principals** It's recommended to always specify one or more service principal owners, including the principal being used to execute Terraform, such as in the example above.

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

---

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

## Attributes Reference

In addition to all arguments above, the following attributes are exported:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,21 @@ func servicePrincipalData() *schema.Resource {
Computed: true,
},

"saml_single_sign_on": {
Description: "Settings related to SAML single sign-on",
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"relay_state": {
Description: "The relative URI the service provider would redirect to after completion of the single sign-on flow",
Type: schema.TypeString,
Computed: true,
},
},
},
},

"service_principal_names": {
Description: "A list of identifier URI(s), copied over from the associated application",
Type: schema.TypeList,
Expand Down Expand Up @@ -309,6 +324,7 @@ func servicePrincipalDataSourceRead(ctx context.Context, d *schema.ResourceData,
tf.Set(d, "preferred_single_sign_on_mode", servicePrincipal.PreferredSingleSignOnMode)
tf.Set(d, "redirect_uris", tf.FlattenStringSlicePtr(servicePrincipal.ReplyUrls))
tf.Set(d, "saml_metadata_url", servicePrincipal.SamlMetadataUrl)
tf.Set(d, "saml_single_sign_on", flattenSamlSingleSignOn(servicePrincipal.SamlSingleSignOnSettings))
tf.Set(d, "service_principal_names", servicePrincipalNames)
tf.Set(d, "sign_in_audience", servicePrincipal.SignInAudience)
tf.Set(d, "tags", servicePrincipal.Tags)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ func (ServicePrincipalDataSource) testCheckFunc(data acceptance.TestData) resour
check.That(data.ResourceName).Key("oauth2_permission_scopes.#").HasValue("2"),
check.That(data.ResourceName).Key("object_id").IsUuid(),
check.That(data.ResourceName).Key("redirect_uris.#").HasValue("2"),
check.That(data.ResourceName).Key("saml_single_sign_on.#").HasValue("1"),
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"),
Expand Down
39 changes: 39 additions & 0 deletions internal/services/serviceprincipals/service_principal_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,24 @@ func servicePrincipalResource() *schema.Resource {
Computed: true,
},

"saml_single_sign_on": {
Description: "Settings related to SAML single sign-on",
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
DiffSuppressFunc: servicePrincipalDiffSuppress,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"relay_state": {
Description: "The relative URI the service provider would redirect to after completion of the single sign-on flow",
Type: schema.TypeString,
Optional: true,
ValidateDiagFunc: validate.NoEmptyStrings,
},
},
},
},

"service_principal_names": {
Description: "A list of identifier URI(s), copied over from the associated application",
Type: schema.TypeList,
Expand All @@ -240,6 +258,24 @@ func servicePrincipalResource() *schema.Resource {
}
}

func servicePrincipalDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
suppress := false

switch {
case k == "saml_single_sign_on.#" && old == "1" && new == "0":
samlSingleSignOnRaw := d.Get("saml_single_sign_on").([]interface{})
if len(samlSingleSignOnRaw) == 1 {
suppress = true
samlSingleSignOn := samlSingleSignOnRaw[0].(map[string]interface{})
if v, ok := samlSingleSignOn["relay_state"]; ok && v.(string) != "" {
suppress = false
}
}
}

return suppress
}

func servicePrincipalResourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client).ServicePrincipals.ServicePrincipalsClient
directoryObjectsClient := meta.(*clients.Client).ServicePrincipals.DirectoryObjectsClient
Expand Down Expand Up @@ -282,6 +318,7 @@ func servicePrincipalResourceCreate(ctx context.Context, d *schema.ResourceData,
Notes: utils.NullableString(d.Get("notes").(string)),
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()),
}

Expand Down Expand Up @@ -374,6 +411,7 @@ func servicePrincipalResourceUpdate(ctx context.Context, d *schema.ResourceData,
Notes: utils.NullableString(d.Get("notes").(string)),
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()),
}

Expand Down Expand Up @@ -466,6 +504,7 @@ func servicePrincipalResourceRead(ctx context.Context, d *schema.ResourceData, m
tf.Set(d, "preferred_single_sign_on_mode", servicePrincipal.PreferredSingleSignOnMode)
tf.Set(d, "redirect_uris", tf.FlattenStringSlicePtr(servicePrincipal.ReplyUrls))
tf.Set(d, "saml_metadata_url", servicePrincipal.SamlMetadataUrl)
tf.Set(d, "saml_single_sign_on", flattenSamlSingleSignOn(servicePrincipal.SamlSingleSignOnSettings))
tf.Set(d, "service_principal_names", servicePrincipalNames)
tf.Set(d, "sign_in_audience", servicePrincipal.SignInAudience)
tf.Set(d, "tags", servicePrincipal.Tags)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,10 @@ resource "azuread_service_principal" "test" {
"cto@hashitown.net",
]
saml_single_sign_on {
relay_state = "/samlHome"
}
alternative_names = ["foo", "bar"]
tags = ["test", "multiple", "CapitalS"]
}
Expand Down
35 changes: 35 additions & 0 deletions internal/services/serviceprincipals/serviceprincipals.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package serviceprincipals

import (
"github.com/manicminer/hamilton/msgraph"

"github.com/hashicorp/terraform-provider-azuread/internal/utils"
)

func expandSamlSingleSignOn(in []interface{}) *msgraph.SamlSingleSignOnSettings {
result := msgraph.SamlSingleSignOnSettings{}
if len(in) == 0 || in[0] == nil {
return &result
}

samlSingleSignOnSettings := in[0].(map[string]interface{})

result.RelayState = utils.String(samlSingleSignOnSettings["relay_state"].(string))

return &result
}

func flattenSamlSingleSignOn(in *msgraph.SamlSingleSignOnSettings) []map[string]interface{} {
if in == nil {
return []map[string]interface{}{}
}

relayState := ""
if in.RelayState != nil {
relayState = *in.RelayState
}

return []map[string]interface{}{{
"relay_state": relayState,
}}
}

0 comments on commit d057983

Please sign in to comment.