Skip to content

Commit

Permalink
Feature/application app roles (#98)
Browse files Browse the repository at this point in the history
this should resolve #75
  • Loading branch information
Steve Hawkins authored and katbyte committed Jun 12, 2019
1 parent f01cbc7 commit d1c18e8
Show file tree
Hide file tree
Showing 3 changed files with 333 additions and 6 deletions.
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": {
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)
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

0 comments on commit d1c18e8

Please sign in to comment.