Skip to content

Commit

Permalink
Merge pull request #252 from terraform-providers/app/oauth2-scopes
Browse files Browse the repository at this point in the history
application: support for setting oauth2_permissions
  • Loading branch information
manicminer authored May 27, 2020
2 parents 37ae009 + 3624d82 commit 6151540
Show file tree
Hide file tree
Showing 3 changed files with 276 additions and 27 deletions.
130 changes: 128 additions & 2 deletions azuread/resource_application.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,66 @@ func resourceApplication() *schema.Resource {
Computed: true,
},

"oauth2_permissions": graph.SchemaOauth2PermissionsComputed(),
"oauth2_permissions": {
Type: schema.TypeSet,
Optional: true,
Computed: true,
ConfigMode: schema.SchemaConfigModeAttr,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"admin_consent_description": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validate.NoEmptyStrings,
},

"admin_consent_display_name": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validate.NoEmptyStrings,
},

"id": {
Type: schema.TypeString,
Computed: true,
},

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

"type": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validation.StringInSlice([]string{"Admin", "User"}, false),
},

"user_consent_description": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},

"user_consent_display_name": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},

"value": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ValidateFunc: validate.NoEmptyStrings,
},
},
},
},
},
}
}
Expand All @@ -229,6 +288,9 @@ func resourceApplicationCreate(d *schema.ResourceData, meta interface{}) error {
}
}

// We don't send Oauth2Permissions here because applications tend to get a default `user_impersonation` scope
// defined, which will either conflict if we also define it, or create an unwanted diff if we don't
// After creating the application, we update it later before this function returns, including any Oauth2Permissions
properties := graphrbac.ApplicationCreateParameters{
DisplayName: &name,
IdentifierUris: tf.ExpandStringSlicePtr(identUrls.([]interface{})),
Expand Down Expand Up @@ -312,7 +374,9 @@ func resourceApplicationCreate(d *schema.ResourceData, meta interface{}) error {
}
}

return resourceApplicationRead(d, meta)
// After creating the application, we immediately update it to ensure we overwrite any default properties
// such as the `user_impersonation` scope the application may get, whether we define such a scope or not
return resourceApplicationUpdate(d, meta)
}

func resourceApplicationUpdate(d *schema.ResourceData, meta interface{}) error {
Expand Down Expand Up @@ -359,6 +423,32 @@ func resourceApplicationUpdate(d *schema.ResourceData, meta interface{}) error {
properties.RequiredResourceAccess = expandADApplicationRequiredResourceAccess(d)
}

if d.HasChange("oauth2_permissions") {
// if the permission already exists then it must be disabled
// with no other changes before it can be edited or deleted
var app graphrbac.Application
var appProperties 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 _, OAuth2Permission := range *app.Oauth2Permissions {
*OAuth2Permission.IsEnabled = false
}
appProperties.Oauth2Permissions = app.Oauth2Permissions
if _, err := client.Patch(ctx, d.Id(), appProperties); err != nil {
return fmt.Errorf("Error disabling OAuth2 permissions for Azure AD Application with ID %q: %+v", d.Id(), err)
}

// now we can set the new state of the permission
properties.Oauth2Permissions = expandADApplicationOAuth2Permissions(d.Get("oauth2_permissions"))
}

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
Expand Down Expand Up @@ -631,6 +721,42 @@ func expandADApplicationAppRoles(i interface{}) *[]graphrbac.AppRole {
return &output
}

func expandADApplicationOAuth2Permissions(i interface{}) *[]graphrbac.OAuth2Permission {
input := i.(*schema.Set).List()
result := make([]graphrbac.OAuth2Permission, 0)

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

AdminConsentDescription := OAuth2Permissions["admin_consent_description"].(string)
AdminConsentDisplayName := OAuth2Permissions["admin_consent_display_name"].(string)
ID := OAuth2Permissions["id"].(string)
if ID == "" {
ID = uuid.New().String()
}

IsEnabled := OAuth2Permissions["is_enabled"].(bool)
Type := OAuth2Permissions["type"].(string)
UserConsentDescription := OAuth2Permissions["user_consent_description"].(string)
UserConsentDisplayName := OAuth2Permissions["user_consent_display_name"].(string)
Value := OAuth2Permissions["value"].(string)

result = append(result,
graphrbac.OAuth2Permission{
AdminConsentDescription: &AdminConsentDescription,
AdminConsentDisplayName: &AdminConsentDisplayName,
ID: &ID,
IsEnabled: &IsEnabled,
Type: &Type,
UserConsentDescription: &UserConsentDescription,
UserConsentDisplayName: &UserConsentDisplayName,
Value: &Value,
},
)
}
return &result
}

