Skip to content

Commit

Permalink
New resource: azuread_pre_authorized_application
Browse files Browse the repository at this point in the history
  • Loading branch information
manicminer committed Jun 30, 2021
1 parent 7d7c740 commit 99e9a0e
Show file tree
Hide file tree
Showing 4 changed files with 465 additions and 3 deletions.
71 changes: 71 additions & 0 deletions docs/resources/pre_authorized_application.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
subcategory: "Applications"
---

# Resource: azuread_pre_authorized_application

Manages client applications that are pre-authorized with the specified permissions to access an application's APIs without requiring user consent.

## Example Usage

```terraform
resource "azuread_application" "authorized" {
display_name = "example-authorized-app"
}
resource "azuread_application" "authorizer" {
display_name = "example-authorizing-app"
api {
oauth2_permission_scope {
admin_consent_description = "Administer the application"
admin_consent_display_name = "Administer"
enabled = true
id = "ced9c4c3-c273-4f0f-ac71-a20377b90f9c"
type = "Admin"
value = "administer"
}
oauth2_permission_scope {
admin_consent_description = "Access the application"
admin_consent_display_name = "Access"
enabled = true
id = "2d5e07ca-664d-4d9b-ad61-ec07fd215213"
type = "User"
user_consent_description = "Access the application"
user_consent_display_name = "Access"
value = "user_impersonation"
}
}
}
resource "azuread_pre_authorized_application" "example" {
application_object_id = azuread_application.authorizer.object_id
authorized_app_id = azuread_application.authorized.application_id
permission_ids = ["ced9c4c3-c273-4f0f-ac71-a20377b90f9c", "2d5e07ca-664d-4d9b-ad61-ec07fd215213"]
}
```

## Argument Reference

The following arguments are supported:

* `application_object_id` - (Required) The object ID of the application for which permissions are being authorized. Changing this field forces a new resource to be created.
* `authorized_application_id` - (Optional) The application ID (client ID) of the application being authorized. Changing this field forces a new resource to be created.
* `permission_ids` - (Required) A set of permission scope IDs required by the authorized application.

## Attributes Reference

In addition to all arguments above, the following attributes are exported:

*No additional attributes are exported*

## Import

Pre-authorized applications can be imported using the object ID of the authorizing application and the application ID of the application being authorized, e.g.

```shell
terraform import azuread_pre_authorized_application.example 00000000-0000-0000-0000-000000000000/preAuthorizedApplication/11111111-1111-1111-1111-111111111111
```

-> **NOTE:** This ID format is unique to Terraform and is composed of the authorizing application's object ID, the string "preAuthorizedApplication" and the authorized application's application ID (client ID) in the format `{ObjectId}/preAuthorizedApplication/{ApplicationId}`.
258 changes: 258 additions & 0 deletions internal/services/applications/pre_authorized_application_resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
package applications

import (
"context"
"errors"
"fmt"
"log"
"net/http"
"strings"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/manicminer/hamilton/msgraph"

"github.com/hashicorp/terraform-provider-azuread/internal/clients"
"github.com/hashicorp/terraform-provider-azuread/internal/services/applications/parse"
"github.com/hashicorp/terraform-provider-azuread/internal/tf"
"github.com/hashicorp/terraform-provider-azuread/internal/utils"
"github.com/hashicorp/terraform-provider-azuread/internal/validate"
)

func preAuthorizedApplicationResource() *schema.Resource {
return &schema.Resource{
CreateContext: preAuthorizedApplicationResourceCreate,
ReadContext: preAuthorizedApplicationResourceRead,
UpdateContext: preAuthorizedApplicationResourceUpdate,
DeleteContext: preAuthorizedApplicationResourceDelete,

Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(5 * time.Minute),
Read: schema.DefaultTimeout(5 * time.Minute),
Update: schema.DefaultTimeout(5 * time.Minute),
Delete: schema.DefaultTimeout(5 * time.Minute),
},

Importer: tf.ValidateResourceIDPriorToImport(func(id string) error {
_, err := parse.PreAuthorizedApplicationID(id)
return err
}),

Schema: map[string]*schema.Schema{
"application_object_id": {
Description: "The object ID of the application to which this pre-authorized application should be added",
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateDiagFunc: validate.UUID,
},

"authorized_app_id": {
Description: "The application ID of the pre-authorized application",
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateDiagFunc: validate.UUID,
},

"permission_ids": {
Description: "The IDs of the permission scopes required by the pre-authorized application",
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateDiagFunc: validate.UUID,
},
},
},
}
}

func preAuthorizedApplicationResourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client).Applications.ApplicationsClient
id := parse.NewPreAuthorizedApplicationID(d.Get("application_object_id").(string), d.Get("authorized_app_id").(string))

tf.LockByName(applicationResourceName, id.ObjectId)
defer tf.UnlockByName(applicationResourceName, id.ObjectId)

app, status, err := client.Get(ctx, id.ObjectId)
if err != nil {
if status == http.StatusNotFound {
return tf.ErrorDiagPathF(nil, "application_object_id", "Application with object ID %q was not found", id.ObjectId)
}
return tf.ErrorDiagPathF(err, "application_object_id", "Retrieving application with object ID %q", id.ObjectId)
}
if app == nil || app.ID == nil {
return tf.ErrorDiagF(errors.New("nil application or application with nil ID was returned"), "API error retrieving application with object ID %q", id.ObjectId)
}

newPreAuthorizedApps := make([]msgraph.ApiPreAuthorizedApplication, 0)
if app.Api != nil && app.Api.PreAuthorizedApplications != nil {
for _, a := range *app.Api.PreAuthorizedApplications {
if a.AppId != nil && strings.EqualFold(*a.AppId, id.AppId) {
return tf.ImportAsExistsDiag("azuread_pre_authorized_application", id.String())
}
newPreAuthorizedApps = append(newPreAuthorizedApps, a)
}
}

newPreAuthorizedApps = append(newPreAuthorizedApps, msgraph.ApiPreAuthorizedApplication{
AppId: utils.String(id.AppId),
PermissionIds: tf.ExpandStringSlicePtr(d.Get("permission_ids").(*schema.Set).List()),
})

properties := msgraph.Application{
ID: app.ID,
Api: &msgraph.ApplicationApi{
PreAuthorizedApplications: &newPreAuthorizedApps,
},
}

if _, err := client.Update(ctx, properties); err != nil {
return tf.ErrorDiagF(err, "Adding pre-authorized application %q for application with object ID %q", id.AppId, id.ObjectId)
}

d.SetId(id.String())

return preAuthorizedApplicationResourceRead(ctx, d, meta)
}

func preAuthorizedApplicationResourceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client).Applications.ApplicationsClient
id, err := parse.PreAuthorizedApplicationID(d.Id())
if err != nil {
return tf.ErrorDiagPathF(err, "id", "Parsing pre-authorized application ID %q", d.Id())
}

tf.LockByName(applicationResourceName, id.ObjectId)
defer tf.UnlockByName(applicationResourceName, id.ObjectId)

app, status, err := client.Get(ctx, id.ObjectId)
if err != nil {
if status == http.StatusNotFound {
return tf.ErrorDiagPathF(nil, "application_object_id", "Application with object ID %q was not found", id.ObjectId)
}
return tf.ErrorDiagPathF(err, "application_object_id", "Retrieving application with object ID %q", id.ObjectId)
}
if app == nil || app.ID == nil {
return tf.ErrorDiagF(errors.New("nil application or application with nil ID was returned"), "API error retrieving application with object ID %q", id.ObjectId)
}
if app.Api == nil || app.Api.PreAuthorizedApplications == nil {
return tf.ErrorDiagF(errors.New("application with nil preAuthorizedApplications was returned"), "API error retrieving application with object ID %q", id.ObjectId)
}

found := false
newPreAuthorizedApps := *app.Api.PreAuthorizedApplications
for i, a := range newPreAuthorizedApps {
if a.AppId != nil && strings.EqualFold(*a.AppId, id.AppId) {
found = true
newPreAuthorizedApps[i].PermissionIds = tf.ExpandStringSlicePtr(d.Get("permission_ids").(*schema.Set).List())
break
}
}
if !found {
return tf.ErrorDiagF(fmt.Errorf("could not match an existing preAuthorizedApplication for %q", id.AppId), "retrieving application with object ID %q", id.ObjectId)
}

