From a25cfc942ee9fb58691bcb37bc917d0c34641262 Mon Sep 17 00:00:00 2001 From: gerardaus Date: Mon, 28 Jan 2019 14:18:40 -0800 Subject: [PATCH] azuread_application: Added support for Required Resource Access (#23) In order to support the "Required Resource Access Type", the azure sdk needed to be upgraded. (fixes #9) --- azuread/data_application.go | 35 +++++++ azuread/data_application_test.go | 3 + azuread/resource_application.go | 124 +++++++++++++++++++++++ azuread/resource_application_test.go | 30 ++++++ website/docs/d/application.html.markdown | 19 ++++ website/docs/r/application.html.markdown | 45 ++++++++ 6 files changed, 256 insertions(+) diff --git a/azuread/data_application.go b/azuread/data_application.go index 7cd80d9cc3..625e44a57d 100644 --- a/azuread/data_application.go +++ b/azuread/data_application.go @@ -69,6 +69,37 @@ func dataApplication() *schema.Resource { Type: schema.TypeString, Computed: true, }, + + "required_resource_access": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "resource_app_id": { + Type: schema.TypeString, + Computed: true, + }, + + "resource_access": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + }, + + "type": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + }, + }, }, } } @@ -137,5 +168,9 @@ func dataApplicationRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error setting `reply_urls`: %+v", err) } + if err := d.Set("required_resource_access", flattenADApplicationRequiredResourceAccess(application.RequiredResourceAccess)); err != nil { + return fmt.Errorf("Error setting `required_resource_access`: %+v", err) + } + return nil } diff --git a/azuread/data_application_test.go b/azuread/data_application_test.go index 93d467cd1f..c4939e67ff 100644 --- a/azuread/data_application_test.go +++ b/azuread/data_application_test.go @@ -28,6 +28,7 @@ func TestAccAzureADApplicationDataSource_byObjectId(t *testing.T) { resource.TestCheckResourceAttr(dataSourceName, "homepage", fmt.Sprintf("https://acctest%s", id)), resource.TestCheckResourceAttr(dataSourceName, "identifier_uris.#", "0"), resource.TestCheckResourceAttr(dataSourceName, "reply_urls.#", "0"), + resource.TestCheckResourceAttr(dataSourceName, "required_resource_access.#", "0"), resource.TestCheckResourceAttr(dataSourceName, "oauth2_allow_implicit_flow", "false"), resource.TestCheckResourceAttrSet(dataSourceName, "application_id"), ), @@ -57,6 +58,7 @@ func TestAccAzureADApplicationDataSource_byObjectIdComplete(t *testing.T) { resource.TestCheckResourceAttr(dataSourceName, "identifier_uris.#", "1"), resource.TestCheckResourceAttr(dataSourceName, "reply_urls.#", "1"), resource.TestCheckResourceAttr(dataSourceName, "oauth2_allow_implicit_flow", "true"), + resource.TestCheckResourceAttr(dataSourceName, "required_resource_access.#", "2"), resource.TestCheckResourceAttrSet(dataSourceName, "application_id"), ), }, @@ -84,6 +86,7 @@ func TestAccAzureADApplicationDataSource_byName(t *testing.T) { resource.TestCheckResourceAttr(dataSourceName, "homepage", fmt.Sprintf("https://acctest%s", id)), resource.TestCheckResourceAttr(dataSourceName, "identifier_uris.#", "0"), resource.TestCheckResourceAttr(dataSourceName, "reply_urls.#", "0"), + resource.TestCheckResourceAttr(dataSourceName, "required_resource_access.#", "0"), resource.TestCheckResourceAttr(dataSourceName, "oauth2_allow_implicit_flow", "false"), resource.TestCheckResourceAttrSet(dataSourceName, "application_id"), ), diff --git a/azuread/resource_application.go b/azuread/resource_application.go index 74cf42add1..005a086518 100644 --- a/azuread/resource_application.go +++ b/azuread/resource_application.go @@ -74,6 +74,42 @@ 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, + ValidateFunc: validate.UUID, + }, + + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice( + []string{"Scope", "Role"}, + false, // force case sensitivity + ), + }, + }, + }, + }, + }, + }, + }, }, } } @@ -90,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 { @@ -140,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) } @@ -176,6 +217,10 @@ func resourceApplicationRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error setting `reply_urls`: %+v", err) } + if err := d.Set("required_resource_access", flattenADApplicationRequiredResourceAccess(resp.RequiredResourceAccess)); err != nil { + return fmt.Errorf("Error setting `required_resource_access`: %+v", err) + } + return nil } @@ -213,3 +258,82 @@ func expandADApplicationHomepage(d *schema.ResourceData, name string) *string { return p.String(fmt.Sprintf("https://%s", name)) } + +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{} { + if in == nil { + return []map[string]interface{}{} + } + + result := make([]map[string]interface{}, 0, len(*in)) + for _, requiredResourceAccess := range *in { + resource := make(map[string]interface{}) + if requiredResourceAccess.ResourceAppID != nil { + resource["resource_app_id"] = *requiredResourceAccess.ResourceAppID + } + + resource["resource_access"] = flattenADApplicationResourceAccess(requiredResourceAccess.ResourceAccess) + + result = append(result, resource) + } + + return result +} + +func flattenADApplicationResourceAccess(in *[]graphrbac.ResourceAccess) []interface{} { + if in == nil { + return []interface{}{} + } + + accesses := make([]interface{}, 0) + for _, resourceAccess := range *in { + access := make(map[string]interface{}) + if resourceAccess.ID != nil { + access["id"] = *resourceAccess.ID + } + if resourceAccess.Type != nil { + access["type"] = *resourceAccess.Type + } + accesses = append(accesses, access) + } + + return accesses +} diff --git a/azuread/resource_application_test.go b/azuread/resource_application_test.go index f041930346..e5fde86d86 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,34 @@ 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/website/docs/d/application.html.markdown b/website/docs/d/application.html.markdown index dafe0651ba..70f69564fc 100644 --- a/website/docs/d/application.html.markdown +++ b/website/docs/d/application.html.markdown @@ -47,3 +47,22 @@ output "azure_ad_object_id" { * `object_id` - the Object ID of the Azure Active Directory Application. * `reply_urls` - A list of URLs that user tokens are sent to for sign in, or the redirect URIs that OAuth 2.0 authorization codes and access tokens are sent to. + +* `required_resource_access` - A collection of `required_resource_access` blocks as documented below. + + +--- + +`required_resource_access` block exports the following: + +* `resource_app_id` - The unique identifier for the resource that the application requires access to. + +* `resource_access` - A collection of `resource_access` blocks as documented below + +--- + +`resource_access` block exports the following: + +* `id` - The unique identifier for one of the `OAuth2Permission` or `AppRole` instances that the resource application exposes. + +* `type` - Specifies whether the id property references an `OAuth2Permission` or an `AppRole`. diff --git a/website/docs/r/application.html.markdown b/website/docs/r/application.html.markdown index 3d7fc1f0c3..5bc0980fd6 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. + +--- + +`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: