Skip to content

Commit

Permalink
✨ Add Claims Mapping Policy Resource
Browse files Browse the repository at this point in the history
Adds support for the claims mapping policy resource so these can be
managed with Terraform.

Related to:
- manicminer/hamilton#147
- hashicorp#644
- https://docs.microsoft.com/en-us/graph/api/resources/claimsmappingpolicy?view=graph-rest-1.0
  • Loading branch information
computeracer committed Mar 11, 2022
1 parent 6ee8709 commit 6298318
Show file tree
Hide file tree
Showing 5 changed files with 325 additions and 0 deletions.
72 changes: 72 additions & 0 deletions docs/resources/claims_mapping_policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
subcategory: "Policies"
---

# Resource: claims_mapping_policy

Manages a Claims Mapping Policy 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" "test" {
definition = [
jsonencode(
{
ClaimsMappingPolicy = {
ClaimsSchema = [
{
ID = "employeeid"
JwtClaimType = "name"
SamlClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"
Source = "user"
},
{
ID = "tenantcountry"
JwtClaimType = "country"
SamlClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/country"
Source = "company"
}
]
IncludeBasicClaimSet = "true"
Version = 1
}
}
),
]
description = "hcl-created-policy"
display_name = "hcl-create-policy"
}
```

## Argument Reference

The following arguments are supported:

* `definition` - (Required) The claims mapping policy. This is a JSON formatted
string, for which the [`jsonencode()` function](https://www.terraform.io/language/functions/jsonencode)
can be used.
* `description` - (Required) The description for this Claims Mapping Policy.
* `display_name` - (Required) The friendly name for this Claims Mapping Policy.

## Attributes Reference

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

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

## Import

Claims Mapping Policy can be imported using the `id`, e.g.

```shell
terraform import azuread_claims_mapping_policy.id 00000000-0000-0000-0000-000000000000
```
5 changes: 5 additions & 0 deletions internal/services/serviceprincipals/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
)

type Client struct {
ClaimsMappingPolicyClient *msgraph.ClaimsMappingPolicyClient
DelegatedPermissionGrantsClient *msgraph.DelegatedPermissionGrantsClient
DirectoryObjectsClient *msgraph.DirectoryObjectsClient
ServicePrincipalsClient *msgraph.ServicePrincipalsClient
Expand All @@ -22,9 +23,13 @@ func NewClient(o *common.ClientOptions) *Client {
servicePrincipalsClient := msgraph.NewServicePrincipalsClient(o.TenantID)
o.ConfigureClient(&servicePrincipalsClient.BaseClient)

claimsMappingPolicyClient := msgraph.NewClaimsMappingPolicyClient(o.TenantID)
o.ConfigureClient(&claimsMappingPolicyClient.BaseClient)

return &Client{
DelegatedPermissionGrantsClient: delegatedPermissionGrantsClient,
DirectoryObjectsClient: directoryObjectsClient,
ServicePrincipalsClient: servicePrincipalsClient,
ClaimsMappingPolicyClient: claimsMappingPolicyClient,
}
}
1 change: 1 addition & 0 deletions internal/services/serviceprincipals/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func (r Registration) SupportedDataSources() map[string]*schema.Resource {
// SupportedResources returns the supported Resources supported by this Service
func (r Registration) SupportedResources() map[string]*schema.Resource {
return map[string]*schema.Resource{
"azuread_claims_mapping_policy": servicePrincipalClaimsMappingPolicy(),
"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,162 @@
package serviceprincipals

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

"github.com/hashicorp/go-uuid"
"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/tf"
"github.com/manicminer/hamilton/msgraph"
"github.com/manicminer/hamilton/odata"
)

func servicePrincipalClaimsMappingPolicy() *schema.Resource {
return &schema.Resource{
CreateContext: servicePrincipalClaimsMappingPolicyResourceCreate,
ReadContext: servicePrincipalClaimsMappingPolicyResourceRead,
UpdateContext: servicePrincipalClaimsMappingPolicyResourceUpdate,
DeleteContext: servicePrincipalClaimsMappingPolicyResourceDelete,

Importer: tf.ValidateResourceIDPriorToImport(func(id string) error {
if _, err := uuid.ParseUUID(id); err != nil {
return fmt.Errorf("specified ID (%q) is not valid: %s", id, err)
}
return nil
}),

Schema: map[string]*schema.Schema{
"id": {
Description: "Unique identifier for this policy",
Type: schema.TypeString,
Computed: true,
},

"definition": {
Description: "A string collection containing a JSON string " +
"that defines the rules and settings for this policy.",
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},

"display_name": {
Description: "Display name for this policy",
Type: schema.TypeString,
Required: true,
},

"description": {
Description: "Description for this policy",
Optional: true,
Type: schema.TypeString,
Required: false,
},
},
}
}

func servicePrincipalClaimsMappingPolicyResourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client).ServicePrincipals.ClaimsMappingPolicyClient
var definitions []string
for _, v := range d.Get("definition").([]interface{}) {
definitions = append(definitions, v.(string))
}

displayName := d.Get("display_name").(string)

claimsMappingPolicy := msgraph.ClaimsMappingPolicy{
Definition: &definitions,
DisplayName: &displayName,
}
policy, _, err := client.Create(ctx, claimsMappingPolicy)
if err != nil {
return tf.ErrorDiagF(err, "Could not create ClaimsMappingPolicy %q", displayName)
}

if policy != nil {
d.SetId(*policy.ID)
}

return servicePrincipalClaimsMappingPolicyResourceRead(ctx, d, meta)
}

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

policy, status, err := client.Get(ctx, objectId, odata.Query{})
if err != nil {
if status == http.StatusNotFound {
log.Printf("[DEBUG] Claims Mapping Policy with Object ID %q was not found - removing from state!", objectId)
d.SetId("")
return nil
}

return tf.ErrorDiagF(err, "retrieving Claims Mapping Policy with object ID: %q", d.Id())
}

tf.Set(d, "id", policy.ID)
tf.Set(d, "definition", policy.Definition)
tf.Set(d, "display_name", policy.DisplayName)

return nil
}

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

var definitions []string
for _, v := range d.Get("definition").([]interface{}) {
definitions = append(definitions, v.(string))
}

displayName := d.Get("display_name").(string)

claimsMappingPolicy := msgraph.ClaimsMappingPolicy{
DirectoryObject: msgraph.DirectoryObject{
ID: &objectId,
},
Definition: &definitions,
DisplayName: &displayName,
}
_, err := client.Update(ctx, claimsMappingPolicy)
if err != nil {
return tf.ErrorDiagF(err, "Could not update ClaimsMappingPolicy %q", displayName)
}

return servicePrincipalClaimsMappingPolicyResourceRead(ctx, d, meta)
}

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

_, status, err := client.Get(ctx, objectId, odata.Query{})
if err != nil {
if status == http.StatusNotFound {
return tf.ErrorDiagPathF(
fmt.Errorf(
"Claims Mapping Policy was not found"),
"id", "Retrieving Claims Mapping Policy with object ID %q",
objectId,
)
}

return tf.ErrorDiagPathF(err, "id", "Retrieving Claims Mapping Policy with object ID %q", objectId)
}

status, err = client.Delete(ctx, objectId)
if err != nil {
return tf.ErrorDiagF(err, "Deleting Claims Mapping Policy with object ID %q, received status %d", objectId, status)
}

return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package serviceprincipals_test

import (
"context"
"fmt"
"net/http"
"regexp"
"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/odata"
)

type ServicePrincipalClaimsMappingPolicy struct{}

func TestClaimsMappingPolicy_basic(t *testing.T) {
data := acceptance.BuildTestData(t, "azuread_claims_mapping_policy", "test")
r := ServicePrincipalClaimsMappingPolicy{}
updatedRegex, _ := regexp.Compile(`updated`)

data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.basicClaimsMappingPolicy(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
{
Config: r.updateClaimsMappingPolicy(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("display_name").MatchesRegex(updatedRegex),
),
},
})
}

func (ServicePrincipalClaimsMappingPolicy) basicClaimsMappingPolicy(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azuread" {}
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"
}
`, data.RandomString)
}

func (ServicePrincipalClaimsMappingPolicy) updateClaimsMappingPolicy(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azuread" {}
resource "azuread_claims_mapping_policy" "test" {
definition = [
"{\"ClaimsMappingPolicy\":{\"Version\":1,\"IncludeBasicClaimSet\":\"true\",\"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 updated"
display_name = "integration-%[1]s-updated"
}
`, data.RandomString)
}

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

exists := false
_, status, err := client.Get(ctx, state.ID, odata.Query{})
if err != nil {
if status == http.StatusNotFound {
return nil, fmt.Errorf("Claims mapping policy with object ID %q does not exist", state.ID)
}
return &exists, fmt.Errorf("failed to retrieve claims mapping policy with object ID %q: %+v", state.ID, err)
}

exists = true
return &exists, nil
}

0 comments on commit 6298318

Please sign in to comment.