properties := msgraph.Application{
ID: app.ID,
Api: &msgraph.ApplicationApi{
PreAuthorizedApplications: &newPreAuthorizedApps,
},
}

if _, err := client.Update(ctx, properties); err != nil {
return tf.ErrorDiagF(err, "Updating pre-authorized application %q for application with object ID %q", id.AppId, id.ObjectId)
}

return preAuthorizedApplicationResourceRead(ctx, d, meta)
}

func preAuthorizedApplicationResourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client).Applications.ApplicationsClient
id, err := parse.PreAuthorizedApplicationID(d.Id())
if err != nil {
return tf.ErrorDiagPathF(err, "id", "Parsing pre-authorized application ID %q", d.Id())
}

app, status, err := client.Get(ctx, id.ObjectId)
if err != nil {
if status == http.StatusNotFound {
log.Printf("[DEBUG] Application with ID %q for pre-authorized application %q was not found - removing from state!", id.ObjectId, id.AppId)
d.SetId("")
return nil
}
return tf.ErrorDiagPathF(err, "application_object_id", "Retrieving Application with object ID %q", id.ObjectId)
}
if app == nil || app.ID == nil {
return tf.ErrorDiagF(errors.New("nil application or application with nil ID was returned"), "API error retrieving application with object ID %q", id.ObjectId)
}
if app.Api == nil || app.Api.PreAuthorizedApplications == nil {
return tf.ErrorDiagF(errors.New("application with nil preAuthorizedApplications was returned"), "API error retrieving application with object ID %q", id.ObjectId)
}

var preAuthorizedApp *msgraph.ApiPreAuthorizedApplication
for _, a := range *app.Api.PreAuthorizedApplications {
if a.AppId != nil && strings.EqualFold(*a.AppId, id.AppId) {
preAuthorizedApp = &a
break
}
}
if preAuthorizedApp == nil {
log.Printf("[DEBUG] No matching preAuthorizedApplication for ID %q - removing from state!", id)
d.SetId("")
return nil
}

d.Set("application_object_id", id.ObjectId)
d.Set("authorized_app_id", id.AppId)
d.Set("permission_ids", tf.FlattenStringSlicePtr(preAuthorizedApp.PermissionIds))

return nil
}

func preAuthorizedApplicationResourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client).Applications.ApplicationsClient
id, err := parse.PreAuthorizedApplicationID(d.Id())
if err != nil {
return tf.ErrorDiagPathF(err, "id", "Parsing pre-authorized application ID %q", d.Id())
}

app, status, err := client.Get(ctx, id.ObjectId)
if err != nil {
if status == http.StatusNotFound {
log.Printf("[DEBUG] Application with ID %q for pre-authorized application %q was not found - removing from state!", id.ObjectId, id.AppId)
d.SetId("")
return nil
}
return tf.ErrorDiagPathF(err, "application_object_id", "Retrieving Application with object ID %q", id.ObjectId)
}
if app == nil || app.ID == nil {
return tf.ErrorDiagF(errors.New("nil application or application with nil ID was returned"), "API error retrieving application with object ID %q", id.ObjectId)
}
if app.Api == nil || app.Api.PreAuthorizedApplications == nil {
return tf.ErrorDiagF(errors.New("application with nil preAuthorizedApplications was returned"), "API error retrieving application with object ID %q", id.ObjectId)
}

newPreAuthorizedApps := make([]msgraph.ApiPreAuthorizedApplication, 0)
for _, a := range *app.Api.PreAuthorizedApplications {
if a.AppId != nil && !strings.EqualFold(*a.AppId, id.AppId) {
newPreAuthorizedApps = append(newPreAuthorizedApps, a)
break
}
}

properties := msgraph.Application{
ID: app.ID,
Api: &msgraph.ApplicationApi{
PreAuthorizedApplications: &newPreAuthorizedApps,
},
}

if _, err := client.Update(ctx, properties); err != nil {
return tf.ErrorDiagF(err, "Removing pre-authorized application %q from application with object ID %q", id.AppId, id.ObjectId)
}

return nil
}
Loading

0 comments on commit 99e9a0e

Please sign in to comment.