Skip to content

Commit

Permalink
✨ Add Claims Mapping Policy Assignment Resource
Browse files Browse the repository at this point in the history
Adds support for the claims mapping policy assignment resource so
claims mapping policies can be assigned to a service principle
with Terraform.

Related to:
- manicminer/hamilton#147
- hashicorp#644
- https://docs.microsoft.com/en-us/graph/api/serviceprincipal-post-claimsmappingpolicies?view=graph-rest-1.0&tabs=http
  • Loading branch information
computeracer committed Mar 17, 2022
1 parent 6298318 commit d5154d1
Show file tree
Hide file tree
Showing 5 changed files with 346 additions and 0 deletions.
46 changes: 46 additions & 0 deletions docs/resources/claims_mapping_policy_assignment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

---
subcategory: "Policies"
---

# Resource: claims_mapping_policy_assignment

Manages a Claims Mapping Policy Assignment within Azure Active Directory.

## 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 roles: `Policy.ReadWrite.ApplicationConfiguration`

When authenticated with a user principal, this resource requires one of the following directory roles: `Application Administrator` or `Global Administrator`

## Example Usage

```terraform
resource "azuread_claims_mapping_policy_assignment" "app" {
claims_mapping_policy_id = azuread_claims_mapping_policy.my_policy.id
service_principal_id = azuread_service_principal.my_principal.id
}
```

## Argument Reference

The following arguments are supported:

* `claims_mapping_policy_id` - (Required) The `id` of the claims mapping policy to assign.
* `service_principal_id` - (Required) The `id` of the service principal for the policy assignment.

## Attributes Reference

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

* `id` - The ID of the Claims Mapping Policy Assignment.

## Import

Claims Mapping Policy can be imported using the `id`, in the form `service-principal-uuid/azuread_claims_mapping_policy/claims-mapping-policy-uuid`, e.g:

