diff --git a/.teamcity/components/project.kt b/.teamcity/components/project.kt index 09d5bba34..6922affc2 100644 --- a/.teamcity/components/project.kt +++ b/.teamcity/components/project.kt @@ -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" ) diff --git a/docs/resources/claims_mapping_policy.md b/docs/resources/claims_mapping_policy.md new file mode 100644 index 000000000..d8cfe7625 --- /dev/null +++ b/docs/resources/claims_mapping_policy.md @@ -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 +``` diff --git a/docs/resources/service_principal_claims_mapping_policy_assignment.md b/docs/resources/service_principal_claims_mapping_policy_assignment.md new file mode 100644 index 000000000..8c0275195 --- /dev/null +++ b/docs/resources/service_principal_claims_mapping_policy_assignment.md @@ -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 +``` diff --git a/internal/clients/client.go b/internal/clients/client.go index 9536be02d..4cb695ad2 100644 --- a/internal/clients/client.go +++ b/internal/clients/client.go @@ -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" ) @@ -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 } @@ -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) diff --git a/internal/provider/services.go b/internal/provider/services.go index 9555e6c04..15b51894f 100644 --- a/internal/provider/services.go +++ b/internal/provider/services.go @@ -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" ) @@ -23,6 +24,7 @@ func SupportedServices() []ServiceRegistration { domains.Registration{}, groups.Registration{}, invitations.Registration{}, + policies.Registration{}, serviceprincipals.Registration{}, users.Registration{}, } diff --git a/internal/services/policies/claims_mapping_policy_resource.go b/internal/services/policies/claims_mapping_policy_resource.go new file mode 100644 index 000000000..0a8a81a56 --- /dev/null +++ b/internal/services/policies/claims_mapping_policy_resource.go @@ -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 +} diff --git a/internal/services/policies/claims_mapping_policy_resource_test.go b/internal/services/policies/claims_mapping_policy_resource_test.go new file mode 100644 index 000000000..8f2d03f88 --- /dev/null +++ b/internal/services/policies/claims_mapping_policy_resource_test.go @@ -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 +} diff --git a/internal/services/policies/client/client.go b/internal/services/policies/client/client.go new file mode 100644 index 000000000..82e2bf951 --- /dev/null +++ b/internal/services/policies/client/client.go @@ -0,0 +1,19 @@ +package client + +import ( + "github.com/hashicorp/terraform-provider-azuread/internal/common" + "github.com/manicminer/hamilton/msgraph" +) + +type Client struct { + ClaimsMappingPolicyClient *msgraph.ClaimsMappingPolicyClient +} + +func NewClient(o *common.ClientOptions) *Client { + claimsMappingPolicyClient := msgraph.NewClaimsMappingPolicyClient(o.TenantID) + o.ConfigureClient(&claimsMappingPolicyClient.BaseClient) + + return &Client{ + ClaimsMappingPolicyClient: claimsMappingPolicyClient, + } +} diff --git a/internal/services/policies/registration.go b/internal/services/policies/registration.go new file mode 100644 index 000000000..9ccfe7bbe --- /dev/null +++ b/internal/services/policies/registration.go @@ -0,0 +1,31 @@ +package policies + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +type Registration struct{} + +// Name is the name of this Service +func (r Registration) Name() string { + return "Policies" +} + +// WebsiteCategories returns a list of categories which can be used for the sidebar +func (r Registration) WebsiteCategories() []string { + return []string{ + "Policies", + } +} + +// SupportedDataSources returns the supported Data Sources supported by this Service +func (r Registration) SupportedDataSources() map[string]*schema.Resource { + return 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": claimsMappingPolicyResource(), + } +} diff --git a/internal/services/serviceprincipals/client/client.go b/internal/services/serviceprincipals/client/client.go index f423eb523..2b105e53b 100644 --- a/internal/services/serviceprincipals/client/client.go +++ b/internal/services/serviceprincipals/client/client.go @@ -1,9 +1,8 @@ package client import ( - "github.com/manicminer/hamilton/msgraph" - "github.com/hashicorp/terraform-provider-azuread/internal/common" + "github.com/manicminer/hamilton/msgraph" ) type Client struct { diff --git a/internal/services/serviceprincipals/parse/claims_mapping_policy_assignment.go b/internal/services/serviceprincipals/parse/claims_mapping_policy_assignment.go new file mode 100644 index 000000000..7ab86bdfa --- /dev/null +++ b/internal/services/serviceprincipals/parse/claims_mapping_policy_assignment.go @@ -0,0 +1,30 @@ +package parse + +import "fmt" + +type ClaimsMappingPolicyAssignmentId struct { + ObjectSubResourceId + ServicePrincipalId string + ClaimsMappingPolicyId string +} + +func NewClaimsMappingPolicyAssignmentID(ServicePolicyId, ClaimsMappingPolicyId string) ClaimsMappingPolicyAssignmentId { + return ClaimsMappingPolicyAssignmentId{ + ObjectSubResourceId: NewObjectSubResourceID(ServicePolicyId, "claimsMappingPolicy", ClaimsMappingPolicyId), + ServicePrincipalId: ServicePolicyId, + ClaimsMappingPolicyId: ClaimsMappingPolicyId, + } +} + +func ClaimsMappingPolicyAssignmentID(idString string) (*ClaimsMappingPolicyAssignmentId, error) { + id, err := ObjectSubResourceID(idString, "claimsMappingPolicy") + if err != nil { + return nil, fmt.Errorf("unable to parse Claims Mapping Policy Assignment ID: %v", err) + } + + return &ClaimsMappingPolicyAssignmentId{ + ObjectSubResourceId: *id, + ServicePrincipalId: id.objectId, + ClaimsMappingPolicyId: id.subId, + }, nil +} diff --git a/internal/services/serviceprincipals/registration.go b/internal/services/serviceprincipals/registration.go index 9d46e2cbb..aab9eba0c 100644 --- a/internal/services/serviceprincipals/registration.go +++ b/internal/services/serviceprincipals/registration.go @@ -30,9 +30,10 @@ 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_service_principal": servicePrincipalResource(), - "azuread_service_principal_certificate": servicePrincipalCertificateResource(), - "azuread_service_principal_delegated_permission_grant": servicePrincipalDelegatedPermissionGrantResource(), - "azuread_service_principal_password": servicePrincipalPasswordResource(), + "azuread_service_principal": servicePrincipalResource(), + "azuread_service_principal_certificate": servicePrincipalCertificateResource(), + "azuread_service_principal_claims_mapping_policy_assignment": servicePrincipalClaimsMappingPolicyAssignmentResource(), + "azuread_service_principal_delegated_permission_grant": servicePrincipalDelegatedPermissionGrantResource(), + "azuread_service_principal_password": servicePrincipalPasswordResource(), } } diff --git a/internal/services/serviceprincipals/service_principal_claims_mapping_policy_assignment_resource.go b/internal/services/serviceprincipals/service_principal_claims_mapping_policy_assignment_resource.go new file mode 100644 index 000000000..8a356145b --- /dev/null +++ b/internal/services/serviceprincipals/service_principal_claims_mapping_policy_assignment_resource.go @@ -0,0 +1,154 @@ +package serviceprincipals + +import ( + "context" + "fmt" + "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 servicePrincipalClaimsMappingPolicyAssignmentResource() *schema.Resource { + return &schema.Resource{ + CreateContext: servicePrincipalClaimsMappingPolicyAssignmentResourceCreate, + ReadContext: servicePrincipalClaimsMappingPolicyAssignmentResourceRead, + DeleteContext: servicePrincipalClaimsMappingPolicyAssignmentResourceDelete, + + Importer: tf.ValidateResourceIDPriorToImport(func(id string) error { + _, err := parse.ObjectSubResourceID(id, "claimsMappingPolicy") + return err + }), + + Schema: map[string]*schema.Schema{ + "claims_mapping_policy_id": { + Description: "ID of the claims mapping policy to assign", + Type: schema.TypeString, + ForceNew: true, + Required: true, + }, + + "service_principal_id": { + Description: "Object ID of the service principal for which to assign the policy", + Type: schema.TypeString, + ForceNew: true, + Required: true, + }, + }, + } +} + +func servicePrincipalClaimsMappingPolicyAssignmentResourceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*clients.Client).ServicePrincipals.ServicePrincipalsClient + + policyId := d.Get("claims_mapping_policy_id").(string) + + properties := msgraph.ServicePrincipal{ + DirectoryObject: msgraph.DirectoryObject{ + ID: utils.String(d.Get("service_principal_id").(string)), + }, + ClaimsMappingPolicies: &[]msgraph.ClaimsMappingPolicy{ + { + DirectoryObject: msgraph.DirectoryObject{ + ODataId: (*odata.Id)(utils.String(fmt.Sprintf("%s/v1.0/%s/directoryObjects/%s", + client.BaseClient.Endpoint, client.BaseClient.TenantId, policyId))), + ID: &policyId, + }, + }, + }, + } + + _, err := client.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, + ) + } + + id := parse.NewClaimsMappingPolicyAssignmentID( + *properties.DirectoryObject.ID, + *(*properties.ClaimsMappingPolicies)[0].DirectoryObject.ID, + ) + + d.SetId(id.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.ServicePrincipalId + + 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.ServicePrincipalId + + 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) +} diff --git a/internal/services/serviceprincipals/service_principal_claims_mapping_policy_assignment_resource_test.go b/internal/services/serviceprincipals/service_principal_claims_mapping_policy_assignment_resource_test.go new file mode 100644 index 000000000..a60d1fa81 --- /dev/null +++ b/internal/services/serviceprincipals/service_principal_claims_mapping_policy_assignment_resource_test.go @@ -0,0 +1,83 @@ +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/services/serviceprincipals/parse" + "github.com/hashicorp/terraform-provider-azuread/internal/utils" +) + +type ServicePrincipalClaimsMappingPolicyAssignmentResource struct{} + +func TestClaimsMappingPolicyAssignment_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azuread_service_principal_claims_mapping_policy_assignment", "test") + r := ServicePrincipalClaimsMappingPolicyAssignmentResource{} + + data.ResourceTest(t, r, []resource.TestStep{ + { + Config: r.basicClaimsMappingPolicyAssignment(data), + Check: resource.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (ServicePrincipalClaimsMappingPolicyAssignmentResource) basicClaimsMappingPolicyAssignment(data acceptance.TestData) string { + return fmt.Sprintf(` +data "azuread_application_published_app_ids" "well_known" {} + +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" +} + +resource "azuread_service_principal" "msgraph" { + application_id = data.azuread_application_published_app_ids.well_known.result.MicrosoftGraph + use_existing = true +} + +resource "azuread_service_principal_claims_mapping_policy_assignment" "test" { + claims_mapping_policy_id = azuread_claims_mapping_policy.test.id + service_principal_id = azuread_service_principal.msgraph.id +} +`, data.RandomString) +} + +func (r ServicePrincipalClaimsMappingPolicyAssignmentResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { + client := clients.ServicePrincipals.ServicePrincipalsClient + client.BaseClient.DisableRetries = true + + id, err := parse.ClaimsMappingPolicyAssignmentID(state.ID) + if err != nil { + return nil, fmt.Errorf("parsing CLaims Mapping Policy Assignment ID: %v", err) + } + + policyList, status, err := client.ListClaimsMappingPolicy(ctx, id.ServicePrincipalId) + if err != nil { + if status == http.StatusNotFound { + return utils.Bool(false), fmt.Errorf("Service Policy with object ID %q does not exist", id.ServicePrincipalId) + } + return utils.Bool(false), fmt.Errorf("failed to retrieve claims mapping policy assignments with service policy ID %q: %+v", id.ServicePrincipalId, err) + } + + // Check the assignment is found in the currently assigned policies + for _, policy := range *policyList { + if policy.ID != nil && *policy.ID == id.ClaimsMappingPolicyId { + return utils.Bool(true), nil + } + } + + return utils.Bool(false), nil +}