Skip to content

Commit

Permalink
Merge pull request #766 from hashicorp/r/claims-mapping-policy
Browse files Browse the repository at this point in the history
Add Resources for Claims Mapping Policy Management
  • Loading branch information
manicminer authored Apr 8, 2022
2 parents 359be98 + 1c163dd commit f4e0ecf
Show file tree
Hide file tree
Showing 14 changed files with 659 additions and 6 deletions.
1 change: 1 addition & 0 deletions .teamcity/components/project.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ var services = mapOf(
"domains" to "Domains",
"groups" to "Groups",
"invitations" to "Invitations",
"policies" to "Policies",
"serviceprincipals" to "Service Principals",
"users" to "Users"
)
Expand Down
70 changes: 70 additions & 0 deletions docs/resources/claims_mapping_policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
subcategory: "Policies"
---

# Resource: azuread_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" "my_policy" {
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 = "Policy created with Terraform"
display_name = "My Policy"
}
```

## Argument Reference

The following arguments are supported:

* `definition` - (Required) The claims mapping policy. This is a JSON formatted string, for which the [`jsonencode()`](https://www.terraform.io/language/functions/jsonencode) function can be used.
* `description` - (Required) The description for this Claims Mapping Policy.
* `display_name` - (Required) The display 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.my_policy 00000000-0000-0000-0000-000000000000
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

---
subcategory: "Service Principals"
---

# Resource: azuread_service_principal_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_service_principal_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 object 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/claimsMappingPolicy/claims-mapping-policy-uuid`, e.g:

```shell
terraform import azuread_service_principal_claims_mapping_policy_assignment.app 00000000-0000-0000-0000-000000000000/claimsMappingPolicy/11111111-0000-0000-0000-000000000000
```
3 changes: 3 additions & 0 deletions internal/clients/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
domains "github.com/hashicorp/terraform-provider-azuread/internal/services/domains/client"
groups "github.com/hashicorp/terraform-provider-azuread/internal/services/groups/client"
invitations "github.com/hashicorp/terraform-provider-azuread/internal/services/invitations/client"
policies "github.com/hashicorp/terraform-provider-azuread/internal/services/policies/client"
serviceprincipals "github.com/hashicorp/terraform-provider-azuread/internal/services/serviceprincipals/client"
users "github.com/hashicorp/terraform-provider-azuread/internal/services/users/client"
)
Expand All @@ -41,6 +42,7 @@ type Client struct {
Domains *domains.Client
Groups *groups.Client
Invitations *invitations.Client
Policies *policies.Client
ServicePrincipals *serviceprincipals.Client
Users *users.Client
}
Expand All @@ -56,6 +58,7 @@ func (client *Client) build(ctx context.Context, o *common.ClientOptions) error
client.DirectoryRoles = directoryroles.NewClient(o)
client.Groups = groups.NewClient(o)
client.Invitations = invitations.NewClient(o)
client.Policies = policies.NewClient(o)
client.ServicePrincipals = serviceprincipals.NewClient(o)
client.Users = users.NewClient(o)

Expand Down
2 changes: 2 additions & 0 deletions internal/provider/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/hashicorp/terraform-provider-azuread/internal/services/domains"
"github.com/hashicorp/terraform-provider-azuread/internal/services/groups"
"github.com/hashicorp/terraform-provider-azuread/internal/services/invitations"
"github.com/hashicorp/terraform-provider-azuread/internal/services/policies"
"github.com/hashicorp/terraform-provider-azuread/internal/services/serviceprincipals"
"github.com/hashicorp/terraform-provider-azuread/internal/services/users"
)
Expand All @@ -23,6 +24,7 @@ func SupportedServices() []ServiceRegistration {
domains.Registration{},
groups.Registration{},
invitations.Registration{},
policies.Registration{},
serviceprincipals.Registration{},
users.Registration{},
}
Expand Down
132 changes: 132 additions & 0 deletions internal/services/policies/claims_mapping_policy_resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package policies

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/hashicorp/terraform-provider-azuread/internal/utils"
"github.com/manicminer/hamilton/msgraph"
"github.com/manicminer/hamilton/odata"
)

func claimsMappingPolicyResource() *schema.Resource {
return &schema.Resource{
CreateContext: claimsMappingPolicyResourceCreate,
ReadContext: claimsMappingPolicyResourceRead,
UpdateContext: claimsMappingPolicyResourceUpdate,
DeleteContext: claimsMappingPolicyResourceDelete,

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{
"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,
},
},
}
}

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

claimsMappingPolicy := msgraph.ClaimsMappingPolicy{
Definition: tf.ExpandStringSlicePtr(d.Get("definition").([]interface{})),
DisplayName: utils.String(d.Get("display_name").(string)),
}
policy, _, err := client.Create(ctx, claimsMappingPolicy)
if err != nil {
return tf.ErrorDiagF(err, "Could not create Claims Mapping Policy")
}

if policy.ID == nil || *policy.ID == "" {
return tf.ErrorDiagF(fmt.Errorf("Object ID returned for Claims Mapping Policy is nil"), "Bad API response")
}

d.SetId(*policy.ID)

return claimsMappingPolicyResourceRead(ctx, d, meta)
}

func claimsMappingPolicyResourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client).Policies.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, "definition", policy.Definition)
tf.Set(d, "display_name", policy.DisplayName)

return nil
}

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

claimsMappingPolicy := msgraph.ClaimsMappingPolicy{
DirectoryObject: msgraph.DirectoryObject{
ID: &objectId,
},
Definition: tf.ExpandStringSlicePtr(d.Get("definition").([]interface{})),
DisplayName: utils.String(d.Get("display_name").(string)),
}
_, err := client.Update(ctx, claimsMappingPolicy)
if err != nil {
return tf.ErrorDiagF(err, "Could not update Claims Mapping Policy with object ID %q", objectId)
}

return claimsMappingPolicyResourceRead(ctx, d, meta)
}

func claimsMappingPolicyResourceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client).Policies.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
}
82 changes: 82 additions & 0 deletions internal/services/policies/claims_mapping_policy_resource_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package policies_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/manicminer/hamilton/odata"
)

type ClaimsMappingPolicyResource struct{}

func TestClaimsMappingPolicy_basic(t *testing.T) {
data := acceptance.BuildTestData(t, "azuread_claims_mapping_policy", "test")
r := ClaimsMappingPolicyResource{}

data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.basic(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.update(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func (ClaimsMappingPolicyResource) basic(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\"}]}}"
]
display_name = "acctest-%[1]s"
}
`, data.RandomString)
}

func (ClaimsMappingPolicyResource) update(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\"}]}}"
]
display_name = "acctest-%[1]s-updated"
}
`, data.RandomString)
}

func (r ClaimsMappingPolicyResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) {
client := clients.Policies.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
}
Loading

0 comments on commit f4e0ecf

Please sign in to comment.