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 authored and dhohengassner committed Feb 23, 2022
1 parent 49f95d6 commit 944da8c
Show file tree
Hide file tree
Showing 13 changed files with 680 additions and 11 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: "Claims Mapping Policy Assignment"
---

# 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
```
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,4 @@ require (

go 1.17

replace github.com/manicminer/hamilton => github.com/o11n/hamilton v0.40.2-0.20220217143703-8395e584f1c4
replace github.com/manicminer/hamilton => github.com/o11n/hamilton v0.40.2-0.20220223160839-c750ea366236
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,8 @@ github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs=
github.com/o11n/hamilton v0.40.2-0.20220217143703-8395e584f1c4 h1:ai0wzt2ne+aHdBZ6ibAKODjMf6FTwgbvpxf2sp4kW3c=
github.com/o11n/hamilton v0.40.2-0.20220217143703-8395e584f1c4/go.mod h1:IOYn2Dc9SUiZ7Ryw6c8Ay795vPPMnrCZe3MktS447dc=
github.com/o11n/hamilton v0.40.2-0.20220223160839-c750ea366236 h1:B69a34r+rzT/tir3/JPQ9XGh1DSuEDwR2okVH+nFRC8=
github.com/o11n/hamilton v0.40.2-0.20220223160839-c750ea366236/go.mod h1:IOYn2Dc9SUiZ7Ryw6c8Ay795vPPMnrCZe3MktS447dc=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
Expand Down
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 @@ -35,5 +35,6 @@ func (r Registration) SupportedResources() map[string]*schema.Resource {
"azuread_service_principal_delegated_permission_grant": servicePrincipalDelegatedPermissionGrantResource(),
"azuread_service_principal_password": servicePrincipalPasswordResource(),
"azuread_claims_mapping_policy": servicePrincipalClaimsMappingPolicy(),
"azuread_claims_mapping_policy_assignment": servicePrincipalClaimsMappingPolicyAssignment(),
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
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/manicminer/hamilton/msgraph"
)

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 {
client := meta.(*clients.Client).ServicePrincipals.ServicePrincipalsClient

claimID := d.Get("claims_mapping_policy_id").(string)

assignment := []msgraph.ClaimsMappingPolicy{
{
ID: &claimID,
},
}

spID := d.Get("service_principal_id").(string)

sp := msgraph.ServicePrincipal{
DirectoryObject: msgraph.DirectoryObject{
ID: &spID,
},
ClaimsMappingPolicies: &assignment,
}
_, err := client.AssignClaimsMappingPolicy(ctx, &sp)
if err != nil {
return tf.ErrorDiagF(err, "Could not create ClaimsMappingPolicyAssignment, service_principal_id: %q, claims_mapping_policy_id: %q", spID, claimID)
}

resourceID := parse.NewClaimsMappingPolicyAssignmentID(spID, claimID)

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,135 @@
package serviceprincipals_test

import (
"context"
"fmt"
"log"
"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/manicminer/hamilton/msgraph"
)

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 {
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" "test" {
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.test.id
}
`, data.RandomString)
}

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

exists := false

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

policyID := state.Attributes["claims_mapping_policy_id"]
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 {
log.Printf("[DEBUG] Claims Mapping Policy Assignment was not found")
return &exists, nil
}

exists = true
return &exists, nil
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 944da8c

Please sign in to comment.