func adApplicationSetOwnersTo(client graphrbac.ApplicationsClient, ctx context.Context, id string, desiredOwners []string) error {
existingOwners, err := graph.ApplicationAllOwners(client, ctx, id)
if err != nil {
Expand Down
123 changes: 114 additions & 9 deletions azuread/resource_application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
func TestAccAzureADApplication_basic(t *testing.T) {
resourceName := "azuread_application.test"
ri := tf.AccRandTimeInt()
appName := fmt.Sprintf("acctest-APP-%d", ri)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Expand All @@ -26,12 +27,11 @@ func TestAccAzureADApplication_basic(t *testing.T) {
Config: testAccADApplication_basic(ri),
Check: resource.ComposeTestCheckFunc(
testCheckADApplicationExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("acctest-APP-%[1]d", ri)),
resource.TestCheckResourceAttr(resourceName, "homepage", fmt.Sprintf("https://acctest-APP-%d", ri)),
resource.TestCheckResourceAttr(resourceName, "name", appName),
resource.TestCheckResourceAttr(resourceName, "homepage", fmt.Sprintf("https://%s", appName)),
resource.TestCheckResourceAttr(resourceName, "oauth2_allow_implicit_flow", "false"),
resource.TestCheckResourceAttr(resourceName, "type", "webapp/api"),
resource.TestCheckResourceAttr(resourceName, "oauth2_permissions.#", "1"),
resource.TestCheckResourceAttr(resourceName, "oauth2_permissions.0.admin_consent_description", fmt.Sprintf("Allow the application to access %s on behalf of the signed-in user.", fmt.Sprintf("acctest-APP-%[1]d", ri))),
resource.TestCheckResourceAttrSet(resourceName, "application_id"),
resource.TestCheckResourceAttrSet(resourceName, "object_id"),
),
Expand Down Expand Up @@ -67,6 +67,7 @@ func TestAccAzureADApplication_complete(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "reply_urls.#", "1"),
resource.TestCheckResourceAttr(resourceName, "group_membership_claims", "All"),
resource.TestCheckResourceAttr(resourceName, "required_resource_access.#", "2"),
resource.TestCheckResourceAttr(resourceName, "oauth2_permissions.#", "2"),
resource.TestCheckResourceAttrSet(resourceName, "application_id"),
resource.TestCheckResourceAttrSet(resourceName, "object_id"),
),
Expand Down Expand Up @@ -117,6 +118,7 @@ func TestAccAzureADApplication_update(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "reply_urls.#", "1"),
resource.TestCheckResourceAttr(resourceName, "reply_urls.3714513888", "http://unittest.hashicorptest.com"),
resource.TestCheckResourceAttr(resourceName, "required_resource_access.#", "2"),
resource.TestCheckResourceAttr(resourceName, "oauth2_permissions.#", "2"),
),
},
{
Expand Down Expand Up @@ -160,7 +162,6 @@ func TestAccAzureADApplication_http_homepage(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "oauth2_allow_implicit_flow", "false"),
resource.TestCheckResourceAttr(resourceName, "type", "webapp/api"),
resource.TestCheckResourceAttr(resourceName, "oauth2_permissions.#", "1"),
resource.TestCheckResourceAttr(resourceName, "oauth2_permissions.0.admin_consent_description", fmt.Sprintf("Allow the application to access %s on behalf of the signed-in user.", fmt.Sprintf("acctest-APP-%[1]d", ri))),
resource.TestCheckResourceAttrSet(resourceName, "application_id"),
resource.TestCheckResourceAttrSet(resourceName, "object_id"),
),
Expand Down Expand Up @@ -523,6 +524,59 @@ func TestAccAzureADApplication_native_app_does_not_allow_identifier_uris(t *test
})
}

