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

service_principal: Support the saml_single_sign_on block with the relay_state property #557

Merged
merged 1 commit into from
Sep 9, 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
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
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,
}}
}