Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Resources for Claims Mapping Policy Management #733

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
```
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
```
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,
}
}
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
}
2 changes: 2 additions & 0 deletions internal/services/serviceprincipals/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ 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_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,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,
},

Comment on lines +33 to +38
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The id can be omitted from the schema as the Terraform Plugin SDK adds this implicitly.

"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,
},
},
Comment on lines +39 to +47
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this is a list, does this mean that multiple policies can be specified? If not, can we collapse this into a singular string type. Also, the description for the property should match the description in the documentation (as we'll use the schema description in future to generate the documentation).


"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,
},
computeracer marked this conversation as resolved.
Show resolved Hide resolved
},
}
}

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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use the utils.String() helper to simplify this

Suggested change
DisplayName: &displayName,
DisplayName: utils.String(d.Get("display_name").(string)),

}
policy, _, err := client.Create(ctx, claimsMappingPolicy)
if err != nil {
return tf.ErrorDiagF(err, "Could not create ClaimsMappingPolicy %q", displayName)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to identify the resource in the error message, as Terraform will handle that for us

Suggested change
return tf.ErrorDiagF(err, "Could not create ClaimsMappingPolicy %q", displayName)
return tf.ErrorDiagF(err, "Could not create ClaimsMappingPolicy")

}

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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above

}
_, err := client.Update(ctx, claimsMappingPolicy)
if err != nil {
return tf.ErrorDiagF(err, "Could not update ClaimsMappingPolicy %q", displayName)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we can, we prefer to use the object ID in the error text

Suggested change
return tf.ErrorDiagF(err, "Could not update ClaimsMappingPolicy %q", displayName)
return tf.ErrorDiagF(err, "Could not update ClaimsMappingPolicy with ID: %q", objectId)

}

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
}
Loading