func TestAccAzureADApplication_oauth2PermissionsUpdate(t *testing.T) {
resourceName := "azuread_application.test"
ri := tf.AccRandTimeInt()

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckADApplicationDestroy,
Steps: []resource.TestStep{
{
Config: testAccADApplication_basic(ri),
Check: resource.ComposeTestCheckFunc(
testCheckADApplicationExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("acctest-APP-%[1]d", ri)),
//resource.TestCheckResourceAttr(resourceName, "identifier_uris.#", "0"),
resource.TestCheckResourceAttr(resourceName, "oauth2_permissions.#", "1"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccADApplication_oauth2Permissions(ri),
Check: resource.ComposeTestCheckFunc(
testCheckADApplicationExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("acctest-APP-%[1]d", ri)),
resource.TestCheckResourceAttr(resourceName, "oauth2_permissions.#", "2"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccADApplication_oauth2PermissionsEmpty(ri),
Check: resource.ComposeTestCheckFunc(
testCheckADApplicationExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("acctest-APP-%[1]d", ri)),
resource.TestCheckResourceAttr(resourceName, "oauth2_permissions.#", "0"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testCheckADApplicationExists(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
Expand Down Expand Up @@ -652,10 +706,6 @@ func testAccADApplication_complete(ri int, pw string) string {
return fmt.Sprintf(`
%[1]s
data "azuread_service_principal" "test" {
display_name = "Terraform AzureAD Acceptance Tests"
}
resource "azuread_application" "test" {
name = "acctest-APP-%[2]d"
homepage = "https://homepage-%[2]d"
Expand Down Expand Up @@ -693,7 +743,27 @@ resource "azuread_application" "test" {
type = "Scope"
}
}
owners = [azuread_user.test.object_id, data.azuread_service_principal.test.object_id]
oauth2_permissions {
admin_consent_description = "Administer the application"
admin_consent_display_name = "Administer"
is_enabled = true
type = "Admin"
value = "administer"
}
oauth2_permissions {
admin_consent_description = "Allow the application to access acctest-APP-%[2]d on behalf of the signed-in user."
admin_consent_display_name = "Access acctest-APP-%[2]d"
is_enabled = true
type = "User"
user_consent_description = "Allow the application to access acctest-APP-%[2]d on your behalf."
user_consent_display_name = "Access acctest-APP-%[2]d"
value = "user_impersonation"
}
owners = [azuread_user.test.object_id]
}
`, testAccADUser_basic(ri, pw), ri)
}
Expand Down Expand Up @@ -757,6 +827,41 @@ resource "azuread_application" "test" {
`, ri)
}

func testAccADApplication_oauth2Permissions(ri int) string {
return fmt.Sprintf(`
resource "azuread_application" "test" {
name = "acctest-APP-%[1]d"
oauth2_permissions {
admin_consent_description = "Administer the application"
admin_consent_display_name = "Administer"
is_enabled = true
type = "Admin"
value = "administer"
}
oauth2_permissions {
admin_consent_description = "Allow the application to access acctest-APP-%[1]d on behalf of the signed-in user."
admin_consent_display_name = "Access acctest-APP-%[1]d"
is_enabled = true
type = "User"
user_consent_description = "Allow the application to access acctest-APP-%[1]d on your behalf."
user_consent_display_name = "Access acctest-APP-%[1]d"
value = "user_impersonation"
}
}
`, ri)
}

func testAccADApplication_oauth2PermissionsEmpty(ri int) string {
return fmt.Sprintf(`
resource "azuread_application" "test" {
name = "acctest-APP-%[1]d"
oauth2_permissions = []
}
`, ri)
}

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

0 comments on commit 6151540

Please sign in to comment.