From a2a6ba9decd15e427710202a6b4069909cb2aa2c Mon Sep 17 00:00:00 2001 From: Gerard Paulke Date: Sun, 20 Jan 2019 20:24:53 -0800 Subject: [PATCH] Added support for Required Resource Access Type within provider `azuread_application` --- azuread/resource_application.go | 143 ++++++++++++++++++ azuread/resource_application_test.go | 26 ++++ .../helper/structure/suppress_json_diff.go | 4 + website/docs/r/application.html.markdown | 45 ++++++ 4 files changed, 218 insertions(+) diff --git a/azuread/resource_application.go b/azuread/resource_application.go index 5ac41a55cc..dc986519fe 100644 --- a/azuread/resource_application.go +++ b/azuread/resource_application.go @@ -75,6 +75,41 @@ func resourceApplication() *schema.Resource { Type: schema.TypeString, Computed: true, }, + + "required_resource_access": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "resource_app_id": { + Type: schema.TypeString, + Required: true, + }, + + "resource_access": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Required: true, + }, + + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice( + []string{"scope", "role"}, + true, // ignore case + ), + }, + }, + }, + }, + }, + }, + }, }, } } @@ -91,6 +126,7 @@ func resourceApplicationCreate(d *schema.ResourceData, meta interface{}) error { IdentifierUris: tf.ExpandStringArrayPtr(d.Get("identifier_uris").([]interface{})), ReplyUrls: tf.ExpandStringArrayPtr(d.Get("reply_urls").([]interface{})), AvailableToOtherTenants: p.Bool(d.Get("available_to_other_tenants").(bool)), + RequiredResourceAccess: expandADApplicationRequiredResourceAccess(d), } if v, ok := d.GetOk("oauth2_allow_implicit_flow"); ok { @@ -141,6 +177,10 @@ func resourceApplicationUpdate(d *schema.ResourceData, meta interface{}) error { properties.Oauth2AllowImplicitFlow = p.Bool(oauth) } + if d.HasChange("required_resource_access") { + properties.RequiredResourceAccess = expandADApplicationRequiredResourceAccess(d) + } + if _, err := client.Patch(ctx, d.Id(), properties); err != nil { return fmt.Errorf("Error patching Azure AD Application with ID %q: %+v", d.Id(), err) } @@ -177,6 +217,14 @@ func resourceApplicationRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error setting `reply_urls`: %+v", err) } + requiredResourceAccesses := make([]graphrbac.RequiredResourceAccess, 0) + if s := *resp.RequiredResourceAccess; s != nil { + requiredResourceAccesses = s + } + if err := d.Set("required_resource_access", flattenADApplicationRequiredResourceAccess(&requiredResourceAccesses)); err != nil { + return fmt.Errorf("Error setting `required_resource_access`: %+v", err) + } + return nil } @@ -214,3 +262,98 @@ func expandADApplicationHomepage(d *schema.ResourceData, name string) *string { return p.String(fmt.Sprintf("https://%s", name)) } + +func expandADApplicationIdentifierUris(d *schema.ResourceData) *[]string { + identifierUris := d.Get("identifier_uris").([]interface{}) + identifiers := make([]string, 0) + + for _, id := range identifierUris { + identifiers = append(identifiers, id.(string)) + } + + return &identifiers +} + +func expandADApplicationReplyUrls(d *schema.ResourceData) *[]string { + replyUrls := d.Get("reply_urls").([]interface{}) + urls := make([]string, 0) + + for _, url := range replyUrls { + urls = append(urls, url.(string)) + } + + return &urls +} + +func expandADApplicationRequiredResourceAccess(d *schema.ResourceData) *[]graphrbac.RequiredResourceAccess { + requiredResourcesAccesses := d.Get("required_resource_access").(*schema.Set).List() + result := make([]graphrbac.RequiredResourceAccess, 0) + + for _, raw := range requiredResourcesAccesses { + requiredResourceAccess := raw.(map[string]interface{}) + + resource_app_id := requiredResourceAccess["resource_app_id"].(string) + + result = append(result, + graphrbac.RequiredResourceAccess{ + ResourceAppID: &resource_app_id, + ResourceAccess: expandADApplicationResourceAccess( + requiredResourceAccess["resource_access"].([]interface{}), + ), + }, + ) + } + return &result +} + +func expandADApplicationResourceAccess(in []interface{}) *[]graphrbac.ResourceAccess { + var resourceAccesses []graphrbac.ResourceAccess + for _, resource_access_raw := range in { + resource_access := resource_access_raw.(map[string]interface{}) + + resourceId := resource_access["id"].(string) + resourceType := resource_access["type"].(string) + + resourceAccesses = append(resourceAccesses, + graphrbac.ResourceAccess{ + ID: &resourceId, + Type: &resourceType, + }, + ) + } + + return &resourceAccesses +} + +func flattenADApplicationRequiredResourceAccess(in *[]graphrbac.RequiredResourceAccess) []map[string]interface{} { + result := make([]map[string]interface{}, 0, len(*in)) + + if in != nil { + for _, requiredResourceAccess := range *in { + resource := make(map[string]interface{}) + resource["resource_app_id"] = string(*requiredResourceAccess.ResourceAppID) + + if *requiredResourceAccess.ResourceAccess != nil { + resource["resource_access"] = flattenADApplicationResourceAccess(requiredResourceAccess.ResourceAccess) + } + + result = append(result, resource) + } + } + + return result +} + +func flattenADApplicationResourceAccess(in *[]graphrbac.ResourceAccess) []map[string]interface{} { + accesses := make([]map[string]interface{}, 0) + + for _, resourceAccess := range *in { + accesses = append(accesses, + map[string]interface{}{ + "id": *resourceAccess.ID, + "type": *resourceAccess.Type, + }, + ) + } + return accesses +} diff --git a/azuread/resource_application_test.go b/azuread/resource_application_test.go index f041930346..76f8651df0 100644 --- a/azuread/resource_application_test.go +++ b/azuread/resource_application_test.go @@ -83,6 +83,7 @@ func TestAccAzureADApplication_complete(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "identifier_uris.#", "1"), resource.TestCheckResourceAttr(resourceName, "identifier_uris.0", fmt.Sprintf("http://%s.hashicorptest.com/00000000-0000-0000-0000-00000000", id)), resource.TestCheckResourceAttr(resourceName, "reply_urls.#", "1"), + resource.TestCheckResourceAttr(resourceName, "required_resource_access.#", "2"), resource.TestCheckResourceAttrSet(resourceName, "application_id"), ), }, @@ -125,6 +126,7 @@ func TestAccAzureADApplication_update(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "identifier_uris.0", fmt.Sprintf("http://%s.hashicorptest.com/00000000-0000-0000-0000-00000000", updatedId)), resource.TestCheckResourceAttr(resourceName, "reply_urls.#", "1"), resource.TestCheckResourceAttr(resourceName, "reply_urls.0", fmt.Sprintf("http://%s.hashicorptest.com", updatedId)), + resource.TestCheckResourceAttr(resourceName, "required_resource_access.#", "2"), ), }, }, @@ -203,6 +205,30 @@ resource "azuread_application" "test" { identifier_uris = ["http://%s.hashicorptest.com/00000000-0000-0000-0000-00000000"] reply_urls = ["http://%s.hashicorptest.com"] oauth2_allow_implicit_flow = true + + required_resource_access { + resource_app_id = "00000003-0000-0000-c000-000000000000" + resource_access { + id = "7ab1d382-f21e-4acd-a863-ba3e13f7da61" + type = "Role" + } + resource_access { + id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d" + type = "Scope" + } + + resource_access { + id = "06da0dbc-49e2-44d2-8312-53f166ab848a" + type = "Scope" + } + } + required_resource_access { + resource_app_id = "00000002-0000-0000-c000-000000000000" + resource_access { + id = "311a71cc-e848-46a1-bdf8-97ff7156d8e6" + type = "Scope" + } + } } `, id, id, id, id) } diff --git a/vendor/github.com/hashicorp/terraform/helper/structure/suppress_json_diff.go b/vendor/github.com/hashicorp/terraform/helper/structure/suppress_json_diff.go index 46f794a71b..092362ccb4 100644 --- a/vendor/github.com/hashicorp/terraform/helper/structure/suppress_json_diff.go +++ b/vendor/github.com/hashicorp/terraform/helper/structure/suppress_json_diff.go @@ -1,6 +1,7 @@ package structure import ( + "fmt" "reflect" "github.com/hashicorp/terraform/helper/schema" @@ -12,10 +13,13 @@ func SuppressJsonDiff(k, old, new string, d *schema.ResourceData) bool { return false } + fmt.Printf("[MMMM]: %s", oldMap) + newMap, err := ExpandJsonFromString(new) if err != nil { return false } + fmt.Printf("[NNNN]: %s", newMap) return reflect.DeepEqual(oldMap, newMap) } diff --git a/website/docs/r/application.html.markdown b/website/docs/r/application.html.markdown index 3d7fc1f0c3..6a565e4c3b 100644 --- a/website/docs/r/application.html.markdown +++ b/website/docs/r/application.html.markdown @@ -23,6 +23,33 @@ resource "azuread_application" "test" { reply_urls = ["https://replyurl"] available_to_other_tenants = false oauth2_allow_implicit_flow = true + + required_resource_access { + resource_app_id = "00000003-0000-0000-c000-000000000000" + + resource_access { + id = "..." + type = "Role" + } + resource_access { + id = "..." + type = "Scope" + } + + resource_access { + id = "..." + type = "Scope" + } + } + + required_resource_access { + resource_app_id = "00000002-0000-0000-c000-000000000000" + + resource_access { + id = ".." + type = "Scope" + } + } } ``` @@ -42,6 +69,24 @@ The following arguments are supported: * `oauth2_allow_implicit_flow` - (Optional) Does this Azure AD Application allow OAuth2.0 implicit flow tokens? Defaults to `false`. +* `required_resource_access` - (Optional) A collection of required_resource_access blocks as documented below. Specifies resources that this application requires access to and the set of OAuth permission scopes and application roles that it needs under each of those resources. This pre-configuration of required resource access drives the consent experience. + +--- + +`required_resource_access` supports the following: + +* `resource_app_id` - (Required) The unique identifier for the resource that the application requires access to. This should be equal to the appId declared on the target resource application. + +* `resource_access"` - (Required) A collection of resource_access blocks as documented below + +--- + +`resource_access` supports the following: + +* `id` - (Required) The unique identifier for one of the OAuth2Permission or AppRole instances that the resource application exposes. + +* `type` - (Required) Specifies whether the id property references an OAuth2Permission or an AppRole. Possible values are "scope" or "role". + ## Attributes Reference The following attributes are exported: