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

Feature/application app roles #98

Merged
merged 14 commits into from
Jun 12, 2019
158 changes: 155 additions & 3 deletions azuread/resource_application.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ import (
"log"

"github.com/Azure/azure-sdk-for-go/services/graphrbac/1.6/graphrbac"

"github.com/google/uuid"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"

"github.com/terraform-providers/terraform-provider-azuread/azuread/helpers/ar"
"github.com/terraform-providers/terraform-provider-azuread/azuread/helpers/graph"
"github.com/terraform-providers/terraform-provider-azuread/azuread/helpers/p"
Expand Down Expand Up @@ -94,6 +93,56 @@ func resourceApplication() *schema.Resource {
Default: "webapp/api",
},

"app_role": {
steve-hawkins marked this conversation as resolved.
Show resolved Hide resolved
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Computed: true,
},

"allowed_member_types": {
Type: schema.TypeSet,
Required: true,
MinItems: 1,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice(
[]string{"User", "Application"},
false,
),
},
},

"description": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validate.NoEmptyStrings,
},

"display_name": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validate.NoEmptyStrings,
},

"is_enabled": {
Type: schema.TypeBool,
Optional: true,
Default: true,
},

"value": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validate.NoEmptyStrings,
},
},
},
},

"required_resource_access": {
Type: schema.TypeSet,
Optional: true,
Expand Down Expand Up @@ -159,6 +208,7 @@ func resourceApplicationCreate(d *schema.ResourceData, meta interface{}) error {
ReplyUrls: tf.ExpandStringSlicePtr(d.Get("reply_urls").(*schema.Set).List()),
AvailableToOtherTenants: p.Bool(d.Get("available_to_other_tenants").(bool)),
RequiredResourceAccess: expandADApplicationRequiredResourceAccess(d),
AppRoles: expandADApplicationAppRoles(d.Get("app_role")),
}

if v, ok := d.GetOk("homepage"); ok {
Expand Down Expand Up @@ -251,6 +301,32 @@ func resourceApplicationUpdate(d *schema.ResourceData, meta interface{}) error {
properties.RequiredResourceAccess = expandADApplicationRequiredResourceAccess(d)
}

if d.HasChange("app_role") {
// if the app role already exists then it must be disabled
// with no other changes before it can be edited or deleted
var app graphrbac.Application
var appRolesProperties graphrbac.ApplicationUpdateParameters
resp, err := client.Get(ctx, d.Id())
if err != nil {
if ar.ResponseWasNotFound(resp.Response) {
return fmt.Errorf("Error: AzureAD Application with ID %q was not found", d.Id())
}

return fmt.Errorf("Error making Read request on AzureAD Application with ID %q: %+v", d.Id(), err)
}
app = resp
for _, appRole := range *app.AppRoles {
*appRole.IsEnabled = false
}
appRolesProperties.AppRoles = app.AppRoles
if _, err := client.Patch(ctx, d.Id(), appRolesProperties); err != nil {
return fmt.Errorf("Error disabling App Roles for Azure AD Application with ID %q: %+v", d.Id(), err)
}

// now we can set the new state of the app role
properties.AppRoles = expandADApplicationAppRoles(d.Get("app_role"))
}

if d.HasChange("group_membership_claims") {
properties.GroupMembershipClaims = d.Get("group_membership_claims")
}
Expand All @@ -265,7 +341,6 @@ func resourceApplicationUpdate(d *schema.ResourceData, meta interface{}) error {
properties.IdentifierUris = &[]string{}
default:
return fmt.Errorf("Error paching Azure AD Application with ID %q: Unknow application type %v. Supported types are [webapp/api, native]", d.Id(), appType)

}
}

Expand Down Expand Up @@ -320,6 +395,10 @@ func resourceApplicationRead(d *schema.ResourceData, meta interface{}) error {
return fmt.Errorf("Error setting `required_resource_access`: %+v", err)
}

if err := d.Set("app_role", flattenADApplicationAppRoles(app.AppRoles)); err != nil {
return fmt.Errorf("Error setting `app_role`: %+v", err)
}

if err := d.Set("oauth2_permissions", graph.FlattenOauth2Permissions(app.Oauth2Permissions)); err != nil {
return fmt.Errorf("Error setting `oauth2_permissions`: %+v", err)
}
Expand Down Expand Up @@ -432,3 +511,76 @@ func flattenADApplicationResourceAccess(in *[]graphrbac.ResourceAccess) []interf

return accesses
}

func expandADApplicationAppRoles(i interface{}) *[]graphrbac.AppRole {
input := i.(*schema.Set).List()
if len(input) == 0 {
return nil
}

var output []graphrbac.AppRole

for _, appRoleRaw := range input {
appRole := appRoleRaw.(map[string]interface{})

appRoleID := appRole["id"].(string)
katbyte marked this conversation as resolved.
Show resolved Hide resolved
if appRoleID == "" {
appRoleID = uuid.New().String()
}

var appRoleAllowedMemberTypes []string
for _, appRoleAllowedMemberType := range appRole["allowed_member_types"].(*schema.Set).List() {
appRoleAllowedMemberTypes = append(appRoleAllowedMemberTypes, appRoleAllowedMemberType.(string))
}

appRoleDescription := appRole["description"].(string)
appRoleDisplayName := appRole["display_name"].(string)
appRoleIsEnabled := appRole["is_enabled"].(bool)
appRoleValue := appRole["value"].(string)

output = append(output,
graphrbac.AppRole{
ID: &appRoleID,
AllowedMemberTypes: &appRoleAllowedMemberTypes,
Description: &appRoleDescription,
DisplayName: &appRoleDisplayName,
IsEnabled: &appRoleIsEnabled,
Value: &appRoleValue,
},
)
}

return &output
}

func flattenADApplicationAppRoles(in *[]graphrbac.AppRole) []interface{} {
if in == nil {
return []interface{}{}
}

appRoles := make([]interface{}, 0)
for _, role := range *in {
appRole := make(map[string]interface{})
if role.ID != nil {
appRole["id"] = *role.ID
}
if role.AllowedMemberTypes != nil {
appRole["allowed_member_types"] = *role.AllowedMemberTypes
}
if role.Description != nil {
appRole["description"] = *role.Description
}
if role.DisplayName != nil {
appRole["display_name"] = *role.DisplayName
}
if role.IsEnabled != nil {
appRole["is_enabled"] = *role.IsEnabled
}
if role.Value != nil {
appRole["value"] = *role.Value
}
appRoles = append(appRoles, appRole)
}

return appRoles
}
147 changes: 146 additions & 1 deletion azuread/resource_application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"github.com/google/uuid"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"

"github.com/terraform-providers/terraform-provider-azuread/azuread/helpers/ar"
)

Expand Down Expand Up @@ -138,6 +137,111 @@ func TestAccAzureADApplication_availableToOtherTenants(t *testing.T) {
})
}

func TestAccAzureADApplication_appRoles(t *testing.T) {
resourceName := "azuread_application.test"
id := uuid.New().String()

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckADApplicationDestroy,
Steps: []resource.TestStep{
{
Config: testAccADApplication_appRoles(id),
Check: resource.ComposeTestCheckFunc(
testCheckADApplicationExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "app_role.#", "1"),
resource.TestCheckResourceAttr(resourceName, "app_role.3282540397.allowed_member_types.#", "2"),
resource.TestCheckResourceAttr(resourceName, "app_role.3282540397.allowed_member_types.2550101162", "Application"),
resource.TestCheckResourceAttr(resourceName, "app_role.3282540397.allowed_member_types.2906997583", "User"),
resource.TestCheckResourceAttr(resourceName, "app_role.3282540397.description", "Admins can manage roles and perform all task actions"),
resource.TestCheckResourceAttr(resourceName, "app_role.3282540397.display_name", "Admin"),
resource.TestCheckResourceAttr(resourceName, "app_role.3282540397.is_enabled", "true"),
resource.TestCheckResourceAttr(resourceName, "app_role.3282540397.value", "Admin"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccAzureADApplication_appRolesUpdate(t *testing.T) {
resourceName := "azuread_application.test"
id := uuid.New().String()

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckADApplicationDestroy,
Steps: []resource.TestStep{
{
Config: testAccADApplication_appRoles(id),
Check: resource.ComposeTestCheckFunc(
testCheckADApplicationExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "app_role.#", "1"),
resource.TestCheckResourceAttr(resourceName, "app_role.3282540397.allowed_member_types.#", "2"),
resource.TestCheckResourceAttr(resourceName, "app_role.3282540397.allowed_member_types.2550101162", "Application"),
resource.TestCheckResourceAttr(resourceName, "app_role.3282540397.allowed_member_types.2906997583", "User"),
resource.TestCheckResourceAttr(resourceName, "app_role.3282540397.description", "Admins can manage roles and perform all task actions"),
resource.TestCheckResourceAttr(resourceName, "app_role.3282540397.display_name", "Admin"),
resource.TestCheckResourceAttr(resourceName, "app_role.3282540397.is_enabled", "true"),
resource.TestCheckResourceAttr(resourceName, "app_role.3282540397.value", "Admin"),
),
},
{
Config: testAccADApplication_appRolesUpdate(id),
Check: resource.ComposeTestCheckFunc(
testCheckADApplicationExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "app_role.#", "2"),
resource.TestCheckResourceAttr(resourceName, "app_role.1786747921.allowed_member_types.#", "1"),
resource.TestCheckResourceAttr(resourceName, "app_role.1786747921.allowed_member_types.2906997583", "User"),
resource.TestCheckResourceAttr(resourceName, "app_role.1786747921.description", "ReadOnly roles have limited query access"),
resource.TestCheckResourceAttr(resourceName, "app_role.1786747921.display_name", "ReadOnly"),
resource.TestCheckResourceAttr(resourceName, "app_role.1786747921.is_enabled", "true"),
resource.TestCheckResourceAttr(resourceName, "app_role.1786747921.value", "User"),
resource.TestCheckResourceAttr(resourceName, "app_role.2608972077.allowed_member_types.#", "1"),
resource.TestCheckResourceAttr(resourceName, "app_role.2608972077.allowed_member_types.2906997583", "User"),
resource.TestCheckResourceAttr(resourceName, "app_role.2608972077.description", "Admins can manage roles and perform all task actions"),
resource.TestCheckResourceAttr(resourceName, "app_role.2608972077.display_name", "Admin"),
resource.TestCheckResourceAttr(resourceName, "app_role.2608972077.is_enabled", "true"),
resource.TestCheckResourceAttr(resourceName, "app_role.2608972077.value", "Admin"),
),
},
},
})
}

func TestAccAzureADApplication_appRolesDelete(t *testing.T) {
resourceName := "azuread_application.test"
id := uuid.New().String()

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckADApplicationDestroy,
Steps: []resource.TestStep{
{
Config: testAccADApplication_appRolesUpdate(id),
Check: resource.ComposeTestCheckFunc(
testCheckADApplicationExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "app_role.#", "2"),
),
},
{
Config: testAccADApplication_appRoles(id),
Check: resource.ComposeTestCheckFunc(
testCheckADApplicationExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "app_role.#", "1"),
),
},
},
})
}

func TestAccAzureADApplication_groupMembershipClaimsUpdate(t *testing.T) {
resourceName := "azuread_application.test"
id := uuid.New().String()
Expand Down Expand Up @@ -436,6 +540,47 @@ resource "azuread_application" "test" {
`, id, id, id)
}

func testAccADApplication_appRoles(id string) string {
return fmt.Sprintf(`
resource "azuread_application" "test" {
name = "acctest%s"
app_role {
allowed_member_types = [
"User",
"Application",
]
description = "Admins can manage roles and perform all task actions"
display_name = "Admin"
is_enabled = true
value = "Admin"
}
}
`, id)
}

func testAccADApplication_appRolesUpdate(id string) string {
return fmt.Sprintf(`
resource "azuread_application" "test" {
name = "acctest%s"
app_role {
allowed_member_types = ["User"]
description = "Admins can manage roles and perform all task actions"
display_name = "Admin"
is_enabled = true
value = "Admin"
}

app_role {
allowed_member_types = ["User"]
description = "ReadOnly roles have limited query access"
display_name = "ReadOnly"
is_enabled = true
value = "User"
}
}
`, id)
}

func testAccADApplication_native(id string) string {
return fmt.Sprintf(`
resource "azuread_application" "test" {
Expand Down
Loading