-
Notifications
You must be signed in to change notification settings - Fork 301
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #676 from hashicorp/r/delegated-permission-grant
New resource: azuread_service_principal_delegated_permission_grant
- Loading branch information
Showing
13 changed files
with
1,274 additions
and
29 deletions.
There are no files selected for viewing
128 changes: 128 additions & 0 deletions
128
docs/resources/service_principal_delegated_permission_grant.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
--- | ||
subcategory: "Delegated Permission Grants" | ||
--- | ||
|
||
# Resource: azuread_service_principal_delegated_permission_grant | ||
|
||
Manages a delegated permission grant for a service principal, on behalf of a single user, or all users. | ||
|
||
## API Permissions | ||
|
||
The following API permissions are required in order to use this resource. | ||
|
||
When authenticated with a service principal, this resource requires the following application role: `Directory.ReadWrite.All` | ||
|
||
When authenticated with a user principal, this resource requires one the following directory role: `Global Administrator` | ||
|
||
## Example Usage | ||
|
||
*Delegated permission grant for all users* | ||
|
||
```terraform | ||
data "azuread_application_published_app_ids" "well_known" {} | ||
resource "azuread_service_principal" "msgraph" { | ||
application_id = data.azuread_application_published_app_ids.well_known.result.MicrosoftGraph | ||
use_existing = true | ||
} | ||
resource "azuread_application" "example" { | ||
display_name = "example" | ||
required_resource_access { | ||
resource_app_id = data.azuread_application_published_app_ids.well_known.result.MicrosoftGraph | ||
resource_access { | ||
id = azuread_service_principal.msgraph.oauth2_permission_scope_ids["openid"] | ||
type = "Scope" | ||
} | ||
resource_access { | ||
id = azuread_service_principal.msgraph.oauth2_permission_scope_ids["User.Read"] | ||
type = "Scope" | ||
} | ||
} | ||
} | ||
resource "azuread_service_principal" "example" { | ||
application_id = azuread_application.example.application_id | ||
} | ||
resource "azuread_service_principal_delegated_permission_grant" "example" { | ||
service_principal_object_id = azuread_service_principal.example.object_id | ||
resource_service_principal_object_id = azuread_service_principal.msgraph.object_id | ||
claim_values = ["openid", "User.Read.All"] | ||
} | ||
``` | ||
|
||
*Delegated permission grant for a single user* | ||
|
||
```terraform | ||
data "azuread_application_published_app_ids" "well_known" {} | ||
resource "azuread_service_principal" "msgraph" { | ||
application_id = data.azuread_application_published_app_ids.well_known.result.MicrosoftGraph | ||
use_existing = true | ||
} | ||
resource "azuread_application" "example" { | ||
display_name = "example" | ||
required_resource_access { | ||
resource_app_id = data.azuread_application_published_app_ids.well_known.result.MicrosoftGraph | ||
resource_access { | ||
id = azuread_service_principal.msgraph.oauth2_permission_scope_ids["openid"] | ||
type = "Scope" | ||
} | ||
resource_access { | ||
id = azuread_service_principal.msgraph.oauth2_permission_scope_ids["User.Read"] | ||
type = "Scope" | ||
} | ||
} | ||
} | ||
resource "azuread_service_principal" "example" { | ||
application_id = azuread_application.example.application_id | ||
} | ||
resource "azuread_user" "example" { | ||
display_name = "J. Doe" | ||
user_principal_name = "[email protected]" | ||
mail_nickname = "jdoe" | ||
password = "SecretP@sswd99!" | ||
} | ||
resource "azuread_service_principal_delegated_permission_grant" "example" { | ||
service_principal_object_id = azuread_service_principal.example.object_id | ||
resource_service_principal_object_id = azuread_service_principal.msgraph.object_id | ||
claim_values = ["openid", "User.Read.All"] | ||
user_object_id = azuread_user.example.object_id | ||
} | ||
``` | ||
|
||
## Argument Reference | ||
|
||
The following arguments are supported: | ||
|
||
* `claim_values` - (Required) - A set of claim values for delegated permission scopes which should be included in access tokens for the resource. | ||
* `resource_service_principal_object_id` - (Required) The object ID of the service principal representing the resource to be accessed. Changing this forces a new resource to be created. | ||
* `service_principal_object_id` - (Required) The object ID of the service principal for which this delegated permission grant should be created. Changing this forces a new resource to be created. | ||
* `user_object_id` - (Optional) - The object ID of the user on behalf of whom the service principal is authorized to access the resource. When omitted, the delegated permission grant will be consented for all users. Changing this forces a new resource to be created. | ||
|
||
-> **Granting Admin Consent** To grant admin consent for the service principal to impersonate all users, just omit the `user_object_id` property. | ||
|
||
## Attributes Reference | ||
|
||
In addition to all arguments above, the following attributes are exported: | ||
|
||
* `id` - The ID of the delegated permission grant. | ||
|
||
## Import | ||
|
||
Delegated permission grants can be imported using their ID, e.g. | ||
|
||
```shell | ||
terraform import azuread_service_principal_delegated_permission_grant.example aaBBcDDeFG6h5JKLMN2PQrrssTTUUvWWxxxxxyyyzzz | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
168 changes: 168 additions & 0 deletions
168
internal/services/serviceprincipals/service_principal_delegated_permission_grant_resource.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
package serviceprincipals | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"log" | ||
"net/http" | ||
"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/manicminer/hamilton/odata" | ||
|
||
"github.com/hashicorp/terraform-provider-azuread/internal/clients" | ||
"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 servicePrincipalDelegatedPermissionGrantResource() *schema.Resource { | ||
return &schema.Resource{ | ||
CreateContext: servicePrincipalDelegatedPermissionGrantResourceCreate, | ||
UpdateContext: servicePrincipalDelegatedPermissionGrantResourceUpdate, | ||
ReadContext: servicePrincipalDelegatedPermissionGrantResourceRead, | ||
DeleteContext: servicePrincipalDelegatedPermissionGrantResourceDelete, | ||
|
||
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), | ||
}, | ||
|
||
Schema: map[string]*schema.Schema{ | ||
"claim_values": { | ||
Description: "A set of claim values for delegated permission scopes which should be included in access tokens for the resource", | ||
Type: schema.TypeSet, | ||
Required: true, | ||
MinItems: 1, | ||
Elem: &schema.Schema{ | ||
Type: schema.TypeString, | ||
ValidateDiagFunc: validate.NoEmptyStrings, | ||
}, | ||
}, | ||
|
||
"resource_service_principal_object_id": { | ||
Description: "The object ID of the service principal representing the resource to be accessed", | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
ValidateDiagFunc: validate.UUID, | ||
}, | ||
|
||
"service_principal_object_id": { | ||
Description: "The object ID of the service principal for which this delegated permission grant should be created", | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
ValidateDiagFunc: validate.UUID, | ||
}, | ||
|
||
"user_object_id": { | ||
Description: "The object ID of the user on behalf of whom the service principal is authorized to access the resource", | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ForceNew: true, | ||
ValidateDiagFunc: validate.UUID, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func servicePrincipalDelegatedPermissionGrantResourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
client := meta.(*clients.Client).ServicePrincipals.DelegatedPermissionGrantsClient | ||
servicePrincipalsClient := meta.(*clients.Client).ServicePrincipals.ServicePrincipalsClient | ||
|
||
servicePrincipalId := d.Get("service_principal_object_id").(string) | ||
resourceId := d.Get("resource_service_principal_object_id").(string) | ||
|
||
if _, status, err := servicePrincipalsClient.Get(ctx, servicePrincipalId, odata.Query{}); err != nil { | ||
if status == http.StatusNotFound { | ||
return tf.ErrorDiagPathF(err, "principal_object_id", "Service principal with object ID %q was not found)", servicePrincipalId) | ||
} | ||
return tf.ErrorDiagF(err, "Could not retrieve service principal with object ID %q", servicePrincipalId) | ||
} | ||
|
||
if _, status, err := servicePrincipalsClient.Get(ctx, resourceId, odata.Query{}); err != nil { | ||
if status == http.StatusNotFound { | ||
return tf.ErrorDiagPathF(err, "principal_object_id", "Service principal not found for resource (Object ID: %q)", resourceId) | ||
} | ||
return tf.ErrorDiagF(err, "Could not retrieve service principal for resource (Object ID: %q)", resourceId) | ||
} | ||
|
||
properties := msgraph.DelegatedPermissionGrant{ | ||
ClientId: utils.String(servicePrincipalId), | ||
ResourceId: utils.String(resourceId), | ||
Scopes: tf.ExpandStringSlicePtr(d.Get("claim_values").(*schema.Set).List()), | ||
} | ||
|
||
if v, ok := d.GetOk("user_object_id"); ok && v.(string) != "" { | ||
properties.PrincipalId = utils.String(v.(string)) | ||
properties.ConsentType = utils.String(msgraph.DelegatedPermissionGrantConsentTypePrincipal) | ||
} else { | ||
properties.ConsentType = utils.String(msgraph.DelegatedPermissionGrantConsentTypeAllPrincipals) | ||
} | ||
|
||
delegatedPermissionGrant, _, err := client.Create(ctx, properties) | ||
if err != nil { | ||
return tf.ErrorDiagF(err, "Could not create delegated permission grant") | ||
} | ||
|
||
if delegatedPermissionGrant.Id == nil || *delegatedPermissionGrant.Id == "" { | ||
return tf.ErrorDiagF(errors.New("ID returned for delegated permission grant is nil"), "Bad API response") | ||
} | ||
|
||
d.SetId(*delegatedPermissionGrant.Id) | ||
|
||
return servicePrincipalDelegatedPermissionGrantResourceRead(ctx, d, meta) | ||
} | ||
|
||
func servicePrincipalDelegatedPermissionGrantResourceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
client := meta.(*clients.Client).ServicePrincipals.DelegatedPermissionGrantsClient | ||
|
||
properties := msgraph.DelegatedPermissionGrant{ | ||
Id: utils.String(d.Id()), | ||
Scopes: tf.ExpandStringSlicePtr(d.Get("claim_values").(*schema.Set).List()), | ||
} | ||
|
||
if _, err := client.Update(ctx, properties); err != nil { | ||
return tf.ErrorDiagF(err, "Could not update delegated permission grant") | ||
} | ||
|
||
return servicePrincipalDelegatedPermissionGrantResourceRead(ctx, d, meta) | ||
} | ||
|
||
func servicePrincipalDelegatedPermissionGrantResourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
client := meta.(*clients.Client).ServicePrincipals.DelegatedPermissionGrantsClient | ||
|
||
delegatedPermissionGrant, status, err := client.Get(ctx, d.Id(), odata.Query{}) | ||
if err != nil { | ||
if status == http.StatusNoContent { | ||
log.Printf("[DEBUG] Delegated Permission Grant with ID %q was not found - removing from state", d.Id()) | ||
d.SetId("") | ||
return nil | ||
} | ||
return tf.ErrorDiagPathF(err, "id", "Retrieving Delegated Permission Grant with ID %q", d.Id()) | ||
} | ||
|
||
tf.Set(d, "claim_values", delegatedPermissionGrant.Scopes) | ||
tf.Set(d, "resource_service_principal_object_id", delegatedPermissionGrant.ResourceId) | ||
tf.Set(d, "service_principal_object_id", delegatedPermissionGrant.ClientId) | ||
tf.Set(d, "user_object_id", delegatedPermissionGrant.PrincipalId) | ||
|
||
return nil | ||
} | ||
|
||
func servicePrincipalDelegatedPermissionGrantResourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
client := meta.(*clients.Client).ServicePrincipals.DelegatedPermissionGrantsClient | ||
|
||
id := d.Id() | ||
|
||
if status, err := client.Delete(ctx, id); err != nil { | ||
return tf.ErrorDiagPathF(err, "id", "Deleting delegated permission grant with ID %q, got status %d", id, status) | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.