```shell
terraform import azuread_claims_mapping_policy_assignment.app 00000000-0000-0000-0000-000000000000/azuread_claims_mapping_policy/00000000-0000-0000-0000-000000000000
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package parse

import "fmt"

type ClaimsMappingPolicyAssignmentId struct {
ObjectSubResourceId
ServicePolicyId string
ClaimsMappingPolicyId string
}

func NewClaimsMappingPolicyAssignmentID(ServicePolicyId, ClaimsMappingPolicyId string) ClaimsMappingPolicyAssignmentId {
return ClaimsMappingPolicyAssignmentId{
ObjectSubResourceId: NewObjectSubResourceID(ServicePolicyId, "azuread_claims_mapping_policy", ClaimsMappingPolicyId),
ServicePolicyId: ServicePolicyId,
ClaimsMappingPolicyId: ClaimsMappingPolicyId,
}
}

func ClaimsMappingPolicyAssignmentID(idString string) (*ClaimsMappingPolicyAssignmentId, error) {
id, err := ObjectSubResourceID(idString, "azuread_claims_mapping_policy")
if err != nil {
return nil, fmt.Errorf("unable to parse azuread_claims_mapping_policy ID: %v", err)
}

return &ClaimsMappingPolicyAssignmentId{
ObjectSubResourceId: *id,
ServicePolicyId: id.objectId,
ClaimsMappingPolicyId: id.subId,
}, nil
}
1 change: 1 addition & 0 deletions internal/services/serviceprincipals/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func (r Registration) SupportedDataSources() map[string]*schema.Resource {
func (r Registration) SupportedResources() map[string]*schema.Resource {
return map[string]*schema.Resource{
"azuread_claims_mapping_policy": servicePrincipalClaimsMappingPolicy(),
"azuread_claims_mapping_policy_assignment": servicePrincipalClaimsMappingPolicyAssignment(),
"azuread_service_principal": servicePrincipalResource(),
"azuread_service_principal_certificate": servicePrincipalCertificateResource(),
"azuread_service_principal_delegated_permission_grant": servicePrincipalDelegatedPermissionGrantResource(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package serviceprincipals

import (
"context"
"log"
"net/http"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-provider-azuread/internal/clients"
"github.com/hashicorp/terraform-provider-azuread/internal/services/serviceprincipals/parse"
"github.com/hashicorp/terraform-provider-azuread/internal/tf"
"github.com/hashicorp/terraform-provider-azuread/internal/utils"
"github.com/manicminer/hamilton/msgraph"
"github.com/manicminer/hamilton/odata"
)

func servicePrincipalClaimsMappingPolicyAssignment() *schema.Resource {
return &schema.Resource{
CreateContext: servicePrincipalClaimsMappingPolicyAssignmentResourceCreate,
ReadContext: servicePrincipalClaimsMappingPolicyAssignmentResourceRead,
DeleteContext: servicePrincipalClaimsMappingPolicyAssignmentResourceDelete,

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

Schema: map[string]*schema.Schema{
"claims_mapping_policy_id": {
Description: "ID of the claims mapping policy to assign",
ForceNew: true,
Type: schema.TypeString,
Required: true,
},

"service_principal_id": {
Description: "ID of the service principal to assign the policy to",
ForceNew: true,
Type: schema.TypeString,
Required: true,
},
},
}
}

func servicePrincipalClaimsMappingPolicyAssignmentResourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
spClient := meta.(*clients.Client).ServicePrincipals.ServicePrincipalsClient
policyClient := meta.(*clients.Client).ServicePrincipals.ClaimsMappingPolicyClient

policyID := d.Get("claims_mapping_policy_id").(string)
policy, _, err := policyClient.Get(ctx, policyID, odata.Query{})
if err != nil {
return tf.ErrorDiagF(
err,
"Could not find ClaimsMappingPolicy, claims_mapping_policy_id: %q",
policyID,
)
}

properties := msgraph.ServicePrincipal{
DirectoryObject: msgraph.DirectoryObject{
ID: utils.String(d.Get("service_principal_id").(string)),
},
ClaimsMappingPolicies: &[]msgraph.ClaimsMappingPolicy{
*policy,
},
}
_, err = spClient.AssignClaimsMappingPolicy(ctx, &properties)
if err != nil {
return tf.ErrorDiagF(
err,
"Could not create ClaimsMappingPolicyAssignment, service_principal_id: %q, claims_mapping_policy_id: %q",
*properties.DirectoryObject.ID,
*(*properties.ClaimsMappingPolicies)[0].DirectoryObject.ID,
)
}

resourceID := parse.NewClaimsMappingPolicyAssignmentID(
*properties.DirectoryObject.ID,
*(*properties.ClaimsMappingPolicies)[0].DirectoryObject.ID,
)

d.SetId(resourceID.String())

return servicePrincipalClaimsMappingPolicyAssignmentResourceRead(ctx, d, meta)
}

func servicePrincipalClaimsMappingPolicyAssignmentResourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client).ServicePrincipals.ServicePrincipalsClient

id, err := parse.ClaimsMappingPolicyAssignmentID(d.Id())
if err != nil {
return tf.ErrorDiagPathF(err, "id", "Parsing Claims Mapping Policy Assignment ID %q", d.Id())
}

spID := id.ServicePolicyId

policyList, status, err := client.ListClaimsMappingPolicy(ctx, spID)
if err != nil {
if status == http.StatusNotFound {
log.Printf("[DEBUG] Service Principal with Object ID %q was not found - removing claims mapping policy assignment from state!", spID)
d.SetId("")
return nil
}

return tf.ErrorDiagF(err, "listing Claims Mapping Policy Assignments for Service Principal with object ID: %q", d.Id())
}

policyID := id.ClaimsMappingPolicyId
var foundPolicy *msgraph.ClaimsMappingPolicy

// Check the assignment is found in the currently assigned policies
for _, policy := range *policyList {
if *policy.ID == policyID {
foundPolicy = &policy
break
}
}
if foundPolicy == nil {
d.SetId("")
log.Printf("[DEBUG] Claims Mapping Policy with Object ID %q was not found - removing assignment from state!", policyID)
return nil
}

tf.Set(d, "service_principal_id", spID)
tf.Set(d, "claims_mapping_policy_id", policyID)

return nil
}

func servicePrincipalClaimsMappingPolicyAssignmentResourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client).ServicePrincipals.ServicePrincipalsClient

id, err := parse.ClaimsMappingPolicyAssignmentID(d.Id())
if err != nil {
return tf.ErrorDiagPathF(err, "id", "Parsing Claims Mapping Policy Assignment ID %q", d.Id())
}

claimIDs := []string{id.ClaimsMappingPolicyId}

spID := id.ServicePolicyId

sp := msgraph.ServicePrincipal{
DirectoryObject: msgraph.DirectoryObject{
ID: &spID,
},
}
_, err = client.RemoveClaimsMappingPolicy(ctx, &sp, &claimIDs)
if err != nil {
return tf.ErrorDiagF(err, "Could not Remove ClaimsMappingPolicyAssignment, service_principal_id: %q, claims_mapping_policy_ids: %q", spID, claimIDs)
}

return servicePrincipalClaimsMappingPolicyAssignmentResourceRead(ctx, d, meta)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package serviceprincipals_test

import (
"context"
"fmt"
"net/http"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/hashicorp/terraform-provider-azuread/internal/acceptance"
"github.com/hashicorp/terraform-provider-azuread/internal/acceptance/check"
"github.com/hashicorp/terraform-provider-azuread/internal/clients"
"github.com/hashicorp/terraform-provider-azuread/internal/utils"
)

type ServicePrincipalClaimsMappingPolicyAssignment struct{}

func TestClaimsMappingPolicyAssignment_basic(t *testing.T) {
data := acceptance.BuildTestData(t, "azuread_claims_mapping_policy_assignment", "test")
mappingPolicy := acceptance.TestData{
ResourceName: "azuread_claims_mapping_policy.test2",
}
r := ServicePrincipalClaimsMappingPolicyAssignment{}

data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.basicClaimsMappingPolicyAssignment(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.updateClaimsMappingPolicyAssignment(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key(
"claims_mapping_policy_id",
).MatchesOtherKey(
check.That(mappingPolicy.ResourceName).Key(
"id",
),
),
),
},
})
}

func (ServicePrincipalClaimsMappingPolicyAssignment) basicClaimsMappingPolicyAssignment(data acceptance.TestData) string {
c := ServicePrincipalClaimsMappingPolicy{}
return c.basicClaimsMappingPolicy(data) + `
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_claims_mapping_policy_assignment" "test" {
service_principal_id = azuread_service_principal.msgraph.id
claims_mapping_policy_id = azuread_claims_mapping_policy.test.id
}
`
}

func (ServicePrincipalClaimsMappingPolicyAssignment) updateClaimsMappingPolicyAssignment(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azuread" {}
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_claims_mapping_policy" "test2" {
definition = [
"{\"ClaimsMappingPolicy\":{\"Version\":1,\"IncludeBasicClaimSet\":\"false\",\"ClaimsSchema\": [{\"Source\":\"user\",\"ID\":\"employeeid\",\"SamlClaimType\":\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name\",\"JwtClaimType\":\"name\"},{\"Source\":\"company\",\"ID\":\"tenantcountry\",\"SamlClaimType\":\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/country\",\"JwtClaimType\":\"country\"}]}}"
]
description = "%[1]s"
display_name = "integration-%[1]s"
}
resource "azuread_claims_mapping_policy_assignment" "test" {
service_principal_id = azuread_service_principal.msgraph.id
claims_mapping_policy_id = azuread_claims_mapping_policy.test2.id
}
`, data.RandomString)
}

func (r ServicePrincipalClaimsMappingPolicyAssignment) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) {
client := clients.ServicePrincipals.ServicePrincipalsClient
client.BaseClient.DisableRetries = true

spID := state.Attributes["service_principal_id"]
policyList, status, err := client.ListClaimsMappingPolicy(ctx, spID)
if err != nil {
if status == http.StatusNotFound {
return utils.Bool(false), fmt.Errorf("Service Policy with object ID %q does not exist", spID)
}
return utils.Bool(false), fmt.Errorf("failed to retrieve claims mapping policy assignments with service policy ID %q: %+v", spID, err)
}

policyID := state.Attributes["claims_mapping_policy_id"]

// Check the assignment is found in the currently assigned policies
for _, policy := range *policyList {
if *policy.ID == policyID {
return utils.Bool(true), nil
}
}
return utils.Bool(false), nil
}

0 comments on commit d5154d1

Please sign in to comment.