From 0e3b0ccce42f5e6afe05457b28a470c455331293 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Thu, 29 Feb 2024 18:25:40 +1300 Subject: [PATCH 01/55] Enable typed resources for identitygovernance --- internal/provider/services.go | 1 + .../services/identitygovernance/registration.go | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/internal/provider/services.go b/internal/provider/services.go index 20c524da44..8ff1076bea 100644 --- a/internal/provider/services.go +++ b/internal/provider/services.go @@ -29,6 +29,7 @@ func SupportedTypedServices() []sdk.TypedServiceRegistration { applications.Registration{}, directoryroles.Registration{}, domains.Registration{}, + identitygovernance.Registration{}, serviceprincipals.Registration{}, } } diff --git a/internal/services/identitygovernance/registration.go b/internal/services/identitygovernance/registration.go index 81a3e1b8ac..ee2d7e9fac 100644 --- a/internal/services/identitygovernance/registration.go +++ b/internal/services/identitygovernance/registration.go @@ -3,7 +3,10 @@ package identitygovernance -import "github.com/hashicorp/terraform-provider-azuread/internal/tf/pluginsdk" +import ( + "github.com/hashicorp/terraform-provider-azuread/internal/sdk" + "github.com/hashicorp/terraform-provider-azuread/internal/tf/pluginsdk" +) type Registration struct{} @@ -44,3 +47,15 @@ func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { "azuread_access_package_resource_package_association": accessPackageResourcePackageAssociationResource(), } } + +// DataSources returns the typed DataSources supported by this service +func (r Registration) DataSources() []sdk.DataSource { + return []sdk.DataSource{} +} + +// Resources returns the typed Resources supported by this service +func (r Registration) Resources() []sdk.Resource { + return []sdk.Resource{ + PrivilegedAccessGroupAssignmentScheduleRequestResource{}, + } +} From b14959b1ead833e731f2ab34b920bd76063793cc Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Thu, 29 Feb 2024 18:26:36 +1300 Subject: [PATCH 02/55] Create `azuread_privileged_access_group_assignment_schedule_request` --- ...ccess_group_assignment_schedule_request.md | 70 ++++ .../identitygovernance/client/client.go | 37 +- ...ccess_group_assignment_schedule_request.go | 35 ++ ...ccess_group_assignment_schedule_request.go | 326 ++++++++++++++++++ 4 files changed, 452 insertions(+), 16 deletions(-) create mode 100644 docs/resources/privileged_access_group_assignment_schedule_request.md create mode 100644 internal/services/identitygovernance/parse/privileged_access_group_assignment_schedule_request.go create mode 100644 internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go diff --git a/docs/resources/privileged_access_group_assignment_schedule_request.md b/docs/resources/privileged_access_group_assignment_schedule_request.md new file mode 100644 index 0000000000..1fe65bca93 --- /dev/null +++ b/docs/resources/privileged_access_group_assignment_schedule_request.md @@ -0,0 +1,70 @@ +--- +subcategory: "Identity Governance" +--- + +# Resource: azuread_privileged_access_group_assignment_schedule_request + +Manages an active assignment to a privileged access group. + +## API Permissions + +The following API permissions are required in order to use this resource. + +When authenticated with a service principal, this resource requires the `PrivilegedAssignmentSchedule.ReadWrite.AzureADGroup` Microsoft Graph API permissions. + +When authenticated with a user principal, this resource requires `Global Administrator` directory role, or the `Privileged Role Administrator` role in Identity Governance. + +## Example Usage + +```terraform +resource "azuread_group" "example" { + display_name = "group-name" + security_enabled = true +} + +resource "azuread_user" "member" { + user_principal_name = "jdoe@hashicorp.com" + display_name = "J. Doe" + mail_nickname = "jdoe" + password = "SecretP@sswd99!" +} + +resource "azuread_privileged_access_group_assignment_schedule_request" "example" { + group_id = azuread_group.pim.id + principal_id = azuread_user.member.id + assignment_type = "member" + duration = "P30D" + justification = "as requested" +} +``` + +## Argument Reference + +- `group_id` (Required) The Object ID of the Azure AD group to which the principal will be assigned. +- `principal_id` (Required) The Object ID of the principal to be assigned to the above group. Can be either a user or a group. +- `assignment_type` (Required) The type of assignment to the group. Can be either `member` or `owner`. +- `justification` (Optional) The justification for this assignment. May be required by the role policy. +- `ticket_number` (Optional) The ticket number in the ticket system approving this assignment. May be required by the role policy. +- `ticket_system` (Optional) The ticket system containing the ticket number approving this assignment. May be required by the role policy. +- `start_date` (Optional) The date from which this assignment is valid, formatted as an RFC3339 date string (e.g. 2018-01-01T01:02:03Z). If not provided, the assignment is immediately valid. +- `expiration_date` (Optional) The date that this assignment expires, formatted as an RFC3339 date string (e.g. 2018-01-01T01:02:03Z). +- `duration` (Optional) The duration that this assignment is valid for, formatted as an ISO8601 duration (e.g. P30D for 30 days, PT3H for three hours). +- `permanent_assignment` (Optional) Is this assigment permanently valid. + +At least one of `expiration_date`, `duration`, or `permanent_assignment` must be supplied. The role policy may limit the maximum duration which can be supplied. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +- `id` (String) The ID of this request. +- `status` (String) The provisioning status of this request. +- `target_schedule_id` (String) The ID of this schedule created by this request. + +## Import + +An assignment schedule can be imported using the ID, e.g. + +```shell +terraform import azuread_privileged_access_group_assignment_schedule_request.example 00000000-0000-0000-0000-000000000000 +``` diff --git a/internal/services/identitygovernance/client/client.go b/internal/services/identitygovernance/client/client.go index 201d2cecf5..f98a39ac2c 100644 --- a/internal/services/identitygovernance/client/client.go +++ b/internal/services/identitygovernance/client/client.go @@ -9,14 +9,15 @@ import ( ) type Client struct { - AccessPackageAssignmentPolicyClient *msgraph.AccessPackageAssignmentPolicyClient - AccessPackageCatalogClient *msgraph.AccessPackageCatalogClient - AccessPackageCatalogRoleAssignmentsClient *msgraph.EntitlementRoleAssignmentsClient - AccessPackageCatalogRoleClient *msgraph.EntitlementRoleDefinitionsClient - AccessPackageClient *msgraph.AccessPackageClient - AccessPackageResourceClient *msgraph.AccessPackageResourceClient - AccessPackageResourceRequestClient *msgraph.AccessPackageResourceRequestClient - AccessPackageResourceRoleScopeClient *msgraph.AccessPackageResourceRoleScopeClient + AccessPackageAssignmentPolicyClient *msgraph.AccessPackageAssignmentPolicyClient + AccessPackageCatalogClient *msgraph.AccessPackageCatalogClient + AccessPackageCatalogRoleAssignmentsClient *msgraph.EntitlementRoleAssignmentsClient + AccessPackageCatalogRoleClient *msgraph.EntitlementRoleDefinitionsClient + AccessPackageClient *msgraph.AccessPackageClient + AccessPackageResourceClient *msgraph.AccessPackageResourceClient + AccessPackageResourceRequestClient *msgraph.AccessPackageResourceRequestClient + AccessPackageResourceRoleScopeClient *msgraph.AccessPackageResourceRoleScopeClient + PrivilegedAccessGroupAssignmentScheduleRequestsClient *msgraph.PrivilegedAccessGroupAssignmentScheduleRequestsClient } func NewClient(o *common.ClientOptions) *Client { @@ -54,14 +55,18 @@ func NewClient(o *common.ClientOptions) *Client { o.ConfigureClient(&accessPackageResourceRoleScopeClient.BaseClient) accessPackageResourceRoleScopeClient.BaseClient.ApiVersion = msgraph.VersionBeta + privilegedAccessGroupAssignmentScheduleRequestsClient := msgraph.NewPrivilegedAccessGroupAssignmentScheduleRequestsClient() + o.ConfigureClient(&privilegedAccessGroupAssignmentScheduleRequestsClient.BaseClient) + return &Client{ - AccessPackageAssignmentPolicyClient: accessPackageAssignmentPolicyClient, - AccessPackageCatalogClient: accessPackageCatalogClient, - AccessPackageCatalogRoleAssignmentsClient: accessPackageCatalogRoleAssignmentsClient, - AccessPackageCatalogRoleClient: accessPackageCatalogRoleClient, - AccessPackageClient: accessPackageClient, - AccessPackageResourceClient: accessPackageResourceClient, - AccessPackageResourceRequestClient: accessPackageResourceRequestClient, - AccessPackageResourceRoleScopeClient: accessPackageResourceRoleScopeClient, + AccessPackageAssignmentPolicyClient: accessPackageAssignmentPolicyClient, + AccessPackageCatalogClient: accessPackageCatalogClient, + AccessPackageCatalogRoleAssignmentsClient: accessPackageCatalogRoleAssignmentsClient, + AccessPackageCatalogRoleClient: accessPackageCatalogRoleClient, + AccessPackageClient: accessPackageClient, + AccessPackageResourceClient: accessPackageResourceClient, + AccessPackageResourceRequestClient: accessPackageResourceRequestClient, + AccessPackageResourceRoleScopeClient: accessPackageResourceRoleScopeClient, + PrivilegedAccessGroupAssignmentScheduleRequestsClient: privilegedAccessGroupAssignmentScheduleRequestsClient, } } diff --git a/internal/services/identitygovernance/parse/privileged_access_group_assignment_schedule_request.go b/internal/services/identitygovernance/parse/privileged_access_group_assignment_schedule_request.go new file mode 100644 index 0000000000..584bd5828d --- /dev/null +++ b/internal/services/identitygovernance/parse/privileged_access_group_assignment_schedule_request.go @@ -0,0 +1,35 @@ +package parse + +import ( + "fmt" + + "github.com/hashicorp/terraform-provider-azuread/internal/tf/validation" +) + +type PrivilegedAccessGroupAssignmentScheduleRequestId struct { + RequestId string +} + +func NewPrivilegedAccessGroupAssignmentScheduleRequestID(requestId string) *PrivilegedAccessGroupAssignmentScheduleRequestId { + return &PrivilegedAccessGroupAssignmentScheduleRequestId{ + RequestId: requestId, + } +} + +func ParsePrivilegedAccessGroupAssignmentScheduleRequestID(idString string) (*PrivilegedAccessGroupAssignmentScheduleRequestId, error) { + if _, err := validation.IsUUID(idString, "RequestId"); len(err) > 0 { + return nil, fmt.Errorf("parsing RequestId: %+v", err) + } + + return &PrivilegedAccessGroupAssignmentScheduleRequestId{ + RequestId: idString, + }, nil +} + +func (id *PrivilegedAccessGroupAssignmentScheduleRequestId) ID() string { + return id.RequestId +} + +func (id *PrivilegedAccessGroupAssignmentScheduleRequestId) String() string { + return fmt.Sprintf("Privileged Access Group Assigment Schedule Request ID: %q", id.RequestId) +} diff --git a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go new file mode 100644 index 0000000000..cfd5083760 --- /dev/null +++ b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go @@ -0,0 +1,326 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package identitygovernance + +import ( + "context" + "fmt" + "net/http" + "time" + + "github.com/hashicorp/terraform-provider-azuread/internal/sdk" + "github.com/hashicorp/terraform-provider-azuread/internal/services/identitygovernance/parse" + "github.com/hashicorp/terraform-provider-azuread/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azuread/internal/tf/validation" + "github.com/manicminer/hamilton/msgraph" +) + +type PrivilegedAccessGroupAssignmentScheduleRequestModel struct { + AssignmentType string `tfschema:"assignment_type"` + Duration string `tfschema:"duration"` + ExpirationDate string `tfschema:"expiration_date"` + GroupId string `tfschema:"group_id"` + Justification string `tfschema:"justification"` + PermanentAssignment bool `tfschema:"permanent_assignment"` + PrincipalId string `tfschema:"principal_id"` + StartDate string `tfschema:"start_date"` + Status string `tfschema:"status"` + TargetScheduleId string `tfschema:"target_schedule_id"` + TicketNumber string `tfschema:"ticket_number"` + TicketSystem string `tfschema:"ticket_system"` +} + +type PrivilegedAccessGroupAssignmentScheduleRequestResource struct{} + +func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return validation.IsUUID +} + +var _ sdk.Resource = PrivilegedAccessGroupAssignmentScheduleRequestResource{} + +func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) ResourceType() string { + return "azuread_privileged_access_group_assignment_schedule_request" +} + +func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) ModelObject() interface{} { + return &PrivilegedAccessGroupAssignmentScheduleRequestModel{} +} + +func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "group_id": { + Description: "The ID of the Group representing the scope of the assignment", + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + }, + + "principal_id": { + Description: "The ID of the Principal assigned to the schedule", + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + }, + + "assignment_type": { + Description: "The ID of the assignment to the group", + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + msgraph.PrivilegedAccessGroupRelationshipMember, + msgraph.PrivilegedAccessGroupRelationshipOwner, + msgraph.PrivilegedAccessGroupRelationshipUnknown, + }, false), + }, + + "start_date": { + Description: "The date that this assignment starts, formatted as an RFC3339 date string in UTC (e.g. 2018-01-01T01:02:03Z)", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + ValidateFunc: validation.IsRFC3339Time, + }, + + "expiration_date": { + Description: "The date that this assignment expires, formatted as an RFC3339 date string in UTC (e.g. 2018-01-01T01:02:03Z)", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"duration", "permanent_assignment"}, + ValidateFunc: validation.IsRFC3339Time, + }, + + "duration": { + Description: "The duration of the assignment, formatted as an ISO8601 duration string (e.g. P3D for 3 days)", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"expiration_date", "permanent_assignment"}, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "permanent_assignment": { + Description: "Is the assignment permanent", + Type: pluginsdk.TypeBool, + Optional: true, + ForceNew: true, + Computed: true, + ConflictsWith: []string{"expiration_date", "duration"}, + }, + + "justification": { + Description: "The justification for the assignment", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "ticket_number": { + Description: "The ticket number authorising the assignment", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + RequiredWith: []string{"ticket_system"}, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "ticket_system": { + Description: "The ticket system authorising the assignment", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + RequiredWith: []string{"ticket_number"}, + ValidateFunc: validation.StringIsNotEmpty, + }, + } +} + +func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "status": { + Description: "The status of the Schedule Request", + Type: pluginsdk.TypeString, + Computed: true, + }, + + "target_schedule_id": { + Description: "The ID of the Schedule targeted by the request", + Type: pluginsdk.TypeString, + Computed: true, + }, + } +} + +func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Create() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.IdentityGovernance.PrivilegedAccessGroupAssignmentScheduleRequestsClient + + var model PrivilegedAccessGroupAssignmentScheduleRequestModel + if err := metadata.Decode(&model); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + schedule := msgraph.RequestSchedule{} + schedule.Expiration = &msgraph.ExpirationPattern{} + + if model.ExpirationDate != "" || model.Duration != "" { + if model.Duration != "" { + schedule.Expiration.Duration = &model.Duration + schedule.Expiration.Type = msgraph.ExpirationPatternTypeAfterDuration + } + + if model.ExpirationDate != "" { + endDate, err := time.Parse(time.RFC3339, model.ExpirationDate) + if err != nil { + return fmt.Errorf("parsing %s: %+v", model.StartDate, err) + } + schedule.Expiration.EndDateTime = &endDate + schedule.Expiration.Type = msgraph.ExpirationPatternTypeAfterDateTime + } + } else if model.PermanentAssignment { + schedule.Expiration.Type = msgraph.ExpirationPatternTypeNoExpiration + } else { + schedule.Expiration.Type = msgraph.ExpirationPatternTypeNotSpecified + } + + if model.StartDate != "" { + startDate, err := time.Parse(time.RFC3339, model.StartDate) + if err != nil { + return fmt.Errorf("parsing %s: %+v", model.StartDate, err) + } + schedule.StartDateTime = &startDate + } else { + now := time.Now() + schedule.StartDateTime = &now + } + + properties := msgraph.PrivilegedAccessGroupAssignmentScheduleRequest{ + AccessId: model.AssignmentType, + PrincipalId: &model.PrincipalId, + GroupId: &model.GroupId, + Action: msgraph.PrivilegedAccessGroupActionAdminAssign, + Justification: &model.Justification, + ScheduleInfo: &schedule, + TicketInfo: &msgraph.TicketInfo{ + TicketNumber: &model.TicketNumber, + TicketSystem: &model.TicketSystem, + }, + } + + req, _, err := client.Create(ctx, properties) + if err != nil { + return fmt.Errorf("Could not create assignment schedule request, %+v", err) + } + + if req.ID == nil || *req.ID == "" { + return fmt.Errorf("ID returned for assignment schedule request is nil/empty") + } + + id := parse.NewPrivilegedAccessGroupAssignmentScheduleRequestID(*req.ID) + metadata.SetID(id) + + return nil + }, + } +} + +func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.IdentityGovernance.PrivilegedAccessGroupAssignmentScheduleRequestsClient + + id, err := parse.ParsePrivilegedAccessGroupAssignmentScheduleRequestID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + var model PrivilegedAccessGroupAssignmentScheduleRequestModel + if err := metadata.Decode(&model); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + result, status, err := client.Get(ctx, id.ID()) + if err != nil { + if status == http.StatusNotFound { + return metadata.MarkAsGone(id) + } + return fmt.Errorf("retrieving %s: %+v", id, err) + } + if result == nil { + return fmt.Errorf("retrieving %s: API error, result was nil", id) + } + + model.AssignmentType = result.AccessId + model.GroupId = *result.GroupId + model.Justification = *result.Justification + model.PermanentAssignment = result.ScheduleInfo.Expiration.Type == msgraph.ExpirationPatternTypeNoExpiration + model.PrincipalId = *result.PrincipalId + model.StartDate = result.ScheduleInfo.StartDateTime.Format(time.RFC3339) + model.Status = result.Status + model.TargetScheduleId = *result.TargetScheduleId + + if result.ScheduleInfo.Expiration.EndDateTime != nil { + model.ExpirationDate = result.ScheduleInfo.Expiration.EndDateTime.Format(time.RFC3339) + } + if result.ScheduleInfo.Expiration.Duration != nil { + model.Duration = *result.ScheduleInfo.Expiration.Duration + } + + if result.TicketInfo.TicketNumber != nil { + model.TicketNumber = *result.TicketInfo.TicketNumber + } + if result.TicketInfo.TicketSystem != nil { + model.TicketSystem = *result.TicketInfo.TicketSystem + } + + return metadata.Encode(&model) + }, + } +} + +func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Delete() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.IdentityGovernance.PrivilegedAccessGroupAssignmentScheduleRequestsClient + + id, err := parse.ParsePrivilegedAccessGroupAssignmentScheduleRequestID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + var model PrivilegedAccessGroupAssignmentScheduleRequestModel + if err := metadata.Decode(&model); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + result, status, err := client.Create(ctx, msgraph.PrivilegedAccessGroupAssignmentScheduleRequest{ + ID: &id.RequestId, + AccessId: model.AssignmentType, + PrincipalId: &model.PrincipalId, + GroupId: &model.GroupId, + Action: msgraph.PrivilegedAccessGroupActionAdminRemove, + }) + if err != nil { + if status == http.StatusNotFound { + return metadata.MarkAsGone(id) + } + return fmt.Errorf("retrieving %s: %+v", id, err) + } + if result == nil { + return fmt.Errorf("retrieving %s: API error, result was nil", id) + } + + return nil + }, + } +} From 003e26d40187bf409183a3e3d2f0320e698dfc38 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Fri, 1 Mar 2024 16:21:46 +1300 Subject: [PATCH 03/55] Handle deletion of requests in all states --- ...ccess_group_assignment_schedule_request.go | 79 +++++++++++++++---- 1 file changed, 63 insertions(+), 16 deletions(-) diff --git a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go index cfd5083760..26076c65b1 100644 --- a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go +++ b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "net/http" + "slices" "time" "github.com/hashicorp/terraform-provider-azuread/internal/sdk" @@ -259,6 +260,13 @@ func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Read() sdk.Resou return fmt.Errorf("retrieving %s: API error, result was nil", id) } + if slices.Contains([]string{ + msgraph.PrivilegedAccessGroupAssignmentStatusCanceled, + msgraph.PrivilegedAccessGroupAssignmentStatusRevoked, + }, result.Status) { + metadata.MarkAsGone(id) + } + model.AssignmentType = result.AccessId model.GroupId = *result.GroupId model.Justification = *result.Justification @@ -303,24 +311,63 @@ func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Delete() sdk.Res return fmt.Errorf("decoding: %+v", err) } - result, status, err := client.Create(ctx, msgraph.PrivilegedAccessGroupAssignmentScheduleRequest{ - ID: &id.RequestId, - AccessId: model.AssignmentType, - PrincipalId: &model.PrincipalId, - GroupId: &model.GroupId, - Action: msgraph.PrivilegedAccessGroupActionAdminRemove, - }) - if err != nil { - if status == http.StatusNotFound { - return metadata.MarkAsGone(id) - } - return fmt.Errorf("retrieving %s: %+v", id, err) - } - if result == nil { - return fmt.Errorf("retrieving %s: API error, result was nil", id) + switch model.Status { + case msgraph.PrivilegedAccessGroupAssignmentStatusDenied: + return cancelRequest(ctx, metadata, client, id) + case msgraph.PrivilegedAccessGroupAssignmentStatusFailed: + return cancelRequest(ctx, metadata, client, id) + case msgraph.PrivilegedAccessGroupAssignmentStatusGranted: + return cancelRequest(ctx, metadata, client, id) + case msgraph.PrivilegedAccessGroupAssignmentStatusPendingAdminDecision: + return cancelRequest(ctx, metadata, client, id) + case msgraph.PrivilegedAccessGroupAssignmentStatusPendingApproval: + return cancelRequest(ctx, metadata, client, id) + case msgraph.PrivilegedAccessGroupAssignmentStatusPendingProvisioning: + return cancelRequest(ctx, metadata, client, id) + case msgraph.PrivilegedAccessGroupAssignmentStatusPendingScheduledCreation: + return cancelRequest(ctx, metadata, client, id) + case msgraph.PrivilegedAccessGroupAssignmentStatusProvisioned: + return revokeRequest(ctx, metadata, client, id, &model) + case msgraph.PrivilegedAccessGroupAssignmentStatusScheduleCreated: + return revokeRequest(ctx, metadata, client, id, &model) + case msgraph.PrivilegedAccessGroupAssignmentStatusCanceled: + return metadata.MarkAsGone(id) + case msgraph.PrivilegedAccessGroupAssignmentStatusRevoked: + return metadata.MarkAsGone(id) } - return nil + return fmt.Errorf("unknown status: %s", model.Status) }, } } + +func cancelRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupAssignmentScheduleRequestsClient, id *parse.PrivilegedAccessGroupAssignmentScheduleRequestId) error { + status, err := client.Cancel(ctx, id.RequestId) + if err != nil { + if status == http.StatusNotFound { + return metadata.MarkAsGone(id) + } + return fmt.Errorf("cancelling %s: %+v", id, err) + } + return metadata.MarkAsGone(id) +} + +func revokeRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupAssignmentScheduleRequestsClient, id *parse.PrivilegedAccessGroupAssignmentScheduleRequestId, model *PrivilegedAccessGroupAssignmentScheduleRequestModel) error { + result, status, err := client.Create(ctx, msgraph.PrivilegedAccessGroupAssignmentScheduleRequest{ + ID: &id.RequestId, + AccessId: model.AssignmentType, + PrincipalId: &model.PrincipalId, + GroupId: &model.GroupId, + Action: msgraph.PrivilegedAccessGroupActionAdminRemove, + }) + if err != nil { + if status == http.StatusNotFound { + return metadata.MarkAsGone(id) + } + return fmt.Errorf("retrieving %s: %+v", id, err) + } + if result == nil { + return fmt.Errorf("retrieving %s: API error, result was nil", id) + } + return metadata.MarkAsGone(id) +} From cccc742c5cf3817ea5115b7b666975f571a0af56 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Mon, 4 Mar 2024 09:41:54 +1300 Subject: [PATCH 04/55] Initial test creation --- ..._group_assignment_schedule_request_test.go | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 internal/services/identitygovernance/privileged_access_group_assignment_schedule_request_test.go diff --git a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request_test.go b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request_test.go new file mode 100644 index 0000000000..029d9a7e80 --- /dev/null +++ b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request_test.go @@ -0,0 +1,165 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package identitygovernance_test + +import ( + "context" + "fmt" + "net/http" + "slices" + "testing" + "time" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/terraform-plugin-testing/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 PrivilegedAccessGroupAssignmentScheduleRequestResource struct{} + +func TestPrivilegedAccessGroupAssignmentScheduleRequest_member(t *testing.T) { + data := acceptance.BuildTestData(t, "azuread_privileged_access_group_assignment_schedule_request", "member") + r := PrivilegedAccessGroupAssignmentScheduleRequestResource{} + + endTime := time.Now().AddDate(0, 2, 0).UTC() + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.member(data, endTime), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + // There is a minimum life of 5 minutes for a schedule request to exist. + // Attempting to delete the request within this time frame will result in + // a 400 error on destroy, which we can't trap. + sleepCheck(5*time.Minute+1*time.Second), + ), + }, + }) +} + +func TestPrivilegedAccessGroupAssignmentScheduleRequest_owner(t *testing.T) { + data := acceptance.BuildTestData(t, "azuread_privileged_access_group_assignment_schedule_request", "owner") + r := PrivilegedAccessGroupAssignmentScheduleRequestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.owner(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + // There is a minimum life of 5 minutes for a schedule request to exist. + // Attempting to delete the request within this time frame will result in + // a 400 error on destroy, which we can't trap. + sleepCheck(5*time.Minute+1*time.Second), + ), + }, + }) + +} + +func (PrivilegedAccessGroupAssignmentScheduleRequestResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { + client := clients.IdentityGovernance.PrivilegedAccessGroupAssignmentScheduleRequestsClient + client.BaseClient.DisableRetries = true + defer func() { client.BaseClient.DisableRetries = false }() + + request, status, err := client.Get(ctx, state.ID) + if err != nil { + if status == http.StatusNotFound { + return pointer.To(false), nil + } + return nil, fmt.Errorf("failed to retrieve privileged group assignment schedule request with ID %q: %+v", state.ID, err) + } + + // Requests are not deleted, but marked as canceled or revoked. + if slices.Contains([]string{ + msgraph.PrivilegedAccessGroupAssignmentStatusCanceled, + msgraph.PrivilegedAccessGroupAssignmentStatusRevoked, + }, request.Status) { + return pointer.To(false), nil + } else { + return pointer.To(true), nil + } +} + +func (PrivilegedAccessGroupAssignmentScheduleRequestResource) member(data acceptance.TestData, endTime time.Time) string { + return fmt.Sprintf(` +provider "azuread" {} + +resource "azuread_group" "pam" { + display_name = "Privileged %[1]s" + mail_enabled = false + security_enabled = true +} + +data "azuread_domains" "test" { + only_initial = true +} + +resource "azuread_user" "member" { + user_principal_name = "pam-member-%[1]s@${data.azuread_domains.test.domains.0.domain_name}" + display_name = "PAM Member %[1]s" + password = "%[2]s" +} + +resource "azuread_privileged_access_group_assignment_schedule_request" "member" { + group_id = azuread_group.pam.id + principal_id = azuread_user.member.id + assignment_type = "member" + expiration_date = "%[3]s" + justification = "required" +} +`, data.RandomString, data.RandomPassword, endTime.Format(time.RFC3339)) +} + +func (PrivilegedAccessGroupAssignmentScheduleRequestResource) owner(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azuread" {} + +data "azuread_domains" "test" { + only_initial = true +} + +resource "azuread_user" "manual_owner" { + user_principal_name = "pam-owner-manual-%[1]s@${data.azuread_domains.test.domains.0.domain_name}" + display_name = "PAM Owner (Manual) %[1]s" + password = "%[2]s" +} + +resource "azuread_group" "pam" { + display_name = "Privileged %[1]s" + mail_enabled = false + security_enabled = true + + owners = [azuread_user.manual_owner.object_id] + + lifecycle { + ignore_changes = [owners] + } +} + +resource "azuread_user" "assigned_owner" { + user_principal_name = "pam-owner-assigned-%[1]s@${data.azuread_domains.test.domains.0.domain_name}" + display_name = "PAM Owner (Assigned) %[1]s" + password = "%[2]s" +} + + +resource "azuread_privileged_access_group_assignment_schedule_request" "owner" { + group_id = azuread_group.pam.id + principal_id = azuread_user.assigned_owner.id + assignment_type = "owner" + duration = "P30D" + justification = "required" +} +`, data.RandomString, data.RandomPassword) +} + +func sleepCheck(d time.Duration) acceptance.TestCheckFunc { + return func(s *terraform.State) error { + time.Sleep(d) + return nil + } +} From f72d4eecbf854eb74136f4d514663bb1a38349ce Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Mon, 4 Mar 2024 11:13:38 +1300 Subject: [PATCH 05/55] Handle case where the request comes back failed from Entra --- .../privileged_access_group_assignment_schedule_request.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go index 26076c65b1..324ce925cb 100644 --- a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go +++ b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go @@ -225,6 +225,10 @@ func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Create() sdk.Res return fmt.Errorf("ID returned for assignment schedule request is nil/empty") } + if req.Status == msgraph.PrivilegedAccessGroupAssignmentStatusFailed { + return fmt.Errorf("Assignment schedule request is in a failed state") + } + id := parse.NewPrivilegedAccessGroupAssignmentScheduleRequestID(*req.ID) metadata.SetID(id) From 11408d5cb6ade50764bcd16161dcb016cc005f43 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Mon, 4 Mar 2024 11:47:03 +1300 Subject: [PATCH 06/55] Create azuread_privileged_access_group_eligibility_schedule_request --- ...cess_group_eligibility_schedule_request.md | 70 ++++ .../identitygovernance/client/client.go | 37 +- ...cess_group_eligibility_schedule_request.go | 35 ++ ...ccess_group_eligiblity_schedule_request.go | 377 ++++++++++++++++++ .../identitygovernance/registration.go | 1 + 5 files changed, 504 insertions(+), 16 deletions(-) create mode 100644 docs/resources/privileged_access_group_eligibility_schedule_request.md create mode 100644 internal/services/identitygovernance/parse/privileged_access_group_eligibility_schedule_request.go create mode 100644 internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go diff --git a/docs/resources/privileged_access_group_eligibility_schedule_request.md b/docs/resources/privileged_access_group_eligibility_schedule_request.md new file mode 100644 index 0000000000..336fb08187 --- /dev/null +++ b/docs/resources/privileged_access_group_eligibility_schedule_request.md @@ -0,0 +1,70 @@ +--- +subcategory: "Identity Governance" +--- + +# Resource: azuread_privileged_access_group_eligibility_schedule_request + +Manages an eligible assignment to a privileged access group. + +## API Permissions + +The following API permissions are required in order to use this resource. + +When authenticated with a service principal, this resource requires the `PrivilegedEligibilitySchedule.ReadWrite.AzureADGroup` Microsoft Graph API permissions. + +When authenticated with a user principal, this resource requires `Global Administrator` directory role, or the `Privileged Role Administrator` role in Identity Governance. + +## Example Usage + +```terraform +resource "azuread_group" "example" { + display_name = "group-name" + security_enabled = true +} + +resource "azuread_user" "member" { + user_principal_name = "jdoe@hashicorp.com" + display_name = "J. Doe" + mail_nickname = "jdoe" + password = "SecretP@sswd99!" +} + +resource "azuread_privileged_access_group_eligibility_schedule_request" "example" { + group_id = azuread_group.pim.id + principal_id = azuread_user.member.id + assignment_type = "member" + duration = "P30D" + justification = "as requested" +} +``` + +## Argument Reference + +- `group_id` (Required) The Object ID of the Azure AD group to which the principal will be assigned. +- `principal_id` (Required) The Object ID of the principal to be assigned to the above group. Can be either a user or a group. +- `assignment_type` (Required) The type of assignment to the group. Can be either `member` or `owner`. +- `justification` (Optional) The justification for this assignment. May be required by the role policy. +- `ticket_number` (Optional) The ticket number in the ticket system approving this assignment. May be required by the role policy. +- `ticket_system` (Optional) The ticket system containing the ticket number approving this assignment. May be required by the role policy. +- `start_date` (Optional) The date from which this assignment is valid, formatted as an RFC3339 date string (e.g. 2018-01-01T01:02:03Z). If not provided, the assignment is immediately valid. +- `expiration_date` (Optional) The date that this assignment expires, formatted as an RFC3339 date string (e.g. 2018-01-01T01:02:03Z). +- `duration` (Optional) The duration that this assignment is valid for, formatted as an ISO8601 duration (e.g. P30D for 30 days, PT3H for three hours). +- `permanent_assignment` (Optional) Is this assigment permanently valid. + +At least one of `expiration_date`, `duration`, or `permanent_assignment` must be supplied. The role policy may limit the maximum duration which can be supplied. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +- `id` (String) The ID of this request. +- `status` (String) The provisioning status of this request. +- `target_schedule_id` (String) The ID of this schedule created by this request. + +## Import + +An assignment schedule can be imported using the ID, e.g. + +```shell +terraform import azuread_privileged_access_group_eligibility_schedule_request.example 00000000-0000-0000-0000-000000000000 +``` diff --git a/internal/services/identitygovernance/client/client.go b/internal/services/identitygovernance/client/client.go index 201d2cecf5..c806c46b7c 100644 --- a/internal/services/identitygovernance/client/client.go +++ b/internal/services/identitygovernance/client/client.go @@ -9,14 +9,15 @@ import ( ) type Client struct { - AccessPackageAssignmentPolicyClient *msgraph.AccessPackageAssignmentPolicyClient - AccessPackageCatalogClient *msgraph.AccessPackageCatalogClient - AccessPackageCatalogRoleAssignmentsClient *msgraph.EntitlementRoleAssignmentsClient - AccessPackageCatalogRoleClient *msgraph.EntitlementRoleDefinitionsClient - AccessPackageClient *msgraph.AccessPackageClient - AccessPackageResourceClient *msgraph.AccessPackageResourceClient - AccessPackageResourceRequestClient *msgraph.AccessPackageResourceRequestClient - AccessPackageResourceRoleScopeClient *msgraph.AccessPackageResourceRoleScopeClient + AccessPackageAssignmentPolicyClient *msgraph.AccessPackageAssignmentPolicyClient + AccessPackageCatalogClient *msgraph.AccessPackageCatalogClient + AccessPackageCatalogRoleAssignmentsClient *msgraph.EntitlementRoleAssignmentsClient + AccessPackageCatalogRoleClient *msgraph.EntitlementRoleDefinitionsClient + AccessPackageClient *msgraph.AccessPackageClient + AccessPackageResourceClient *msgraph.AccessPackageResourceClient + AccessPackageResourceRequestClient *msgraph.AccessPackageResourceRequestClient + AccessPackageResourceRoleScopeClient *msgraph.AccessPackageResourceRoleScopeClient + PrivilegedAccessGroupEligibilityScheduleRequestClient *msgraph.PrivilegedAccessGroupEligibilityScheduleRequestClient } func NewClient(o *common.ClientOptions) *Client { @@ -54,14 +55,18 @@ func NewClient(o *common.ClientOptions) *Client { o.ConfigureClient(&accessPackageResourceRoleScopeClient.BaseClient) accessPackageResourceRoleScopeClient.BaseClient.ApiVersion = msgraph.VersionBeta + privilegedAccessGroupEligibilityScheduleRequestsClient := msgraph.NewPrivilegedAccessGroupEligibilityScheduleRequestClient() + o.ConfigureClient(&privilegedAccessGroupEligibilityScheduleRequestsClient.BaseClient) + return &Client{ - AccessPackageAssignmentPolicyClient: accessPackageAssignmentPolicyClient, - AccessPackageCatalogClient: accessPackageCatalogClient, - AccessPackageCatalogRoleAssignmentsClient: accessPackageCatalogRoleAssignmentsClient, - AccessPackageCatalogRoleClient: accessPackageCatalogRoleClient, - AccessPackageClient: accessPackageClient, - AccessPackageResourceClient: accessPackageResourceClient, - AccessPackageResourceRequestClient: accessPackageResourceRequestClient, - AccessPackageResourceRoleScopeClient: accessPackageResourceRoleScopeClient, + AccessPackageAssignmentPolicyClient: accessPackageAssignmentPolicyClient, + AccessPackageCatalogClient: accessPackageCatalogClient, + AccessPackageCatalogRoleAssignmentsClient: accessPackageCatalogRoleAssignmentsClient, + AccessPackageCatalogRoleClient: accessPackageCatalogRoleClient, + AccessPackageClient: accessPackageClient, + AccessPackageResourceClient: accessPackageResourceClient, + AccessPackageResourceRequestClient: accessPackageResourceRequestClient, + AccessPackageResourceRoleScopeClient: accessPackageResourceRoleScopeClient, + PrivilegedAccessGroupEligibilityScheduleRequestClient: privilegedAccessGroupEligibilityScheduleRequestsClient, } } diff --git a/internal/services/identitygovernance/parse/privileged_access_group_eligibility_schedule_request.go b/internal/services/identitygovernance/parse/privileged_access_group_eligibility_schedule_request.go new file mode 100644 index 0000000000..65845dacfd --- /dev/null +++ b/internal/services/identitygovernance/parse/privileged_access_group_eligibility_schedule_request.go @@ -0,0 +1,35 @@ +package parse + +import ( + "fmt" + + "github.com/hashicorp/terraform-provider-azuread/internal/tf/validation" +) + +type PrivilegedAccessGroupEligibilityScheduleRequestId struct { + RequestId string +} + +func NewPrivilegedAccessGroupEligibilityScheduleRequestID(requestId string) *PrivilegedAccessGroupEligibilityScheduleRequestId { + return &PrivilegedAccessGroupEligibilityScheduleRequestId{ + RequestId: requestId, + } +} + +func ParsePrivilegedAccessGroupEligibilityScheduleRequestID(idString string) (*PrivilegedAccessGroupEligibilityScheduleRequestId, error) { + if _, err := validation.IsUUID(idString, "RequestId"); len(err) > 0 { + return nil, fmt.Errorf("parsing RequestId: %+v", err) + } + + return &PrivilegedAccessGroupEligibilityScheduleRequestId{ + RequestId: idString, + }, nil +} + +func (id *PrivilegedAccessGroupEligibilityScheduleRequestId) ID() string { + return id.RequestId +} + +func (id *PrivilegedAccessGroupEligibilityScheduleRequestId) String() string { + return fmt.Sprintf("Privileged Access Group Assigment Schedule Request ID: %q", id.RequestId) +} diff --git a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go new file mode 100644 index 0000000000..c928899e25 --- /dev/null +++ b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go @@ -0,0 +1,377 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package identitygovernance + +import ( + "context" + "fmt" + "net/http" + "slices" + "time" + + "github.com/hashicorp/terraform-provider-azuread/internal/sdk" + "github.com/hashicorp/terraform-provider-azuread/internal/services/identitygovernance/parse" + "github.com/hashicorp/terraform-provider-azuread/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azuread/internal/tf/validation" + "github.com/manicminer/hamilton/msgraph" +) + +type PrivilegedAccessGroupEligibilityScheduleRequestModel struct { + AssignmentType string `tfschema:"assignment_type"` + Duration string `tfschema:"duration"` + ExpirationDate string `tfschema:"expiration_date"` + GroupId string `tfschema:"group_id"` + Justification string `tfschema:"justification"` + PermanentAssignment bool `tfschema:"permanent_assignment"` + PrincipalId string `tfschema:"principal_id"` + StartDate string `tfschema:"start_date"` + Status string `tfschema:"status"` + TargetScheduleId string `tfschema:"target_schedule_id"` + TicketNumber string `tfschema:"ticket_number"` + TicketSystem string `tfschema:"ticket_system"` +} + +type PrivilegedAccessGroupEligibilityScheduleRequestResource struct{} + +func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return validation.IsUUID +} + +var _ sdk.Resource = PrivilegedAccessGroupEligibilityScheduleRequestResource{} + +func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) ResourceType() string { + return "azuread_privileged_access_group_eligibility_schedule_request" +} + +func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) ModelObject() interface{} { + return &PrivilegedAccessGroupEligibilityScheduleRequestModel{} +} + +func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "group_id": { + Description: "The ID of the Group representing the scope of the assignment", + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + }, + + "principal_id": { + Description: "The ID of the Principal assigned to the schedule", + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + }, + + "assignment_type": { + Description: "The ID of the assignment to the group", + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + msgraph.PrivilegedAccessGroupRelationshipMember, + msgraph.PrivilegedAccessGroupRelationshipOwner, + msgraph.PrivilegedAccessGroupRelationshipUnknown, + }, false), + }, + + "start_date": { + Description: "The date that this assignment starts, formatted as an RFC3339 date string in UTC (e.g. 2018-01-01T01:02:03Z)", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + ValidateFunc: validation.IsRFC3339Time, + }, + + "expiration_date": { + Description: "The date that this assignment expires, formatted as an RFC3339 date string in UTC (e.g. 2018-01-01T01:02:03Z)", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"duration", "permanent_assignment"}, + ValidateFunc: validation.IsRFC3339Time, + }, + + "duration": { + Description: "The duration of the assignment, formatted as an ISO8601 duration string (e.g. P3D for 3 days)", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"expiration_date", "permanent_assignment"}, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "permanent_assignment": { + Description: "Is the assignment permanent", + Type: pluginsdk.TypeBool, + Optional: true, + ForceNew: true, + Computed: true, + ConflictsWith: []string{"expiration_date", "duration"}, + }, + + "justification": { + Description: "The justification for the assignment", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "ticket_number": { + Description: "The ticket number authorising the assignment", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + RequiredWith: []string{"ticket_system"}, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "ticket_system": { + Description: "The ticket system authorising the assignment", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + RequiredWith: []string{"ticket_number"}, + ValidateFunc: validation.StringIsNotEmpty, + }, + } +} + +func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "status": { + Description: "The status of the Schedule Request", + Type: pluginsdk.TypeString, + Computed: true, + }, + + "target_schedule_id": { + Description: "The ID of the Schedule targeted by the request", + Type: pluginsdk.TypeString, + Computed: true, + }, + } +} + +func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Create() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.IdentityGovernance.PrivilegedAccessGroupEligibilityScheduleRequestClient + + var model PrivilegedAccessGroupEligibilityScheduleRequestModel + if err := metadata.Decode(&model); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + schedule := msgraph.RequestSchedule{} + schedule.Expiration = &msgraph.ExpirationPattern{} + + if model.ExpirationDate != "" || model.Duration != "" { + if model.Duration != "" { + schedule.Expiration.Duration = &model.Duration + schedule.Expiration.Type = msgraph.ExpirationPatternTypeAfterDuration + } + + if model.ExpirationDate != "" { + endDate, err := time.Parse(time.RFC3339, model.ExpirationDate) + if err != nil { + return fmt.Errorf("parsing %s: %+v", model.StartDate, err) + } + schedule.Expiration.EndDateTime = &endDate + schedule.Expiration.Type = msgraph.ExpirationPatternTypeAfterDateTime + } + } else if model.PermanentAssignment { + schedule.Expiration.Type = msgraph.ExpirationPatternTypeNoExpiration + } else { + schedule.Expiration.Type = msgraph.ExpirationPatternTypeNotSpecified + } + + if model.StartDate != "" { + startDate, err := time.Parse(time.RFC3339, model.StartDate) + if err != nil { + return fmt.Errorf("parsing %s: %+v", model.StartDate, err) + } + schedule.StartDateTime = &startDate + } else { + now := time.Now() + schedule.StartDateTime = &now + } + + properties := msgraph.PrivilegedAccessGroupEligibilityScheduleRequest{ + AccessId: model.AssignmentType, + PrincipalId: &model.PrincipalId, + GroupId: &model.GroupId, + Action: msgraph.PrivilegedAccessGroupActionAdminAssign, + Justification: &model.Justification, + ScheduleInfo: &schedule, + TicketInfo: &msgraph.TicketInfo{ + TicketNumber: &model.TicketNumber, + TicketSystem: &model.TicketSystem, + }, + } + + req, _, err := client.Create(ctx, properties) + if err != nil { + return fmt.Errorf("Could not create assignment schedule request, %+v", err) + } + + if req.ID == nil || *req.ID == "" { + return fmt.Errorf("ID returned for assignment schedule request is nil/empty") + } + + if req.Status == msgraph.PrivilegedAccessGroupEligibilityStatusFailed { + return fmt.Errorf("Assignment schedule request is in a failed state") + } + + id := parse.NewPrivilegedAccessGroupEligibilityScheduleRequestID(*req.ID) + metadata.SetID(id) + + return nil + }, + } +} + +func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.IdentityGovernance.PrivilegedAccessGroupEligibilityScheduleRequestClient + + id, err := parse.ParsePrivilegedAccessGroupEligibilityScheduleRequestID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + var model PrivilegedAccessGroupEligibilityScheduleRequestModel + if err := metadata.Decode(&model); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + result, status, err := client.Get(ctx, id.ID()) + if err != nil { + if status == http.StatusNotFound { + return metadata.MarkAsGone(id) + } + return fmt.Errorf("retrieving %s: %+v", id, err) + } + if result == nil { + return fmt.Errorf("retrieving %s: API error, result was nil", id) + } + + if slices.Contains([]string{ + msgraph.PrivilegedAccessGroupEligibilityStatusCanceled, + msgraph.PrivilegedAccessGroupEligibilityStatusRevoked, + }, result.Status) { + metadata.MarkAsGone(id) + } + + model.AssignmentType = result.AccessId + model.GroupId = *result.GroupId + model.Justification = *result.Justification + model.PermanentAssignment = result.ScheduleInfo.Expiration.Type == msgraph.ExpirationPatternTypeNoExpiration + model.PrincipalId = *result.PrincipalId + model.StartDate = result.ScheduleInfo.StartDateTime.Format(time.RFC3339) + model.Status = result.Status + model.TargetScheduleId = *result.TargetScheduleId + + if result.ScheduleInfo.Expiration.EndDateTime != nil { + model.ExpirationDate = result.ScheduleInfo.Expiration.EndDateTime.Format(time.RFC3339) + } + if result.ScheduleInfo.Expiration.Duration != nil { + model.Duration = *result.ScheduleInfo.Expiration.Duration + } + + if result.TicketInfo.TicketNumber != nil { + model.TicketNumber = *result.TicketInfo.TicketNumber + } + if result.TicketInfo.TicketSystem != nil { + model.TicketSystem = *result.TicketInfo.TicketSystem + } + + return metadata.Encode(&model) + }, + } +} + +func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Delete() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.IdentityGovernance.PrivilegedAccessGroupEligibilityScheduleRequestClient + + id, err := parse.ParsePrivilegedAccessGroupEligibilityScheduleRequestID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + var model PrivilegedAccessGroupEligibilityScheduleRequestModel + if err := metadata.Decode(&model); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + switch model.Status { + case msgraph.PrivilegedAccessGroupEligibilityStatusDenied: + return cancelRequest(ctx, metadata, client, id) + case msgraph.PrivilegedAccessGroupEligibilityStatusFailed: + return cancelRequest(ctx, metadata, client, id) + case msgraph.PrivilegedAccessGroupEligibilityStatusGranted: + return cancelRequest(ctx, metadata, client, id) + case msgraph.PrivilegedAccessGroupEligibilityStatusPendingAdminDecision: + return cancelRequest(ctx, metadata, client, id) + case msgraph.PrivilegedAccessGroupEligibilityStatusPendingApproval: + return cancelRequest(ctx, metadata, client, id) + case msgraph.PrivilegedAccessGroupEligibilityStatusPendingProvisioning: + return cancelRequest(ctx, metadata, client, id) + case msgraph.PrivilegedAccessGroupEligibilityStatusPendingScheduledCreation: + return cancelRequest(ctx, metadata, client, id) + case msgraph.PrivilegedAccessGroupEligibilityStatusProvisioned: + return revokeRequest(ctx, metadata, client, id, &model) + case msgraph.PrivilegedAccessGroupEligibilityStatusScheduleCreated: + return revokeRequest(ctx, metadata, client, id, &model) + case msgraph.PrivilegedAccessGroupEligibilityStatusCanceled: + return metadata.MarkAsGone(id) + case msgraph.PrivilegedAccessGroupEligibilityStatusRevoked: + return metadata.MarkAsGone(id) + } + + return fmt.Errorf("unknown status: %s", model.Status) + }, + } +} + +func cancelRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupEligibilityScheduleRequestClient, id *parse.PrivilegedAccessGroupEligibilityScheduleRequestId) error { + status, err := client.Cancel(ctx, id.RequestId) + if err != nil { + if status == http.StatusNotFound { + return metadata.MarkAsGone(id) + } + return fmt.Errorf("cancelling %s: %+v", id, err) + } + return metadata.MarkAsGone(id) +} + +func revokeRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupEligibilityScheduleRequestClient, id *parse.PrivilegedAccessGroupEligibilityScheduleRequestId, model *PrivilegedAccessGroupEligibilityScheduleRequestModel) error { + result, status, err := client.Create(ctx, msgraph.PrivilegedAccessGroupEligibilityScheduleRequest{ + ID: &id.RequestId, + AccessId: model.AssignmentType, + PrincipalId: &model.PrincipalId, + GroupId: &model.GroupId, + Action: msgraph.PrivilegedAccessGroupActionAdminRemove, + }) + if err != nil { + if status == http.StatusNotFound { + return metadata.MarkAsGone(id) + } + return fmt.Errorf("retrieving %s: %+v", id, err) + } + if result == nil { + return fmt.Errorf("retrieving %s: API error, result was nil", id) + } + return metadata.MarkAsGone(id) +} diff --git a/internal/services/identitygovernance/registration.go b/internal/services/identitygovernance/registration.go index ee2d7e9fac..5e63598d66 100644 --- a/internal/services/identitygovernance/registration.go +++ b/internal/services/identitygovernance/registration.go @@ -57,5 +57,6 @@ func (r Registration) DataSources() []sdk.DataSource { func (r Registration) Resources() []sdk.Resource { return []sdk.Resource{ PrivilegedAccessGroupAssignmentScheduleRequestResource{}, + PrivilegedAccessGroupEligibilityScheduleRequestResource{}, } } From b27b94e40e94d36a1ee4e6c243b5a3d01c43a2e5 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Mon, 4 Mar 2024 11:47:38 +1300 Subject: [PATCH 07/55] Create tests for azuread_privileged_access_group_eligibility_schedule_request --- ..._group_eligiblity_schedule_request_test.go | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request_test.go diff --git a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request_test.go b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request_test.go new file mode 100644 index 0000000000..6f68bf8ceb --- /dev/null +++ b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request_test.go @@ -0,0 +1,165 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package identitygovernance_test + +import ( + "context" + "fmt" + "net/http" + "slices" + "testing" + "time" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/terraform-plugin-testing/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 PrivilegedAccessGroupEligiblityScheduleRequestResource struct{} + +func TestPrivilegedAccessGroupEligiblityScheduleRequest_member(t *testing.T) { + data := acceptance.BuildTestData(t, "azuread_privileged_access_group_eligibility_schedule_request", "member") + r := PrivilegedAccessGroupEligiblityScheduleRequestResource{} + + endTime := time.Now().AddDate(0, 2, 0).UTC() + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.member(data, endTime), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + // There is a minimum life of 5 minutes for a schedule request to exist. + // Attempting to delete the request within this time frame will result in + // a 400 error on destroy, which we can't trap. + sleepCheck(5*time.Minute+1*time.Second), + ), + }, + }) +} + +func TestPrivilegedAccessGroupEligiblityScheduleRequest_owner(t *testing.T) { + data := acceptance.BuildTestData(t, "azuread_privileged_access_group_eligibility_schedule_request", "owner") + r := PrivilegedAccessGroupEligiblityScheduleRequestResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.owner(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + // There is a minimum life of 5 minutes for a schedule request to exist. + // Attempting to delete the request within this time frame will result in + // a 400 error on destroy, which we can't trap. + sleepCheck(5*time.Minute+1*time.Second), + ), + }, + }) + +} + +func (PrivilegedAccessGroupEligiblityScheduleRequestResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { + client := clients.IdentityGovernance.PrivilegedAccessGroupEligibilityScheduleRequestClient + client.BaseClient.DisableRetries = true + defer func() { client.BaseClient.DisableRetries = false }() + + request, status, err := client.Get(ctx, state.ID) + if err != nil { + if status == http.StatusNotFound { + return pointer.To(false), nil + } + return nil, fmt.Errorf("failed to retrieve privileged group assignment schedule request with ID %q: %+v", state.ID, err) + } + + // Requests are not deleted, but marked as canceled or revoked. + if slices.Contains([]string{ + msgraph.PrivilegedAccessGroupEligibilityStatusCanceled, + msgraph.PrivilegedAccessGroupEligibilityStatusRevoked, + }, request.Status) { + return pointer.To(false), nil + } else { + return pointer.To(true), nil + } +} + +func (PrivilegedAccessGroupEligiblityScheduleRequestResource) member(data acceptance.TestData, endTime time.Time) string { + return fmt.Sprintf(` +provider "azuread" {} + +resource "azuread_group" "pam" { + display_name = "Privileged Eligibility %[1]s" + mail_enabled = false + security_enabled = true +} + +data "azuread_domains" "test" { + only_initial = true +} + +resource "azuread_user" "member" { + user_principal_name = "pam-member-%[1]s@${data.azuread_domains.test.domains.0.domain_name}" + display_name = "PAM Member %[1]s" + password = "%[2]s" +} + +resource "azuread_privileged_access_group_eligibility_schedule_request" "member" { + group_id = azuread_group.pam.id + principal_id = azuread_user.member.id + assignment_type = "member" + expiration_date = "%[3]s" + justification = "required" +} +`, data.RandomString, data.RandomPassword, endTime.Format(time.RFC3339)) +} + +func (PrivilegedAccessGroupEligiblityScheduleRequestResource) owner(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azuread" {} + +data "azuread_domains" "test" { + only_initial = true +} + +resource "azuread_user" "manual_owner" { + user_principal_name = "pam-eligible-owner-manual-%[1]s@${data.azuread_domains.test.domains.0.domain_name}" + display_name = "PAM Owner (Manual) %[1]s" + password = "%[2]s" +} + +resource "azuread_group" "pam" { + display_name = "Privileged Eligibility %[1]s" + mail_enabled = false + security_enabled = true + + owners = [azuread_user.manual_owner.object_id] + + lifecycle { + ignore_changes = [owners] + } +} + +resource "azuread_user" "eligibile_owner" { + user_principal_name = "pam-eligible-owner-eligibile-%[1]s@${data.azuread_domains.test.domains.0.domain_name}" + display_name = "PAM Owner (Eligibile) %[1]s" + password = "%[2]s" +} + + +resource "azuread_privileged_access_group_eligibility_schedule_request" "owner" { + group_id = azuread_group.pam.id + principal_id = azuread_user.eligibile_owner.id + assignment_type = "owner" + duration = "P30D" + justification = "required" +} +`, data.RandomString, data.RandomPassword) +} + +func sleepCheck(d time.Duration) acceptance.TestCheckFunc { + return func(s *terraform.State) error { + time.Sleep(d) + return nil + } +} From 236a7b07f55834097c3245b042defadfd636140d Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Mon, 4 Mar 2024 13:46:06 +1300 Subject: [PATCH 08/55] Enable typed resources for Policies --- internal/provider/services.go | 1 + internal/services/policies/registration.go | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/internal/provider/services.go b/internal/provider/services.go index 20c524da44..059e4457d5 100644 --- a/internal/provider/services.go +++ b/internal/provider/services.go @@ -29,6 +29,7 @@ func SupportedTypedServices() []sdk.TypedServiceRegistration { applications.Registration{}, directoryroles.Registration{}, domains.Registration{}, + policies.Registration{}, serviceprincipals.Registration{}, } } diff --git a/internal/services/policies/registration.go b/internal/services/policies/registration.go index 5d95f81304..3da055ffbd 100644 --- a/internal/services/policies/registration.go +++ b/internal/services/policies/registration.go @@ -3,7 +3,10 @@ package policies -import "github.com/hashicorp/terraform-provider-azuread/internal/tf/pluginsdk" +import ( + "github.com/hashicorp/terraform-provider-azuread/internal/sdk" + "github.com/hashicorp/terraform-provider-azuread/internal/tf/pluginsdk" +) type Registration struct{} @@ -36,3 +39,15 @@ func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { "azuread_claims_mapping_policy": claimsMappingPolicyResource(), } } + +// DataSources returns the typed DataSources supported by this service +func (r Registration) DataSources() []sdk.DataSource { + return []sdk.DataSource{ + } +} + +// Resources returns the typed Resources supported by this service +func (r Registration) Resources() []sdk.Resource { + return []sdk.Resource{ + } +} From f6ac3610fba15f36a2c04bec1b69c63d539f1e83 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Mon, 4 Mar 2024 20:24:43 +1300 Subject: [PATCH 09/55] Create clients --- internal/services/policies/client/client.go | 15 +++++++++++++++ internal/services/policies/registration.go | 1 + 2 files changed, 16 insertions(+) diff --git a/internal/services/policies/client/client.go b/internal/services/policies/client/client.go index 66d24f537b..f655fab470 100644 --- a/internal/services/policies/client/client.go +++ b/internal/services/policies/client/client.go @@ -11,6 +11,9 @@ import ( type Client struct { AuthenticationStrengthPoliciesClient *msgraph.AuthenticationStrengthPoliciesClient ClaimsMappingPolicyClient *msgraph.ClaimsMappingPolicyClient + RoleManagementPolicyAssignmentClient *msgraph.RoleManagementPolicyAssignmentClient + RoleManagementPolicyClient *msgraph.RoleManagementPolicyClient + RoleManagementPolicyRuleClient *msgraph.RoleManagementPolicyRuleClient } func NewClient(o *common.ClientOptions) *Client { @@ -20,8 +23,20 @@ func NewClient(o *common.ClientOptions) *Client { claimsMappingPolicyClient := msgraph.NewClaimsMappingPolicyClient() o.ConfigureClient(&claimsMappingPolicyClient.BaseClient) + roleManagementPolicyAssignmentClient := msgraph.NewRoleManagementPolicyAssignmentClient() + o.ConfigureClient(&roleManagementPolicyAssignmentClient.BaseClient) + + roleManagementPolicyClient := msgraph.NewRoleManagementPolicyClient() + o.ConfigureClient(&roleManagementPolicyClient.BaseClient) + + roleManagementPolicyRuleClient := msgraph.NewRoleManagementPolicyRuleClient() + o.ConfigureClient(&roleManagementPolicyRuleClient.BaseClient) + return &Client{ AuthenticationStrengthPoliciesClient: authenticationStrengthpoliciesClient, ClaimsMappingPolicyClient: claimsMappingPolicyClient, + RoleManagementPolicyAssignmentClient: roleManagementPolicyAssignmentClient, + RoleManagementPolicyClient: roleManagementPolicyClient, + RoleManagementPolicyRuleClient: roleManagementPolicyRuleClient, } } diff --git a/internal/services/policies/registration.go b/internal/services/policies/registration.go index 3da055ffbd..814c0355ab 100644 --- a/internal/services/policies/registration.go +++ b/internal/services/policies/registration.go @@ -49,5 +49,6 @@ func (r Registration) DataSources() []sdk.DataSource { // Resources returns the typed Resources supported by this service func (r Registration) Resources() []sdk.Resource { return []sdk.Resource{ + RoleManagementPolicyResource{}, } } From dc2facb9ad59c492a14d7c7aa3c9be9f7e275c35 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Mon, 4 Mar 2024 20:28:10 +1300 Subject: [PATCH 10/55] Initial resource for PAM Group policies --- .../group_role_management_policy_resource.go | 642 ++++++++++++++++++ 1 file changed, 642 insertions(+) create mode 100644 internal/services/policies/group_role_management_policy_resource.go diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go new file mode 100644 index 0000000000..7b7da9c6a5 --- /dev/null +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -0,0 +1,642 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package policies + +import ( + "context" + "fmt" + "net/http" + "slices" + "time" + + "github.com/hashicorp/go-azure-sdk/sdk/odata" + "github.com/hashicorp/terraform-provider-azuread/internal/sdk" + "github.com/hashicorp/terraform-provider-azuread/internal/services/policies/parse" + "github.com/hashicorp/terraform-provider-azuread/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azuread/internal/tf/validation" + "github.com/manicminer/hamilton/msgraph" +) + +type RoleManagementPolicyModel struct { + Description string `tfschema:"description"` + DisplayName string `tfschema:"display_name"` + GroupId string `tfschema:"object_id"` + ScopeType msgraph.UnifiedRoleManagementPolicyScope `tfschema:"assignment_type"` + ActiveAssignmentRules AssignmentRules `tfschema:"active_assignment_rules` + EligbleAssignmentRules AssignmentRules `tfschema:"eligible_assignment_rules` + ActivationRules ActivationRules `tfschema:"activation_rules"` + NotificationRules NotificationRules `tfschema:"notification_rules"` +} + +type AssignmentRules struct { + AllowPermanent bool `tfschema:"allow_permanent"` + ExpireAfter string `tfschema:"expire_after"` + ReqireMultiFactorAuth bool `tfschema:"require_multifactor_authentication"` + RequireJustification bool `tfschema:"require_justification"` +} + +type ActivationRules struct { + MaximumDuration string `tfschema:"maximum_duration"` + RequireApproval bool `tfschema:"require_approval"` + ApprovalStages []ApprovalStage `tfschema:"approval_stages"` +} + +type ApprovalStage struct { + PrimaryApprovers []Approver `tfschema:"primary_approvers"` +} + +type Approver struct { + Description string `tfschema:"description"` + UserId string `tfschema:"user_id"` + GroupId string `tfschema:"group_id"` +} + +type NotificationRules struct { + AdminNotifications NotificationRule `tfschema:"admin_notifications"` + ApproverNotifications NotificationRule `tfschema:"approver_notifications"` + AssigneeNotifications NotificationRule `tfschema:"assignee_notifications"` +} + +type NotificationRule struct { + EligibleAssignments NotificationSettings `tfschema:"eligible_assignments"` + ActiveAssignments NotificationSettings `tfschema:"active_assignments"` + Activations NotificationSettings `tfschema:"activations"` +} + +type NotificationSettings struct { + NotificationLevel string `tfschema:"notification_level"` + DefaultRecipients bool `tfschema:"default_recipients"` + AdditionalRecipients []string `tfschema:"additional_recipients"` +} + +type RoleManagementPolicyResource struct{} + +func (r RoleManagementPolicyResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return validation.IsUUID +} + +var _ sdk.Resource = RoleManagementPolicyResource{} + +func (r RoleManagementPolicyResource) ResourceType() string { + return "azuread_group_role_management_policy" +} + +func (r RoleManagementPolicyResource) ModelObject() interface{} { + return &RoleManagementPolicyModel{} +} + +func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "object_id": { + Description: "ID of the group to which this policy is assigned", + Type: pluginsdk.TypeString, + Required: true, + ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + }, + + "assignment_type": { + Description: "The ID of the assignment to the group", + Type: pluginsdk.TypeString, + Required: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ + msgraph.PrivilegedAccessGroupRelationshipMember, + msgraph.PrivilegedAccessGroupRelationshipOwner, + msgraph.PrivilegedAccessGroupRelationshipUnknown, + }, false)), + }, + + "eligible_assignment_rules": { + Description: "The rules for eligible assignment of the policy", + Type: pluginsdk.TypeMap, + Optional: true, + Computed: true, + Elem: map[string]*pluginsdk.Schema{ + // Expiration_Admin_Eligibility #microsoft.graph.unifiedRoleManagementPolicyExpirationRule + "allow_permanent": { // isExpirationRequired + Description: "Whether assignments can be permanent", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + ConflictsWith: []string{"eligible_assignment.0.allow_permanent"}, + }, + + "expire_after": { // maximumDuration + Description: "The duration after which assignments expire", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ConflictsWith: []string{"eligible_assignment.0.allow_permanent"}, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"P15D", "P1M", "P3M", "P6M", "P1Y"}, false)), + }, + }, + }, + + "active_assignment_rules": { + Description: "The rules for active assignment of the policy", + Type: pluginsdk.TypeMap, + Optional: true, + Computed: true, + Elem: map[string]*pluginsdk.Schema{ + // Expiration_Admin_Assignment #microsoft.graph.unifiedRoleManagementPolicyExpirationRule + "allow_permanent": { // isExpirationRequired + Description: "Whether assignments can be permanent", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + ConflictsWith: []string{"eligible_assignment.0.allow_permanent"}, + }, + + "expire_after": { // maximumDuration + Description: "The duration after which assignments expire", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ConflictsWith: []string{"eligible_assignment.0.allow_permanent"}, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"P15D", "P1M", "P3M", "P6M", "P1Y"}, false)), + }, + + // Enablement_Admin_Assignment #microsoft.graph.unifiedRoleManagementPolicyEnablementRule + "require_multifactor_authentication": { // enabledRule "MultiFactorAuthentication" + Description: "Whether multi-factor authentication is required to make an assignment", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + "require_justification": { // enabledRule "Justification" + Description: "Whether a justification is required to make an assignment", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + }, + }, + + "activation_rules": { + Description: "The activation rules of the policy", + Type: pluginsdk.TypeMap, + Optional: true, + Computed: true, + Elem: map[string]*pluginsdk.Schema{ + // Expiration_EndUser_Assignment #microsoft.graph.unifiedRoleManagementPolicyExpirationRule + "maximum_duration": { // maximumDuration + Description: "The maximum duration an activation can be valid for", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ + "PT30M", "PT1H", "PT1H30M", "PT2H", "PT2H30M", "PT3H", "PT3H30M", "PT4H", "PT4H30M", "PT5H", "PT5H30M", "PT6H", + "PT6H30M", "PT7H", "PT7H30M", "PT8H", "PT8H30M", "PT9H", "PT9H30M", "PT10H", "PT10H30M", "PT11H", "PT11H30M", "PT12H", + "PT12H30M", "PT13H", "PT13H30M", "PT14H", "PT14H30M", "PT15H", "PT15H30M", "PT16H", "PT16H30M", "PT17H", "PT17H30M", "PT18H", + "PT18H30M", "PT19H", "PT19H30M", "PT20H", "PT20H30M", "PT21H", "PT21H30M", "PT22H", "PT22H30M", "PT23H", "PT23H30M", "P1D", + }, false)), + }, + + // Approval_EndUser_Assignment #microsoft.graph.unifiedRoleManagementPolicyApprovalRule + "require_approval": { // setting.isApprovalRequired + Description: "Whether an approval is required for activation", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + + "approval_stages": { // setting.approvalStages + Description: "The approval stages for the activation", + Type: pluginsdk.TypeMap, + Optional: true, + MinItems: 1, + MaxItems: 1, + Elem: map[string]*pluginsdk.Schema{ + "primary_approvers": { // primaryApprovers + Description: "The IDs of the users or groups who can approve the activation", + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MinItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "description": { // description + Description: "The description of the approver", + Type: pluginsdk.TypeString, + Required: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + }, + + "user_id": { // "@odata.type" : "#microsoft.graph.singleUser", user + Description: "The ID of the user to act as an approver", + Type: pluginsdk.TypeString, + Required: true, + ConflictsWith: []string{"approvers.0.group_id"}, + ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + }, + + "group_id": { // "@odata.type" : "#microsoft.graph.groupMembers" + Description: "The ID of the group to act as an approver", + Type: pluginsdk.TypeString, + Required: true, + ConflictsWith: []string{"approvers.0.user_id"}, + ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + }, + }, + }, + }, + }, + }, + + // AuthenticationContext_EndUser_Assignment #microsoft.graph.unifiedRoleManagementPolicyAuthenticationContextRule + "require_conditional_access_authentication_context": { // isEnabled, claimValue + Description: "Whether a conditional access context is required during activation", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ConflictsWith: []string{"activation.0.require_multifactor_authentication"}, + ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + }, + + // Enablement_EndUser_Assignment #microsoft.graph.unifiedRoleManagementPolicyEnablementRule + "require_multifactor_authentication": { // enabledRule "MultiFactorAuthentication" + Description: "Whether multi-factor authentication is required during activation", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + ConflictsWith: []string{"activation.0.require_conditional_access"}, + }, + + "require_justification": { // enabledRules "Justification" + Description: "Whether a justification is required during activation", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + + "require_ticket_info": { // enabledRules "Ticketing" + Description: "Whether ticket information is required during activation", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + }, + }, + + "notification_rules": { + Description: "The notification rules of the policy", + Type: pluginsdk.TypeMap, + Optional: true, + Computed: true, + Elem: map[string]*pluginsdk.Schema{ + "admin_notifications": { + Description: "The admin notifications on assignment", + Type: pluginsdk.TypeMap, + Optional: true, + Computed: true, + Elem: map[string]*pluginsdk.Schema{ + // Notification_Admin_Admin_Eligibility #microsoft.graph.unifiedRoleManagementPolicyNotificationRule + "eligible_assignments": { + Description: "The admin notifications for eligible assignments", + Type: pluginsdk.TypeMap, + Optional: true, + Computed: true, + Elem: map[string]*pluginsdk.Schema{ + "notification_level": { // notificationLevel All/Critical + Description: "What level of notifications are sent", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), + }, + "default_recipients": { // isDefaultRecipientsEnabled + Description: "Whether the default recipients are notified", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + "additional_recipients": { // notificationRecipients + Description: "The additional recipients to notify", + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + }, + }, + + // Notification_Admin_Admin_Assignment #microsoft.graph.unifiedRoleManagementPolicyNotificationRule + "active_assignments": { + Description: "The admin notifications for active assignments", + Type: pluginsdk.TypeMap, + Optional: true, + Computed: true, + Elem: map[string]*pluginsdk.Schema{ + "notification_level": { // notificationLevel All/Critical + Description: "What level of notifications are sent", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), + }, + "default_recipients": { // isDefaultRecipientsEnabled + Description: "Whether the default recipients are notified", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + "additional_recipients": { // notificationRecipients + Description: "The additional recipients to notify", + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + }, + }, + + // Notification_Admin_EndUser_Assignment #microsoft.graph.unifiedRoleManagementPolicyNotificationRule + "activations": { + Description: "The admin notifications for role activation", + Type: pluginsdk.TypeMap, + Optional: true, + Computed: true, + Elem: map[string]*pluginsdk.Schema{ + "notification_level": { // notificationLevel All/Critical + Description: "What level of notifications are sent", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), + }, + "default_recipients": { // isDefaultRecipientsEnabled + Description: "Whether the default recipients are notified", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + "additional_recipients": { // notificationRecipients + Description: "The additional recipients to notify", + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + }, + }, + }, + }, + + "approver_notifications": { + Description: "The admin notifications on assignment", + Type: pluginsdk.TypeMap, + Optional: true, + Computed: true, + Elem: map[string]*pluginsdk.Schema{ + // Notification_Approver_Admin_Eligibility #microsoft.graph.unifiedRoleManagementPolicyNotificationRule + "eligible_assignments": { + Description: "The admin notifications for eligible assignments", + Type: pluginsdk.TypeMap, + Optional: true, + Computed: true, + Elem: map[string]*pluginsdk.Schema{ + "notification_level": { // notificationLevel All/Critical + Description: "What level of notifications are sent", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), + }, + "default_recipients": { // isDefaultRecipientsEnabled + Description: "Whether the default recipients are notified", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + "additional_recipients": { // notificationRecipients + Description: "The additional recipients to notify", + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + }, + }, + + // Notification_Approver_Admin_Assignment #microsoft.graph.unifiedRoleManagementPolicyNotificationRule + "active_assignments": { + Description: "The admin notifications for active assignments", + Type: pluginsdk.TypeMap, + Optional: true, + Computed: true, + Elem: map[string]*pluginsdk.Schema{ + "notification_level": { // notificationLevel All/Critical + Description: "What level of notifications are sent", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), + }, + "default_recipients": { // isDefaultRecipientsEnabled + Description: "Whether the default recipients are notified", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + "additional_recipients": { // notificationRecipients + Description: "The additional recipients to notify", + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + }, + }, + + // Notification_Approver_EndUser_Assignment #microsoft.graph.unifiedRoleManagementPolicyNotificationRule + "activations": { + Description: "The admin notifications for role activation", + Type: pluginsdk.TypeMap, + Optional: true, + Computed: true, + Elem: map[string]*pluginsdk.Schema{ + "notification_level": { // notificationLevel All/Critical + Description: "What level of notifications are sent", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), + }, + "default_recipients": { // isDefaultRecipientsEnabled + Description: "Whether the default recipients are notified", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + "additional_recipients": { // notificationRecipients + Description: "The additional recipients to notify", + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + }, + }, + }, + }, + + "assignee_notifications": { + Description: "The admin notifications on assignment", + Type: pluginsdk.TypeMap, + Optional: true, + Computed: true, + Elem: map[string]*pluginsdk.Schema{ + // Notification_Requestor_Admin_Eligibility #microsoft.graph.unifiedRoleManagementPolicyNotificationRule + "eligible_assignments": { + Description: "The admin notifications for eligible assignments", + Type: pluginsdk.TypeMap, + Optional: true, + Computed: true, + Elem: map[string]*pluginsdk.Schema{ + "notification_level": { // notificationLevel All/Critical + Description: "What level of notifications are sent", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), + }, + "default_recipients": { // isDefaultRecipientsEnabled + Description: "Whether the default recipients are notified", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + "additional_recipients": { // notificationRecipients + Description: "The additional recipients to notify", + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + }, + }, + + // Notification_Requestor_Admin_Assignment #microsoft.graph.unifiedRoleManagementPolicyNotificationRule + "active_assignments": { + Description: "The admin notifications for active assignments", + Type: pluginsdk.TypeMap, + Optional: true, + Computed: true, + Elem: map[string]*pluginsdk.Schema{ + "notification_level": { // notificationLevel All/Critical + Description: "What level of notifications are sent", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), + }, + "default_recipients": { // isDefaultRecipientsEnabled + Description: "Whether the default recipients are notified", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + "additional_recipients": { // notificationRecipients + Description: "The additional recipients to notify", + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + }, + }, + + // Notification_Requestor_EndUser_Assignment #microsoft.graph.unifiedRoleManagementPolicyNotificationRule + "activations": { + Description: "The admin notifications for role activation", + Type: pluginsdk.TypeMap, + Optional: true, + Computed: true, + Elem: map[string]*pluginsdk.Schema{ + "notification_level": { // notificationLevel All/Critical + Description: "What level of notifications are sent", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), + }, + "default_recipients": { // isDefaultRecipientsEnabled + Description: "Whether the default recipients are notified", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + "additional_recipients": { // notificationRecipients + Description: "The additional recipients to notify", + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func (r RoleManagementPolicyResource) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "display_name": { + Description: "The display name of the policy", + Type: pluginsdk.TypeString, + Optional: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + }, + + "description": { + Description: "Description of the policy", + Type: pluginsdk.TypeString, + Optional: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + }, + } +} + +func (r RoleManagementPolicyResource) Create() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + }, + } +} + +func (r RoleManagementPolicyResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + }, + } +} + +func (r RoleManagementPolicyResource) Update() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + }, + } +} + +func (r RoleManagementPolicyResource) Delete() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + }, + } +} From 7b1a840531f84ee3945d3dfd34e39b21117eb11d Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Mon, 4 Mar 2024 22:36:19 +1300 Subject: [PATCH 11/55] Initial CRUD functions --- .../group_role_management_policy_resource.go | 540 +++++++++++++++--- 1 file changed, 469 insertions(+), 71 deletions(-) diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index 7b7da9c6a5..ef7bb20ca6 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -6,10 +6,9 @@ package policies import ( "context" "fmt" - "net/http" - "slices" "time" + "github.com/hashicorp/go-azure-helpers/lang/pointer" "github.com/hashicorp/go-azure-sdk/sdk/odata" "github.com/hashicorp/terraform-provider-azuread/internal/sdk" "github.com/hashicorp/terraform-provider-azuread/internal/services/policies/parse" @@ -23,23 +22,27 @@ type RoleManagementPolicyModel struct { DisplayName string `tfschema:"display_name"` GroupId string `tfschema:"object_id"` ScopeType msgraph.UnifiedRoleManagementPolicyScope `tfschema:"assignment_type"` - ActiveAssignmentRules AssignmentRules `tfschema:"active_assignment_rules` - EligbleAssignmentRules AssignmentRules `tfschema:"eligible_assignment_rules` + ActiveAssignmentRules AssignmentRules `tfschema:"active_assignment_rules"` + EligbleAssignmentRules AssignmentRules `tfschema:"eligible_assignment_rules"` ActivationRules ActivationRules `tfschema:"activation_rules"` NotificationRules NotificationRules `tfschema:"notification_rules"` } type AssignmentRules struct { - AllowPermanent bool `tfschema:"allow_permanent"` - ExpireAfter string `tfschema:"expire_after"` - ReqireMultiFactorAuth bool `tfschema:"require_multifactor_authentication"` - RequireJustification bool `tfschema:"require_justification"` + AllowPermanent bool `tfschema:"allow_permanent"` + ExpireAfter string `tfschema:"expire_after"` + RequireMultiFactorAuth bool `tfschema:"require_multifactor_authentication"` + RequireJustification bool `tfschema:"require_justification"` } type ActivationRules struct { - MaximumDuration string `tfschema:"maximum_duration"` - RequireApproval bool `tfschema:"require_approval"` - ApprovalStages []ApprovalStage `tfschema:"approval_stages"` + MaximumDuration string `tfschema:"maximum_duration"` + RequireApproval bool `tfschema:"require_approval"` + ApprovalStages []ApprovalStage `tfschema:"approval_stages"` + RequireConditionalAccessContext string `tfschema:"require_conditional_access_authentication_context"` + RequireMultiFactorAuth bool `tfschema:"require_multifactor_authentication"` + RequireJustification bool `tfschema:"require_justification"` + RequireTicketInfo bool `tfschema:"require_ticket_info"` } type ApprovalStage struct { @@ -92,6 +95,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { Description: "ID of the group to which this policy is assigned", Type: pluginsdk.TypeString, Required: true, + ForceNew: true, ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), }, @@ -99,6 +103,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { Description: "The ID of the assignment to the group", Type: pluginsdk.TypeString, Required: true, + ForceNew: true, ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ msgraph.PrivilegedAccessGroupRelationshipMember, msgraph.PrivilegedAccessGroupRelationshipOwner, @@ -112,8 +117,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { Optional: true, Computed: true, Elem: map[string]*pluginsdk.Schema{ - // Expiration_Admin_Eligibility #microsoft.graph.unifiedRoleManagementPolicyExpirationRule - "allow_permanent": { // isExpirationRequired + "allow_permanent": { Description: "Whether assignments can be permanent", Type: pluginsdk.TypeBool, Optional: true, @@ -121,7 +125,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { ConflictsWith: []string{"eligible_assignment.0.allow_permanent"}, }, - "expire_after": { // maximumDuration + "expire_after": { Description: "The duration after which assignments expire", Type: pluginsdk.TypeString, Optional: true, @@ -138,8 +142,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { Optional: true, Computed: true, Elem: map[string]*pluginsdk.Schema{ - // Expiration_Admin_Assignment #microsoft.graph.unifiedRoleManagementPolicyExpirationRule - "allow_permanent": { // isExpirationRequired + "allow_permanent": { Description: "Whether assignments can be permanent", Type: pluginsdk.TypeBool, Optional: true, @@ -147,7 +150,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { ConflictsWith: []string{"eligible_assignment.0.allow_permanent"}, }, - "expire_after": { // maximumDuration + "expire_after": { Description: "The duration after which assignments expire", Type: pluginsdk.TypeString, Optional: true, @@ -156,14 +159,14 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"P15D", "P1M", "P3M", "P6M", "P1Y"}, false)), }, - // Enablement_Admin_Assignment #microsoft.graph.unifiedRoleManagementPolicyEnablementRule - "require_multifactor_authentication": { // enabledRule "MultiFactorAuthentication" + "require_multifactor_authentication": { Description: "Whether multi-factor authentication is required to make an assignment", Type: pluginsdk.TypeBool, Optional: true, Computed: true, }, - "require_justification": { // enabledRule "Justification" + + "require_justification": { Description: "Whether a justification is required to make an assignment", Type: pluginsdk.TypeBool, Optional: true, @@ -178,8 +181,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { Optional: true, Computed: true, Elem: map[string]*pluginsdk.Schema{ - // Expiration_EndUser_Assignment #microsoft.graph.unifiedRoleManagementPolicyExpirationRule - "maximum_duration": { // maximumDuration + "maximum_duration": { Description: "The maximum duration an activation can be valid for", Type: pluginsdk.TypeString, Optional: true, @@ -192,22 +194,21 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { }, false)), }, - // Approval_EndUser_Assignment #microsoft.graph.unifiedRoleManagementPolicyApprovalRule - "require_approval": { // setting.isApprovalRequired + "require_approval": { Description: "Whether an approval is required for activation", Type: pluginsdk.TypeBool, Optional: true, Computed: true, }, - "approval_stages": { // setting.approvalStages + "approval_stages": { Description: "The approval stages for the activation", Type: pluginsdk.TypeMap, Optional: true, MinItems: 1, MaxItems: 1, Elem: map[string]*pluginsdk.Schema{ - "primary_approvers": { // primaryApprovers + "primary_approvers": { Description: "The IDs of the users or groups who can approve the activation", Type: pluginsdk.TypeList, Optional: true, @@ -215,14 +216,14 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { MinItems: 1, Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ - "description": { // description + "description": { Description: "The description of the approver", Type: pluginsdk.TypeString, Required: true, ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), }, - "user_id": { // "@odata.type" : "#microsoft.graph.singleUser", user + "user_id": { Description: "The ID of the user to act as an approver", Type: pluginsdk.TypeString, Required: true, @@ -230,7 +231,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), }, - "group_id": { // "@odata.type" : "#microsoft.graph.groupMembers" + "group_id": { Description: "The ID of the group to act as an approver", Type: pluginsdk.TypeString, Required: true, @@ -243,8 +244,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { }, }, - // AuthenticationContext_EndUser_Assignment #microsoft.graph.unifiedRoleManagementPolicyAuthenticationContextRule - "require_conditional_access_authentication_context": { // isEnabled, claimValue + "require_conditional_access_authentication_context": { Description: "Whether a conditional access context is required during activation", Type: pluginsdk.TypeString, Optional: true, @@ -253,8 +253,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), }, - // Enablement_EndUser_Assignment #microsoft.graph.unifiedRoleManagementPolicyEnablementRule - "require_multifactor_authentication": { // enabledRule "MultiFactorAuthentication" + "require_multifactor_authentication": { Description: "Whether multi-factor authentication is required during activation", Type: pluginsdk.TypeBool, Optional: true, @@ -262,14 +261,14 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { ConflictsWith: []string{"activation.0.require_conditional_access"}, }, - "require_justification": { // enabledRules "Justification" + "require_justification": { Description: "Whether a justification is required during activation", Type: pluginsdk.TypeBool, Optional: true, Computed: true, }, - "require_ticket_info": { // enabledRules "Ticketing" + "require_ticket_info": { Description: "Whether ticket information is required during activation", Type: pluginsdk.TypeBool, Optional: true, @@ -290,27 +289,26 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { Optional: true, Computed: true, Elem: map[string]*pluginsdk.Schema{ - // Notification_Admin_Admin_Eligibility #microsoft.graph.unifiedRoleManagementPolicyNotificationRule "eligible_assignments": { Description: "The admin notifications for eligible assignments", Type: pluginsdk.TypeMap, Optional: true, Computed: true, Elem: map[string]*pluginsdk.Schema{ - "notification_level": { // notificationLevel All/Critical + "notification_level": { Description: "What level of notifications are sent", Type: pluginsdk.TypeString, Optional: true, Computed: true, ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), }, - "default_recipients": { // isDefaultRecipientsEnabled + "default_recipients": { Description: "Whether the default recipients are notified", Type: pluginsdk.TypeBool, Optional: true, Computed: true, }, - "additional_recipients": { // notificationRecipients + "additional_recipients": { Description: "The additional recipients to notify", Type: pluginsdk.TypeList, Optional: true, @@ -321,27 +319,26 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { }, }, - // Notification_Admin_Admin_Assignment #microsoft.graph.unifiedRoleManagementPolicyNotificationRule "active_assignments": { Description: "The admin notifications for active assignments", Type: pluginsdk.TypeMap, Optional: true, Computed: true, Elem: map[string]*pluginsdk.Schema{ - "notification_level": { // notificationLevel All/Critical + "notification_level": { Description: "What level of notifications are sent", Type: pluginsdk.TypeString, Optional: true, Computed: true, ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), }, - "default_recipients": { // isDefaultRecipientsEnabled + "default_recipients": { Description: "Whether the default recipients are notified", Type: pluginsdk.TypeBool, Optional: true, Computed: true, }, - "additional_recipients": { // notificationRecipients + "additional_recipients": { Description: "The additional recipients to notify", Type: pluginsdk.TypeList, Optional: true, @@ -352,27 +349,26 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { }, }, - // Notification_Admin_EndUser_Assignment #microsoft.graph.unifiedRoleManagementPolicyNotificationRule "activations": { Description: "The admin notifications for role activation", Type: pluginsdk.TypeMap, Optional: true, Computed: true, Elem: map[string]*pluginsdk.Schema{ - "notification_level": { // notificationLevel All/Critical + "notification_level": { Description: "What level of notifications are sent", Type: pluginsdk.TypeString, Optional: true, Computed: true, ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), }, - "default_recipients": { // isDefaultRecipientsEnabled + "default_recipients": { Description: "Whether the default recipients are notified", Type: pluginsdk.TypeBool, Optional: true, Computed: true, }, - "additional_recipients": { // notificationRecipients + "additional_recipients": { Description: "The additional recipients to notify", Type: pluginsdk.TypeList, Optional: true, @@ -391,27 +387,26 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { Optional: true, Computed: true, Elem: map[string]*pluginsdk.Schema{ - // Notification_Approver_Admin_Eligibility #microsoft.graph.unifiedRoleManagementPolicyNotificationRule "eligible_assignments": { Description: "The admin notifications for eligible assignments", Type: pluginsdk.TypeMap, Optional: true, Computed: true, Elem: map[string]*pluginsdk.Schema{ - "notification_level": { // notificationLevel All/Critical + "notification_level": { Description: "What level of notifications are sent", Type: pluginsdk.TypeString, Optional: true, Computed: true, ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), }, - "default_recipients": { // isDefaultRecipientsEnabled + "default_recipients": { Description: "Whether the default recipients are notified", Type: pluginsdk.TypeBool, Optional: true, Computed: true, }, - "additional_recipients": { // notificationRecipients + "additional_recipients": { Description: "The additional recipients to notify", Type: pluginsdk.TypeList, Optional: true, @@ -422,27 +417,26 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { }, }, - // Notification_Approver_Admin_Assignment #microsoft.graph.unifiedRoleManagementPolicyNotificationRule "active_assignments": { Description: "The admin notifications for active assignments", Type: pluginsdk.TypeMap, Optional: true, Computed: true, Elem: map[string]*pluginsdk.Schema{ - "notification_level": { // notificationLevel All/Critical + "notification_level": { Description: "What level of notifications are sent", Type: pluginsdk.TypeString, Optional: true, Computed: true, ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), }, - "default_recipients": { // isDefaultRecipientsEnabled + "default_recipients": { Description: "Whether the default recipients are notified", Type: pluginsdk.TypeBool, Optional: true, Computed: true, }, - "additional_recipients": { // notificationRecipients + "additional_recipients": { Description: "The additional recipients to notify", Type: pluginsdk.TypeList, Optional: true, @@ -453,27 +447,26 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { }, }, - // Notification_Approver_EndUser_Assignment #microsoft.graph.unifiedRoleManagementPolicyNotificationRule "activations": { Description: "The admin notifications for role activation", Type: pluginsdk.TypeMap, Optional: true, Computed: true, Elem: map[string]*pluginsdk.Schema{ - "notification_level": { // notificationLevel All/Critical + "notification_level": { Description: "What level of notifications are sent", Type: pluginsdk.TypeString, Optional: true, Computed: true, ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), }, - "default_recipients": { // isDefaultRecipientsEnabled + "default_recipients": { Description: "Whether the default recipients are notified", Type: pluginsdk.TypeBool, Optional: true, Computed: true, }, - "additional_recipients": { // notificationRecipients + "additional_recipients": { Description: "The additional recipients to notify", Type: pluginsdk.TypeList, Optional: true, @@ -492,27 +485,26 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { Optional: true, Computed: true, Elem: map[string]*pluginsdk.Schema{ - // Notification_Requestor_Admin_Eligibility #microsoft.graph.unifiedRoleManagementPolicyNotificationRule "eligible_assignments": { Description: "The admin notifications for eligible assignments", Type: pluginsdk.TypeMap, Optional: true, Computed: true, Elem: map[string]*pluginsdk.Schema{ - "notification_level": { // notificationLevel All/Critical + "notification_level": { Description: "What level of notifications are sent", Type: pluginsdk.TypeString, Optional: true, Computed: true, ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), }, - "default_recipients": { // isDefaultRecipientsEnabled + "default_recipients": { Description: "Whether the default recipients are notified", Type: pluginsdk.TypeBool, Optional: true, Computed: true, }, - "additional_recipients": { // notificationRecipients + "additional_recipients": { Description: "The additional recipients to notify", Type: pluginsdk.TypeList, Optional: true, @@ -523,27 +515,26 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { }, }, - // Notification_Requestor_Admin_Assignment #microsoft.graph.unifiedRoleManagementPolicyNotificationRule "active_assignments": { Description: "The admin notifications for active assignments", Type: pluginsdk.TypeMap, Optional: true, Computed: true, Elem: map[string]*pluginsdk.Schema{ - "notification_level": { // notificationLevel All/Critical + "notification_level": { Description: "What level of notifications are sent", Type: pluginsdk.TypeString, Optional: true, Computed: true, ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), }, - "default_recipients": { // isDefaultRecipientsEnabled + "default_recipients": { Description: "Whether the default recipients are notified", Type: pluginsdk.TypeBool, Optional: true, Computed: true, }, - "additional_recipients": { // notificationRecipients + "additional_recipients": { Description: "The additional recipients to notify", Type: pluginsdk.TypeList, Optional: true, @@ -554,27 +545,26 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { }, }, - // Notification_Requestor_EndUser_Assignment #microsoft.graph.unifiedRoleManagementPolicyNotificationRule "activations": { Description: "The admin notifications for role activation", Type: pluginsdk.TypeMap, Optional: true, Computed: true, Elem: map[string]*pluginsdk.Schema{ - "notification_level": { // notificationLevel All/Critical + "notification_level": { Description: "What level of notifications are sent", Type: pluginsdk.TypeString, Optional: true, Computed: true, ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), }, - "default_recipients": { // isDefaultRecipientsEnabled + "default_recipients": { Description: "Whether the default recipients are notified", Type: pluginsdk.TypeBool, Optional: true, Computed: true, }, - "additional_recipients": { // notificationRecipients + "additional_recipients": { Description: "The additional recipients to notify", Type: pluginsdk.TypeList, Optional: true, @@ -613,6 +603,46 @@ func (r RoleManagementPolicyResource) Create() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 5 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + policyClient := metadata.Client.Policies.RoleManagementPolicyClient + assignmentClient := metadata.Client.Policies.RoleManagementPolicyAssignmentClient + + // Fetch the existing policy, as they already exist + policies, _, err := assignmentClient.List(ctx, odata.Query{ + Filter: fmt.Sprintf("scopeId eq '%s' and scopeType eq 'Group'", metadata.ResourceData.Get("object_id").(string)), + }) + if err != nil { + return fmt.Errorf("Could not list existing policy, %+v", err) + } + if len(*policies) != 0 { + return fmt.Errorf("Got the wrong number of policies, expected 1, got %d", len(*policies)) + } + + id, err := parse.ParseRoleManagementPolicyID(*(*policies)[0].ID) + if err != nil { + return fmt.Errorf("Could not parse policy ID, %+v", err) + } + + metadata.SetID(id) + + policy, _, err := policyClient.Get(ctx, id.ID()) + if err != nil { + return fmt.Errorf("Could not retrieve existing policy, %+v", err) + } + if policy == nil { + return fmt.Errorf("retrieving %s: API error, result was nil", id) + } + + policyUpdate, err := buildPolicyForUpdate(pointer.To(metadata), policy) + if err != nil { + return fmt.Errorf("Could not build update request, %+v", err) + } + + _, err = policyClient.Update(ctx, *policyUpdate) + if err != nil { + return fmt.Errorf("Could not create assignment schedule request, %+v", err) + } + + return nil }, } } @@ -621,6 +651,149 @@ func (r RoleManagementPolicyResource) Read() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 5 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Policies.RoleManagementPolicyClient + + id, err := parse.ParseRoleManagementPolicyID(metadata.ResourceData.Id()) + if err != nil { + return fmt.Errorf("Could not parse policy ID, %+v", err) + } + + var model RoleManagementPolicyModel + if err := metadata.Decode(&model); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + result, _, err := client.Get(ctx, id.ID()) + if err != nil { + return fmt.Errorf("retrieving %s: %+v", id, err) + } + if result == nil { + return fmt.Errorf("retrieving %s: API error, result was nil", id) + } + + model.Description = *result.Description + model.DisplayName = *result.DisplayName + model.GroupId = *result.ScopeId + model.ScopeType = result.ScopeType + + for _, rule := range *result.Rules { + switch *rule.ID { + case "Expiration_Admin_Eligibility": + model.EligbleAssignmentRules.AllowPermanent = *rule.IsExpirationRequired + model.EligbleAssignmentRules.ExpireAfter = *rule.MaximumDuration + + case "Enablement_Admin_Assignment": + model.ActiveAssignmentRules.RequireMultiFactorAuth = false + model.ActiveAssignmentRules.RequireJustification = false + for _, enabledRule := range *rule.EnabledRules { + switch enabledRule { + case "MultiFactorAuthentication": + model.ActiveAssignmentRules.RequireMultiFactorAuth = true + case "Justification": + model.ActiveAssignmentRules.RequireJustification = true + } + } + + case "Expiration_Admin_Assignment": + model.ActiveAssignmentRules.AllowPermanent = *rule.IsExpirationRequired + model.ActiveAssignmentRules.ExpireAfter = *rule.MaximumDuration + + case "Expiration_EndUser_Assignment": + model.ActivationRules.MaximumDuration = *rule.MaximumDuration + + case "Approval_EndUser_Assignment": + model.ActivationRules.RequireApproval = *rule.Setting.IsApprovalRequired + model.ActivationRules.ApprovalStages = make([]ApprovalStage, 0) + for _, stage := range *rule.Setting.ApprovalStages { + primaryApprovers := make([]Approver, 0) + for _, approver := range *stage.PrimaryApprovers { + switch { + case *approver.ODataType == "#microsoft.graph.singleUser": + primaryApprovers = append(primaryApprovers, Approver{ + Description: *approver.Description, + UserId: *approver.ID, + }) + case *approver.ODataType == "#microsoft.graph.groupMembers": + primaryApprovers = append(primaryApprovers, Approver{ + Description: *approver.Description, + GroupId: *approver.ID, + }) + default: + return fmt.Errorf("unknown approver type: %s", approver.ODataType) + } + model.ActivationRules.ApprovalStages = append(model.ActivationRules.ApprovalStages, ApprovalStage{ + PrimaryApprovers: primaryApprovers, + }) + } + } + + case "AuthenticationContext_EndUser_Assignment": + model.ActivationRules.RequireConditionalAccessContext = *rule.ClaimValue + + case "Enablement_EndUser_Assignment": + model.ActivationRules.RequireMultiFactorAuth = false + model.ActivationRules.RequireJustification = false + model.ActivationRules.RequireTicketInfo = false + for _, enabledRule := range *rule.EnabledRules { + switch enabledRule { + case "MultiFactorAuthentication": + model.ActivationRules.RequireMultiFactorAuth = true + case "Justification": + model.ActivationRules.RequireJustification = true + case "Ticketing": + model.ActivationRules.RequireTicketInfo = true + } + } + + case "Notification_Admin_Admin_Eligibility": + model.NotificationRules.AdminNotifications.EligibleAssignments.NotificationLevel = rule.NotificationLevel + model.NotificationRules.AdminNotifications.EligibleAssignments.DefaultRecipients = *rule.IsDefaultRecipientsEnabled + model.NotificationRules.AdminNotifications.EligibleAssignments.AdditionalRecipients = *rule.NotificationRecipients + + case "Notification_Admin_Admin_Assignment": + model.NotificationRules.AdminNotifications.ActiveAssignments.NotificationLevel = rule.NotificationLevel + model.NotificationRules.AdminNotifications.ActiveAssignments.DefaultRecipients = *rule.IsDefaultRecipientsEnabled + model.NotificationRules.AdminNotifications.ActiveAssignments.AdditionalRecipients = *rule.NotificationRecipients + + case "Notification_Admin_EndUser_Assignment": + model.NotificationRules.AdminNotifications.Activations.NotificationLevel = rule.NotificationLevel + model.NotificationRules.AdminNotifications.Activations.DefaultRecipients = *rule.IsDefaultRecipientsEnabled + model.NotificationRules.AdminNotifications.Activations.AdditionalRecipients = *rule.NotificationRecipients + + case "Notification_Approver_Admin_Eligibility": + model.NotificationRules.ApproverNotifications.EligibleAssignments.NotificationLevel = rule.NotificationLevel + model.NotificationRules.ApproverNotifications.EligibleAssignments.DefaultRecipients = *rule.IsDefaultRecipientsEnabled + model.NotificationRules.ApproverNotifications.EligibleAssignments.AdditionalRecipients = *rule.NotificationRecipients + + case "Notification_Approver_Admin_Assignment": + model.NotificationRules.ApproverNotifications.ActiveAssignments.NotificationLevel = rule.NotificationLevel + model.NotificationRules.ApproverNotifications.ActiveAssignments.DefaultRecipients = *rule.IsDefaultRecipientsEnabled + model.NotificationRules.ApproverNotifications.ActiveAssignments.AdditionalRecipients = *rule.NotificationRecipients + + case "Notification_Approver_EndUser_Assignment": + model.NotificationRules.ApproverNotifications.Activations.NotificationLevel = rule.NotificationLevel + model.NotificationRules.ApproverNotifications.Activations.DefaultRecipients = *rule.IsDefaultRecipientsEnabled + model.NotificationRules.ApproverNotifications.Activations.AdditionalRecipients = *rule.NotificationRecipients + + case "Notification_Requestor_Admin_Eligibility": + model.NotificationRules.AssigneeNotifications.EligibleAssignments.NotificationLevel = rule.NotificationLevel + model.NotificationRules.AssigneeNotifications.EligibleAssignments.DefaultRecipients = *rule.IsDefaultRecipientsEnabled + model.NotificationRules.AssigneeNotifications.EligibleAssignments.AdditionalRecipients = *rule.NotificationRecipients + + case "Notification_Requestor_Admin_Assignment": + model.NotificationRules.AssigneeNotifications.ActiveAssignments.NotificationLevel = rule.NotificationLevel + model.NotificationRules.AssigneeNotifications.ActiveAssignments.DefaultRecipients = *rule.IsDefaultRecipientsEnabled + model.NotificationRules.AssigneeNotifications.ActiveAssignments.AdditionalRecipients = *rule.NotificationRecipients + + case "Notification_Requestor_EndUser_Assignment": + model.NotificationRules.AssigneeNotifications.Activations.NotificationLevel = rule.NotificationLevel + model.NotificationRules.AssigneeNotifications.Activations.DefaultRecipients = *rule.IsDefaultRecipientsEnabled + model.NotificationRules.AssigneeNotifications.Activations.AdditionalRecipients = *rule.NotificationRecipients + + } + } + + return metadata.Encode(&model) }, } } @@ -629,6 +802,34 @@ func (r RoleManagementPolicyResource) Update() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 5 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Policies.RoleManagementPolicyClient + + id, err := parse.ParseRoleManagementPolicyID(metadata.ResourceData.Id()) + if err != nil { + return fmt.Errorf("Could not parse policy ID, %+v", err) + } + + metadata.SetID(id) + + policy, _, err := client.Get(ctx, id.ID()) + if err != nil { + return fmt.Errorf("Could not retrieve existing policy, %+v", err) + } + if policy == nil { + return fmt.Errorf("retrieving %s: API error, result was nil", id) + } + + policyUpdate, err := buildPolicyForUpdate(pointer.To(metadata), policy) + if err != nil { + return fmt.Errorf("Could not build update request, %+v", err) + } + + _, err = client.Update(ctx, *policyUpdate) + if err != nil { + return fmt.Errorf("Could not create assignment schedule request, %+v", err) + } + + return nil }, } } @@ -637,6 +838,203 @@ func (r RoleManagementPolicyResource) Delete() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 5 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + id, err := parse.ParseRoleManagementPolicyID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + return metadata.MarkAsGone(id) }, } } + +func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.UnifiedRoleManagementPolicy) (*msgraph.UnifiedRoleManagementPolicy, error) { + var model RoleManagementPolicyModel + if err := metadata.Decode(&model); err != nil { + return nil, fmt.Errorf("decoding: %+v", err) + } + + // Take the slice of rules and convert it to a map with the ID as the key + policyRules := make(map[string]msgraph.UnifiedRoleManagementPolicyRule) + for _, rule := range *policy.Rules { + policyRules[*rule.ID] = rule + } + + if metadata.ResourceData.HasChange("eligible_assignment_rules") { + rule := policyRules["Expiration_Admin_Eligibility"] + rule.IsExpirationRequired = pointer.To(model.EligbleAssignmentRules.AllowPermanent) + rule.MaximumDuration = pointer.To(model.EligbleAssignmentRules.ExpireAfter) + policyRules["Expiration_Admin_Eligibility"] = rule + } + + if metadata.ResourceData.HasChange("active_assignment_rules.require_multifactor_authentication") || + metadata.ResourceData.HasChange("active_assignment_rules.require_justification") { + enabledRules := make([]string, 0) + if model.EligbleAssignmentRules.RequireMultiFactorAuth { + enabledRules = append(enabledRules, "MultiFactorAuthentication") + } + if model.EligbleAssignmentRules.RequireJustification { + enabledRules = append(enabledRules, "Justification") + } + + rule := policyRules["Enablement_Admin_Assignment"] + rule.EnabledRules = pointer.To(enabledRules) + policyRules["Enablement_Admin_Assignment"] = rule + } + + if metadata.ResourceData.HasChange("active_assignment_rules.allow_permanent") || + metadata.ResourceData.HasChange("active_assignment_rules.expire_after") { + rule := policyRules["Expiration_Admin_Assignment"] + rule.IsExpirationRequired = pointer.To(model.EligbleAssignmentRules.AllowPermanent) + rule.MaximumDuration = pointer.To(model.EligbleAssignmentRules.ExpireAfter) + policyRules["Expiration_Admin_Assignment"] = rule + } + + if metadata.ResourceData.HasChange("activation_rules.maximum_duration") { + rule := policyRules["Expiration_EndUser_Assignment"] + rule.MaximumDuration = pointer.To(model.ActivationRules.MaximumDuration) + policyRules["Expiration_EndUser_Assignment"] = rule + } + + if metadata.ResourceData.HasChange("activation_rules.require_approval") || + metadata.ResourceData.HasChange("activation_rules.approval_stages") { + rule := policyRules["Approval_EndUser_Assignment"] + approvalStages := make([]msgraph.ApprovalStage, 0) + for _, stage := range model.ActivationRules.ApprovalStages { + primaryApprovers := make([]msgraph.UserSet, 0) + for _, approver := range stage.PrimaryApprovers { + if approver.UserId != "" { + primaryApprovers = append(primaryApprovers, msgraph.UserSet{ + ODataType: pointer.To("#microsoft.graph.singleUser"), + ID: &approver.UserId, + Description: &approver.Description, + }) + } else if approver.GroupId != "" { + primaryApprovers = append(primaryApprovers, msgraph.UserSet{ + ODataType: pointer.To("#microsoft.graph.groupMembers"), + ID: &approver.GroupId, + Description: &approver.Description, + }) + } else { + return nil, fmt.Errorf("either user_id or group_id must be set") + } + } + + approvalStages = append(approvalStages, msgraph.ApprovalStage{ + PrimaryApprovers: &primaryApprovers, + }) + } + + rule.Setting.IsApprovalRequired = pointer.To(model.ActivationRules.RequireApproval) + rule.Setting.ApprovalStages = &approvalStages + policyRules["Approval_EndUser_Assignment"] = rule + } + + if metadata.ResourceData.HasChange("activation_rules.require_conditional_access_authentication_context") { + rule := policyRules["AuthenticationContext_EndUser_Assignment"] + _, set := metadata.ResourceData.GetOk("activation_rules.require_conditional_access_authentication_context") + rule.IsEnabled = pointer.To(set) + rule.ClaimValue = pointer.To(model.ActivationRules.RequireConditionalAccessContext) + policyRules["AuthenticationContext_EndUser_Assignment"] = rule + } + + if metadata.ResourceData.HasChange("activation_rules.require_multifactor_authentication") || + metadata.ResourceData.HasChange("activation_rules.require_justification") || + metadata.ResourceData.HasChange("activation_rules.require_ticket_info") { + enabledRules := make([]string, 0) + if model.ActivationRules.RequireMultiFactorAuth { + enabledRules = append(enabledRules, "MultiFactorAuthentication") + } + if model.ActivationRules.RequireJustification { + enabledRules = append(enabledRules, "Justification") + } + if model.ActivationRules.RequireTicketInfo { + enabledRules = append(enabledRules, "Ticketing") + } + + rule := policyRules["Enablement_EndUser_Assignment"] + rule.EnabledRules = pointer.To(enabledRules) + policyRules["Enablement_EndUser_Assignment"] = rule + } + + if metadata.ResourceData.HasChange("notification_rules.admin_notifications.eligible_assignments") { + rule := policyRules["Notification_Admin_Admin_Eligibility"] + rule.NotificationLevel = model.NotificationRules.AdminNotifications.EligibleAssignments.NotificationLevel + rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules.AdminNotifications.EligibleAssignments.DefaultRecipients) + rule.NotificationRecipients = &model.NotificationRules.AdminNotifications.EligibleAssignments.AdditionalRecipients + policyRules["Notification_Admin_Admin_Eligibility"] = rule + } + + if metadata.ResourceData.HasChange("notification_rules.admin_notifications.active_assignments") { + rule := policyRules["Notification_Admin_Admin_Assignment"] + rule.NotificationLevel = model.NotificationRules.AdminNotifications.ActiveAssignments.NotificationLevel + rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules.AdminNotifications.ActiveAssignments.DefaultRecipients) + rule.NotificationRecipients = &model.NotificationRules.AdminNotifications.ActiveAssignments.AdditionalRecipients + policyRules["Notification_Admin_Admin_Assignment"] = rule + } + + if metadata.ResourceData.HasChange("notification_rules.admin_notifications.activations") { + rule := policyRules["Notification_Admin_EndUser_Assignment"] + rule.NotificationLevel = model.NotificationRules.AdminNotifications.Activations.NotificationLevel + rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules.AdminNotifications.Activations.DefaultRecipients) + rule.NotificationRecipients = &model.NotificationRules.AdminNotifications.Activations.AdditionalRecipients + policyRules["Notification_Admin_EndUser_Assignment"] = rule + } + + if metadata.ResourceData.HasChange("notification_rules.approver_notifications.eligible_assignments") { + rule := policyRules["Notification_Approver_Admin_Eligibility"] + rule.NotificationLevel = model.NotificationRules.ApproverNotifications.EligibleAssignments.NotificationLevel + rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules.ApproverNotifications.EligibleAssignments.DefaultRecipients) + rule.NotificationRecipients = &model.NotificationRules.ApproverNotifications.EligibleAssignments.AdditionalRecipients + policyRules["Notification_Approver_Admin_Eligibility"] = rule + } + + if metadata.ResourceData.HasChange("notification_rules.approver_notifications.active_assignments") { + rule := policyRules["Notification_Approver_Admin_Assignment"] + rule.NotificationLevel = model.NotificationRules.ApproverNotifications.ActiveAssignments.NotificationLevel + rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules.ApproverNotifications.ActiveAssignments.DefaultRecipients) + rule.NotificationRecipients = &model.NotificationRules.ApproverNotifications.ActiveAssignments.AdditionalRecipients + policyRules["Notification_Approver_Admin_Assignment"] = rule + } + + if metadata.ResourceData.HasChange("notification_rules.approver_notifications.activations") { + rule := policyRules["Notification_Approver_EndUser_Assignment"] + rule.NotificationLevel = model.NotificationRules.ApproverNotifications.Activations.NotificationLevel + rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules.ApproverNotifications.Activations.DefaultRecipients) + rule.NotificationRecipients = &model.NotificationRules.ApproverNotifications.Activations.AdditionalRecipients + policyRules["Notification_Approver_EndUser_Assignment"] = rule + } + + if metadata.ResourceData.HasChange("notification_rules.assignee_notifications.eligible_assignments") { + rule := policyRules["Notification_Requestor_Admin_Eligibility"] + rule.NotificationLevel = model.NotificationRules.AssigneeNotifications.EligibleAssignments.NotificationLevel + rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules.AssigneeNotifications.EligibleAssignments.DefaultRecipients) + rule.NotificationRecipients = &model.NotificationRules.AssigneeNotifications.EligibleAssignments.AdditionalRecipients + policyRules["Notification_Requestor_Admin_Eligibility"] = rule + } + + if metadata.ResourceData.HasChange("notification_rules.assignee_notifications.active_assignments") { + rule := policyRules["Notification_Requestor_Admin_Assignment"] + rule.NotificationLevel = model.NotificationRules.AssigneeNotifications.ActiveAssignments.NotificationLevel + rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules.AssigneeNotifications.ActiveAssignments.DefaultRecipients) + rule.NotificationRecipients = &model.NotificationRules.AssigneeNotifications.ActiveAssignments.AdditionalRecipients + policyRules["Notification_Requestor_Admin_Assignment"] = rule + } + + if metadata.ResourceData.HasChange("notification_rules.assignee_notifications.activations") { + rule := policyRules["Notification_Requestor_EndUser_Assignment"] + rule.NotificationLevel = model.NotificationRules.AssigneeNotifications.Activations.NotificationLevel + rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules.AssigneeNotifications.Activations.DefaultRecipients) + rule.NotificationRecipients = &model.NotificationRules.AssigneeNotifications.Activations.AdditionalRecipients + policyRules["Notification_Requestor_EndUser_Assignment"] = rule + } + + returnRules := make([]msgraph.UnifiedRoleManagementPolicyRule, 0) + for _, rule := range policyRules { + returnRules = append(returnRules, rule) + } + + return &msgraph.UnifiedRoleManagementPolicy{ + Rules: pointer.To(returnRules), + }, nil +} From 8beaa4f79e4c1882ef951237e3b7ffa7bb17cf21 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Mon, 4 Mar 2024 23:06:34 +1300 Subject: [PATCH 12/55] Correct schema errors --- .../group_role_management_policy_resource.go | 832 ++++++++++-------- 1 file changed, 448 insertions(+), 384 deletions(-) diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index ef7bb20ca6..6312a34f56 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -51,8 +51,8 @@ type ApprovalStage struct { type Approver struct { Description string `tfschema:"description"` - UserId string `tfschema:"user_id"` - GroupId string `tfschema:"group_id"` + ObjectId string `tfschema:"object_id"` + ObjectType string `tfschema:"object_type"` } type NotificationRules struct { @@ -113,463 +113,527 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { "eligible_assignment_rules": { Description: "The rules for eligible assignment of the policy", - Type: pluginsdk.TypeMap, + Type: pluginsdk.TypeList, Optional: true, Computed: true, - Elem: map[string]*pluginsdk.Schema{ - "allow_permanent": { - Description: "Whether assignments can be permanent", - Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, - ConflictsWith: []string{"eligible_assignment.0.allow_permanent"}, - }, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "allow_permanent": { + Description: "Whether assignments can be permanent", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + ConflictsWith: []string{"eligible_assignment_rules.0.expire_after"}, + }, - "expire_after": { - Description: "The duration after which assignments expire", - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - ConflictsWith: []string{"eligible_assignment.0.allow_permanent"}, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"P15D", "P1M", "P3M", "P6M", "P1Y"}, false)), + "expire_after": { + Description: "The duration after which assignments expire", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ConflictsWith: []string{"eligible_assignment_rules.0.allow_permanent"}, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"P15D", "P1M", "P3M", "P6M", "P1Y"}, false)), + }, }, }, }, "active_assignment_rules": { Description: "The rules for active assignment of the policy", - Type: pluginsdk.TypeMap, + Type: pluginsdk.TypeList, Optional: true, Computed: true, - Elem: map[string]*pluginsdk.Schema{ - "allow_permanent": { - Description: "Whether assignments can be permanent", - Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, - ConflictsWith: []string{"eligible_assignment.0.allow_permanent"}, - }, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "allow_permanent": { + Description: "Whether assignments can be permanent", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + ConflictsWith: []string{"active_assignment_rules.0.expire_after"}, + }, - "expire_after": { - Description: "The duration after which assignments expire", - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - ConflictsWith: []string{"eligible_assignment.0.allow_permanent"}, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"P15D", "P1M", "P3M", "P6M", "P1Y"}, false)), - }, + "expire_after": { + Description: "The duration after which assignments expire", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ConflictsWith: []string{"active_assignment_rules.0.allow_permanent"}, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"P15D", "P1M", "P3M", "P6M", "P1Y"}, false)), + }, - "require_multifactor_authentication": { - Description: "Whether multi-factor authentication is required to make an assignment", - Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, - }, + "require_multifactor_authentication": { + Description: "Whether multi-factor authentication is required to make an assignment", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, - "require_justification": { - Description: "Whether a justification is required to make an assignment", - Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, + "require_justification": { + Description: "Whether a justification is required to make an assignment", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, }, }, }, "activation_rules": { Description: "The activation rules of the policy", - Type: pluginsdk.TypeMap, + Type: pluginsdk.TypeList, Optional: true, Computed: true, - Elem: map[string]*pluginsdk.Schema{ - "maximum_duration": { - Description: "The maximum duration an activation can be valid for", - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ - "PT30M", "PT1H", "PT1H30M", "PT2H", "PT2H30M", "PT3H", "PT3H30M", "PT4H", "PT4H30M", "PT5H", "PT5H30M", "PT6H", - "PT6H30M", "PT7H", "PT7H30M", "PT8H", "PT8H30M", "PT9H", "PT9H30M", "PT10H", "PT10H30M", "PT11H", "PT11H30M", "PT12H", - "PT12H30M", "PT13H", "PT13H30M", "PT14H", "PT14H30M", "PT15H", "PT15H30M", "PT16H", "PT16H30M", "PT17H", "PT17H30M", "PT18H", - "PT18H30M", "PT19H", "PT19H30M", "PT20H", "PT20H30M", "PT21H", "PT21H30M", "PT22H", "PT22H30M", "PT23H", "PT23H30M", "P1D", - }, false)), - }, - - "require_approval": { - Description: "Whether an approval is required for activation", - Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, - }, - - "approval_stages": { - Description: "The approval stages for the activation", - Type: pluginsdk.TypeMap, - Optional: true, - MinItems: 1, - MaxItems: 1, - Elem: map[string]*pluginsdk.Schema{ - "primary_approvers": { - Description: "The IDs of the users or groups who can approve the activation", - Type: pluginsdk.TypeList, - Optional: true, - Computed: true, - MinItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "description": { - Description: "The description of the approver", - Type: pluginsdk.TypeString, - Required: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), - }, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "maximum_duration": { + Description: "The maximum duration an activation can be valid for", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ + "PT30M", "PT1H", "PT1H30M", "PT2H", "PT2H30M", "PT3H", "PT3H30M", "PT4H", "PT4H30M", "PT5H", "PT5H30M", "PT6H", + "PT6H30M", "PT7H", "PT7H30M", "PT8H", "PT8H30M", "PT9H", "PT9H30M", "PT10H", "PT10H30M", "PT11H", "PT11H30M", "PT12H", + "PT12H30M", "PT13H", "PT13H30M", "PT14H", "PT14H30M", "PT15H", "PT15H30M", "PT16H", "PT16H30M", "PT17H", "PT17H30M", "PT18H", + "PT18H30M", "PT19H", "PT19H30M", "PT20H", "PT20H30M", "PT21H", "PT21H30M", "PT22H", "PT22H30M", "PT23H", "PT23H30M", "P1D", + }, false)), + }, - "user_id": { - Description: "The ID of the user to act as an approver", - Type: pluginsdk.TypeString, - Required: true, - ConflictsWith: []string{"approvers.0.group_id"}, - ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), - }, + "require_approval": { + Description: "Whether an approval is required for activation", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, - "group_id": { - Description: "The ID of the group to act as an approver", - Type: pluginsdk.TypeString, - Required: true, - ConflictsWith: []string{"approvers.0.user_id"}, - ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + "approval_stages": { + Description: "The approval stages for the activation", + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "primary_approvers": { + Description: "The IDs of the users or groups who can approve the activation", + Type: pluginsdk.TypeList, + Required: true, + MinItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "description": { + Description: "The description of the approver", + Type: pluginsdk.TypeString, + Required: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + }, + + "object_id": { + Description: "The ID of the useror group to act as an approver", + Type: pluginsdk.TypeString, + Required: true, + ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + }, + + "object_type": { + Description: "The type of the object to act as an approver", + Type: pluginsdk.TypeString, + Required: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"user", "group"}, false)), + }, + }, }, }, }, }, }, - }, - "require_conditional_access_authentication_context": { - Description: "Whether a conditional access context is required during activation", - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - ConflictsWith: []string{"activation.0.require_multifactor_authentication"}, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), - }, + "require_conditional_access_authentication_context": { + Description: "Whether a conditional access context is required during activation", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ConflictsWith: []string{"activation_rules.0.require_multifactor_authentication"}, + ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + }, - "require_multifactor_authentication": { - Description: "Whether multi-factor authentication is required during activation", - Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, - ConflictsWith: []string{"activation.0.require_conditional_access"}, - }, + "require_multifactor_authentication": { + Description: "Whether multi-factor authentication is required during activation", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + ConflictsWith: []string{"activation_rules.0.require_conditional_access_authentication_context"}, + }, - "require_justification": { - Description: "Whether a justification is required during activation", - Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, - }, + "require_justification": { + Description: "Whether a justification is required during activation", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, - "require_ticket_info": { - Description: "Whether ticket information is required during activation", - Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, + "require_ticket_info": { + Description: "Whether ticket information is required during activation", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, }, }, }, "notification_rules": { Description: "The notification rules of the policy", - Type: pluginsdk.TypeMap, + Type: pluginsdk.TypeList, Optional: true, Computed: true, - Elem: map[string]*pluginsdk.Schema{ - "admin_notifications": { - Description: "The admin notifications on assignment", - Type: pluginsdk.TypeMap, - Optional: true, - Computed: true, - Elem: map[string]*pluginsdk.Schema{ - "eligible_assignments": { - Description: "The admin notifications for eligible assignments", - Type: pluginsdk.TypeMap, - Optional: true, - Computed: true, - Elem: map[string]*pluginsdk.Schema{ - "notification_level": { - Description: "What level of notifications are sent", - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), - }, - "default_recipients": { - Description: "Whether the default recipients are notified", - Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, - }, - "additional_recipients": { - Description: "The additional recipients to notify", + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "admin_notifications": { + Description: "The admin notifications on assignment", + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "eligible_assignments": { + Description: "The admin notifications for eligible assignments", Type: pluginsdk.TypeList, Optional: true, - Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "notification_level": { + Description: "What level of notifications are sent", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ + msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelAll, + msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelCritical, + }, false)), + }, + "default_recipients": { + Description: "Whether the default recipients are notified", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + "additional_recipients": { + Description: "The additional recipients to notify", + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + }, }, }, - }, - }, - "active_assignments": { - Description: "The admin notifications for active assignments", - Type: pluginsdk.TypeMap, - Optional: true, - Computed: true, - Elem: map[string]*pluginsdk.Schema{ - "notification_level": { - Description: "What level of notifications are sent", - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), - }, - "default_recipients": { - Description: "Whether the default recipients are notified", - Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, - }, - "additional_recipients": { - Description: "The additional recipients to notify", + "active_assignments": { + Description: "The admin notifications for active assignments", Type: pluginsdk.TypeList, Optional: true, - Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "notification_level": { + Description: "What level of notifications are sent", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ + msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelAll, + msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelCritical, + }, false)), + }, + "default_recipients": { + Description: "Whether the default recipients are notified", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + "additional_recipients": { + Description: "The additional recipients to notify", + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + }, }, }, - }, - }, - "activations": { - Description: "The admin notifications for role activation", - Type: pluginsdk.TypeMap, - Optional: true, - Computed: true, - Elem: map[string]*pluginsdk.Schema{ - "notification_level": { - Description: "What level of notifications are sent", - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), - }, - "default_recipients": { - Description: "Whether the default recipients are notified", - Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, - }, - "additional_recipients": { - Description: "The additional recipients to notify", + "activations": { + Description: "The admin notifications for role activation", Type: pluginsdk.TypeList, Optional: true, - Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "notification_level": { + Description: "What level of notifications are sent", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ + msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelAll, + msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelCritical, + }, false)), + }, + "default_recipients": { + Description: "Whether the default recipients are notified", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + "additional_recipients": { + Description: "The additional recipients to notify", + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + }, }, }, }, }, }, - }, - "approver_notifications": { - Description: "The admin notifications on assignment", - Type: pluginsdk.TypeMap, - Optional: true, - Computed: true, - Elem: map[string]*pluginsdk.Schema{ - "eligible_assignments": { - Description: "The admin notifications for eligible assignments", - Type: pluginsdk.TypeMap, - Optional: true, - Computed: true, - Elem: map[string]*pluginsdk.Schema{ - "notification_level": { - Description: "What level of notifications are sent", - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), - }, - "default_recipients": { - Description: "Whether the default recipients are notified", - Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, - }, - "additional_recipients": { - Description: "The additional recipients to notify", + "approver_notifications": { + Description: "The admin notifications on assignment", + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "eligible_assignments": { + Description: "The admin notifications for eligible assignments", Type: pluginsdk.TypeList, Optional: true, - Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "notification_level": { + Description: "What level of notifications are sent", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ + msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelAll, + msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelCritical, + }, false)), + }, + "default_recipients": { + Description: "Whether the default recipients are notified", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + "additional_recipients": { + Description: "The additional recipients to notify", + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + }, }, }, - }, - }, - "active_assignments": { - Description: "The admin notifications for active assignments", - Type: pluginsdk.TypeMap, - Optional: true, - Computed: true, - Elem: map[string]*pluginsdk.Schema{ - "notification_level": { - Description: "What level of notifications are sent", - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), - }, - "default_recipients": { - Description: "Whether the default recipients are notified", - Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, - }, - "additional_recipients": { - Description: "The additional recipients to notify", + "active_assignments": { + Description: "The admin notifications for active assignments", Type: pluginsdk.TypeList, Optional: true, - Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "notification_level": { + Description: "What level of notifications are sent", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ + msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelAll, + msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelCritical, + }, false)), + }, + "default_recipients": { + Description: "Whether the default recipients are notified", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + "additional_recipients": { + Description: "The additional recipients to notify", + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + }, }, }, - }, - }, - "activations": { - Description: "The admin notifications for role activation", - Type: pluginsdk.TypeMap, - Optional: true, - Computed: true, - Elem: map[string]*pluginsdk.Schema{ - "notification_level": { - Description: "What level of notifications are sent", - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), - }, - "default_recipients": { - Description: "Whether the default recipients are notified", - Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, - }, - "additional_recipients": { - Description: "The additional recipients to notify", + "activations": { + Description: "The admin notifications for role activation", Type: pluginsdk.TypeList, Optional: true, - Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "notification_level": { + Description: "What level of notifications are sent", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ + msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelAll, + msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelCritical, + }, false)), + }, + "default_recipients": { + Description: "Whether the default recipients are notified", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + "additional_recipients": { + Description: "The additional recipients to notify", + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + }, }, }, }, }, }, - }, - "assignee_notifications": { - Description: "The admin notifications on assignment", - Type: pluginsdk.TypeMap, - Optional: true, - Computed: true, - Elem: map[string]*pluginsdk.Schema{ - "eligible_assignments": { - Description: "The admin notifications for eligible assignments", - Type: pluginsdk.TypeMap, - Optional: true, - Computed: true, - Elem: map[string]*pluginsdk.Schema{ - "notification_level": { - Description: "What level of notifications are sent", - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), - }, - "default_recipients": { - Description: "Whether the default recipients are notified", - Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, - }, - "additional_recipients": { - Description: "The additional recipients to notify", + "assignee_notifications": { + Description: "The admin notifications on assignment", + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "eligible_assignments": { + Description: "The admin notifications for eligible assignments", Type: pluginsdk.TypeList, Optional: true, - Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "notification_level": { + Description: "What level of notifications are sent", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), + }, + "default_recipients": { + Description: "Whether the default recipients are notified", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + "additional_recipients": { + Description: "The additional recipients to notify", + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + }, }, }, - }, - }, - "active_assignments": { - Description: "The admin notifications for active assignments", - Type: pluginsdk.TypeMap, - Optional: true, - Computed: true, - Elem: map[string]*pluginsdk.Schema{ - "notification_level": { - Description: "What level of notifications are sent", - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), - }, - "default_recipients": { - Description: "Whether the default recipients are notified", - Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, - }, - "additional_recipients": { - Description: "The additional recipients to notify", + "active_assignments": { + Description: "The admin notifications for active assignments", Type: pluginsdk.TypeList, Optional: true, - Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "notification_level": { + Description: "What level of notifications are sent", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), + }, + "default_recipients": { + Description: "Whether the default recipients are notified", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + "additional_recipients": { + Description: "The additional recipients to notify", + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + }, }, }, - }, - }, - "activations": { - Description: "The admin notifications for role activation", - Type: pluginsdk.TypeMap, - Optional: true, - Computed: true, - Elem: map[string]*pluginsdk.Schema{ - "notification_level": { - Description: "What level of notifications are sent", - Type: pluginsdk.TypeString, - Optional: true, - Computed: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), - }, - "default_recipients": { - Description: "Whether the default recipients are notified", - Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, - }, - "additional_recipients": { - Description: "The additional recipients to notify", + "activations": { + Description: "The admin notifications for role activation", Type: pluginsdk.TypeList, Optional: true, - Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "notification_level": { + Description: "What level of notifications are sent", + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), + }, + "default_recipients": { + Description: "Whether the default recipients are notified", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + "additional_recipients": { + Description: "The additional recipients to notify", + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + }, }, }, }, @@ -584,17 +648,15 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { func (r RoleManagementPolicyResource) Attributes() map[string]*pluginsdk.Schema { return map[string]*pluginsdk.Schema{ "display_name": { - Description: "The display name of the policy", - Type: pluginsdk.TypeString, - Optional: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + Description: "The display name of the policy", + Type: pluginsdk.TypeString, + Computed: true, }, "description": { - Description: "Description of the policy", - Type: pluginsdk.TypeString, - Optional: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + Description: "Description of the policy", + Type: pluginsdk.TypeString, + Computed: true, }, } } @@ -711,12 +773,14 @@ func (r RoleManagementPolicyResource) Read() sdk.ResourceFunc { case *approver.ODataType == "#microsoft.graph.singleUser": primaryApprovers = append(primaryApprovers, Approver{ Description: *approver.Description, - UserId: *approver.ID, + ObjectId: *approver.ID, + ObjectType: "user", }) case *approver.ODataType == "#microsoft.graph.groupMembers": primaryApprovers = append(primaryApprovers, Approver{ Description: *approver.Description, - GroupId: *approver.ID, + ObjectId: *approver.ID, + ObjectType: "group", }) default: return fmt.Errorf("unknown approver type: %s", approver.ODataType) @@ -903,16 +967,16 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie for _, stage := range model.ActivationRules.ApprovalStages { primaryApprovers := make([]msgraph.UserSet, 0) for _, approver := range stage.PrimaryApprovers { - if approver.UserId != "" { + if approver.ObjectType == "user" { primaryApprovers = append(primaryApprovers, msgraph.UserSet{ ODataType: pointer.To("#microsoft.graph.singleUser"), - ID: &approver.UserId, + ID: &approver.ObjectId, Description: &approver.Description, }) - } else if approver.GroupId != "" { + } else if approver.ObjectType == "group" { primaryApprovers = append(primaryApprovers, msgraph.UserSet{ ODataType: pointer.To("#microsoft.graph.groupMembers"), - ID: &approver.GroupId, + ID: &approver.ObjectId, Description: &approver.Description, }) } else { From bb7437805db1d0dca01f7c6bfb780b33b4511ad9 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Mon, 4 Mar 2024 23:46:54 +1300 Subject: [PATCH 13/55] Fix Create function --- .../policies/group_role_management_policy_resource.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index 6312a34f56..fdd97a0ab5 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -670,12 +670,12 @@ func (r RoleManagementPolicyResource) Create() sdk.ResourceFunc { // Fetch the existing policy, as they already exist policies, _, err := assignmentClient.List(ctx, odata.Query{ - Filter: fmt.Sprintf("scopeId eq '%s' and scopeType eq 'Group'", metadata.ResourceData.Get("object_id").(string)), + Filter: fmt.Sprintf("scopeId eq '%s' and scopeType eq 'Group' and roleDefinitionId eq '%s'", metadata.ResourceData.Get("object_id").(string), metadata.ResourceData.Get("assignment_type").(string)), }) if err != nil { return fmt.Errorf("Could not list existing policy, %+v", err) } - if len(*policies) != 0 { + if len(*policies) != 1 { return fmt.Errorf("Got the wrong number of policies, expected 1, got %d", len(*policies)) } From 7ddb94efbb17948f02eb73bc11009128b5e0d387 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Mon, 4 Mar 2024 23:53:56 +1300 Subject: [PATCH 14/55] Fix ID issues --- .../policies/group_role_management_policy_resource.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index fdd97a0ab5..cc182e3fd1 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -679,11 +679,12 @@ func (r RoleManagementPolicyResource) Create() sdk.ResourceFunc { return fmt.Errorf("Got the wrong number of policies, expected 1, got %d", len(*policies)) } - id, err := parse.ParseRoleManagementPolicyID(*(*policies)[0].ID) + assignmentId, err := parse.ParseRoleManagementPolicyAssignmentID(*(*policies)[0].ID) if err != nil { - return fmt.Errorf("Could not parse policy ID, %+v", err) + return fmt.Errorf("Could not parse policy assignment ID, %+v", err) } + id := parse.NewRoleManagementPolicyID(assignmentId.ScopeType, assignmentId.ScopeId, assignmentId.PolicyId) metadata.SetID(id) policy, _, err := policyClient.Get(ctx, id.ID()) @@ -1099,6 +1100,7 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie } return &msgraph.UnifiedRoleManagementPolicy{ + ID: policy.ID, Rules: pointer.To(returnRules), }, nil } From 07cc7c25ba5c72f0896ecfac8007837431a706c9 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Tue, 5 Mar 2024 09:10:09 +1300 Subject: [PATCH 15/55] Add parsing for role policies --- .../policies/parse/role_management_policy.go | 60 ++++++++++++++++ .../role_management_policy_assignment.go | 71 +++++++++++++++++++ .../parse/role_management_policy_rule.go | 37 ++++++++++ 3 files changed, 168 insertions(+) create mode 100644 internal/services/policies/parse/role_management_policy.go create mode 100644 internal/services/policies/parse/role_management_policy_assignment.go create mode 100644 internal/services/policies/parse/role_management_policy_rule.go diff --git a/internal/services/policies/parse/role_management_policy.go b/internal/services/policies/parse/role_management_policy.go new file mode 100644 index 0000000000..448fd54a8c --- /dev/null +++ b/internal/services/policies/parse/role_management_policy.go @@ -0,0 +1,60 @@ +package parse + +import ( + "fmt" + "strings" + + "github.com/hashicorp/terraform-provider-azuread/internal/tf/validation" + "github.com/manicminer/hamilton/msgraph" +) + +type RoleManagementPolicyId struct { + PolicyId string + ScopeId string + ScopeType string +} + +func NewRoleManagementPolicyID(scopeType, scopeId, policyId string) *RoleManagementPolicyId { + return &RoleManagementPolicyId{ + ScopeType: scopeType, + ScopeId: scopeId, + PolicyId: policyId, + } +} + +func ParseRoleManagementPolicyID(input string) (*RoleManagementPolicyId, error) { + parts := strings.Split(input, "_") + if len(parts) != 3 { + return nil, fmt.Errorf("parsing RoleManagementPolicyId: invalid format") + } + + id := RoleManagementPolicyId{ + ScopeType: parts[0], + ScopeId: parts[1], + PolicyId: parts[2], + } + + if _, err := validation.IsUUID(id.ScopeId, "ScopeId"); len(err) > 0 { + return nil, fmt.Errorf("parsing RoleManagementPolicyId: %+v", err) + } + + if _, err := validation.IsUUID(id.PolicyId, "PolicyId"); len(err) > 0 { + return nil, fmt.Errorf("parsing RoleManagementPolicyId: %+v", err) + } + + if id.ScopeType != msgraph.UnifiedRoleManagementPolicyScopeDirectory && + id.ScopeType != msgraph.UnifiedRoleManagementPolicyScopeDirectoryRole && + id.ScopeType != msgraph.UnifiedRoleManagementPolicyScopeGroup { + return nil, fmt.Errorf("parsing RoleManagementPolicyId: invalid ScopeType") + } + + return &id, nil +} + +func (id *RoleManagementPolicyId) ID() string { + return fmt.Sprintf("%s_%s_%s", id.ScopeType, id.ScopeId, id.PolicyId) +} + +func (id *RoleManagementPolicyId) String() string { + return fmt.Sprintf("Role Management Policy Assignment ID: %s_%s_%s", id.ScopeType, id.ScopeId, id.PolicyId) +} diff --git a/internal/services/policies/parse/role_management_policy_assignment.go b/internal/services/policies/parse/role_management_policy_assignment.go new file mode 100644 index 0000000000..f735e1d30d --- /dev/null +++ b/internal/services/policies/parse/role_management_policy_assignment.go @@ -0,0 +1,71 @@ +package parse + +import ( + "fmt" + "strings" + + "github.com/hashicorp/terraform-provider-azuread/internal/tf/validation" + "github.com/manicminer/hamilton/msgraph" +) + +type RoleManagementPolicyAssignmentId struct { + PolicyId string + RoleDefinitionId string + ScopeId string + ScopeType string +} + +func NewRoleManagementPolicyAssignmentID(scopeType, scopeId, policyId, roleDefinitionId string) *RoleManagementPolicyAssignmentId { + return &RoleManagementPolicyAssignmentId{ + ScopeType: scopeType, + ScopeId: scopeId, + PolicyId: policyId, + RoleDefinitionId: roleDefinitionId, + } +} + +func ParseRoleManagementPolicyAssignmentID(input string) (*RoleManagementPolicyAssignmentId, error) { + parts := strings.Split(input, "_") + if len(parts) != 4 { + return nil, fmt.Errorf("parsing RoleManagementPolicyAssignmentId: invalid format") + } + + id := RoleManagementPolicyAssignmentId{ + ScopeType: parts[0], + ScopeId: parts[1], + PolicyId: parts[2], + RoleDefinitionId: parts[3], + } + + if _, err := validation.IsUUID(id.ScopeId, "ScopeId"); len(err) > 0 { + return nil, fmt.Errorf("parsing RoleManagementPolicyAssignmentId: %+v", err) + } + + if _, err := validation.IsUUID(id.PolicyId, "PolicyId"); len(err) > 0 { + return nil, fmt.Errorf("parsing RoleManagementPolicyAssignmentId: %+v", err) + } + + if id.ScopeType == msgraph.UnifiedRoleManagementPolicyScopeDirectory || + id.ScopeType == msgraph.UnifiedRoleManagementPolicyScopeDirectoryRole { + if _, err := validation.IsUUID(id.RoleDefinitionId, "RoleDefinitionId"); len(err) > 0 { + return nil, fmt.Errorf("parsing RoleManagementPolicyAssignmentId: %+v", err) + } + } else if id.ScopeType == msgraph.UnifiedRoleManagementPolicyScopeGroup { + if id.RoleDefinitionId != msgraph.PrivilegedAccessGroupRelationshipMember && + id.RoleDefinitionId != msgraph.PrivilegedAccessGroupRelationshipOwner { + return nil, fmt.Errorf("parsing RoleManagementPolicyAssignmentId: invalid RoleDefinitionId") + } + } else { + return nil, fmt.Errorf("parsing RoleManagementPolicyAssignmentId: invalid ScopeType") + } + + return &id, nil +} + +func (id *RoleManagementPolicyAssignmentId) ID() string { + return fmt.Sprintf("%s_%s_%s_%s", id.ScopeType, id.ScopeId, id.PolicyId, id.RoleDefinitionId) +} + +func (id *RoleManagementPolicyAssignmentId) String() string { + return fmt.Sprintf("Role Management Policy Assignment ID: %s_%s_%s_%s", id.ScopeType, id.ScopeId, id.PolicyId, id.RoleDefinitionId) +} diff --git a/internal/services/policies/parse/role_management_policy_rule.go b/internal/services/policies/parse/role_management_policy_rule.go new file mode 100644 index 0000000000..bc49c67d35 --- /dev/null +++ b/internal/services/policies/parse/role_management_policy_rule.go @@ -0,0 +1,37 @@ +package parse + +import ( + "fmt" + "strings" +) + +type RoleManagementPolicyRuleId struct { + RuleId string +} + +func NewRoleManagementPolicyRuleID(ruleId string) *RoleManagementPolicyRuleId { + return &RoleManagementPolicyRuleId{ + RuleId: ruleId, + } +} + +func ParseRoleManagementPolicyRuleID(input string) (*RoleManagementPolicyRuleId, error) { + parts := strings.Split(input, "_") + if len(parts) == 3 && parts[0] == "Notification" { + return nil, fmt.Errorf("parsing RoleManagementPolicyRuleId: invalid format") + } else if len(parts) == 4 && parts[0] != "Notification" { + return nil, fmt.Errorf("parsing RoleManagementPolicyRuleId: invalid format") + } + + return &RoleManagementPolicyRuleId{ + RuleId: input, + }, nil +} + +func (id *RoleManagementPolicyRuleId) ID() string { + return id.RuleId +} + +func (id *RoleManagementPolicyRuleId) String() string { + return fmt.Sprintf("Role Management Policy Assignment ID: %s", id.RuleId) +} From f163e0426eef186d9206f437ab4c2adbd566ebb3 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Tue, 5 Mar 2024 09:41:35 +1300 Subject: [PATCH 16/55] Finish migrating settings into blocks --- .../group_role_management_policy_resource.go | 309 +++++++++--------- 1 file changed, 151 insertions(+), 158 deletions(-) diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index cc182e3fd1..a8801da6c4 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -22,10 +22,10 @@ type RoleManagementPolicyModel struct { DisplayName string `tfschema:"display_name"` GroupId string `tfschema:"object_id"` ScopeType msgraph.UnifiedRoleManagementPolicyScope `tfschema:"assignment_type"` - ActiveAssignmentRules AssignmentRules `tfschema:"active_assignment_rules"` - EligbleAssignmentRules AssignmentRules `tfschema:"eligible_assignment_rules"` - ActivationRules ActivationRules `tfschema:"activation_rules"` - NotificationRules NotificationRules `tfschema:"notification_rules"` + ActiveAssignmentRules []AssignmentRules `tfschema:"active_assignment_rules"` + EligbleAssignmentRules []AssignmentRules `tfschema:"eligible_assignment_rules"` + ActivationRules []ActivationRules `tfschema:"activation_rules"` + NotificationRules []NotificationRules `tfschema:"notification_rules"` } type AssignmentRules struct { @@ -56,21 +56,21 @@ type Approver struct { } type NotificationRules struct { - AdminNotifications NotificationRule `tfschema:"admin_notifications"` - ApproverNotifications NotificationRule `tfschema:"approver_notifications"` - AssigneeNotifications NotificationRule `tfschema:"assignee_notifications"` + AdminNotifications []NotificationRule `tfschema:"admin_notifications"` + ApproverNotifications []NotificationRule `tfschema:"approver_notifications"` + AssigneeNotifications []NotificationRule `tfschema:"assignee_notifications"` } type NotificationRule struct { - EligibleAssignments NotificationSettings `tfschema:"eligible_assignments"` - ActiveAssignments NotificationSettings `tfschema:"active_assignments"` - Activations NotificationSettings `tfschema:"activations"` + EligibleAssignments []NotificationSettings `tfschema:"eligible_assignments"` + ActiveAssignments []NotificationSettings `tfschema:"active_assignments"` + Activations []NotificationSettings `tfschema:"activations"` } type NotificationSettings struct { - NotificationLevel string `tfschema:"notification_level"` - DefaultRecipients bool `tfschema:"default_recipients"` - AdditionalRecipients []string `tfschema:"additional_recipients"` + NotificationLevel msgraph.UnifiedRoleManagementPolicyRuleNotificationLevel `tfschema:"notification_level"` + DefaultRecipients bool `tfschema:"default_recipients"` + AdditionalRecipients []string `tfschema:"additional_recipients"` } type RoleManagementPolicyResource struct{} @@ -120,19 +120,15 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ "allow_permanent": { - Description: "Whether assignments can be permanent", - Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, - ConflictsWith: []string{"eligible_assignment_rules.0.expire_after"}, + Description: "Whether assignments can be permanent", + Type: pluginsdk.TypeBool, + Optional: true, }, "expire_after": { Description: "The duration after which assignments expire", Type: pluginsdk.TypeString, Optional: true, - Computed: true, - ConflictsWith: []string{"eligible_assignment_rules.0.allow_permanent"}, ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"P15D", "P1M", "P3M", "P6M", "P1Y"}, false)), }, }, @@ -148,19 +144,15 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ "allow_permanent": { - Description: "Whether assignments can be permanent", - Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, - ConflictsWith: []string{"active_assignment_rules.0.expire_after"}, + Description: "Whether assignments can be permanent", + Type: pluginsdk.TypeBool, + Optional: true, }, "expire_after": { Description: "The duration after which assignments expire", Type: pluginsdk.TypeString, Optional: true, - Computed: true, - ConflictsWith: []string{"active_assignment_rules.0.allow_permanent"}, ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"P15D", "P1M", "P3M", "P6M", "P1Y"}, false)), }, @@ -742,31 +734,31 @@ func (r RoleManagementPolicyResource) Read() sdk.ResourceFunc { for _, rule := range *result.Rules { switch *rule.ID { case "Expiration_Admin_Eligibility": - model.EligbleAssignmentRules.AllowPermanent = *rule.IsExpirationRequired - model.EligbleAssignmentRules.ExpireAfter = *rule.MaximumDuration + model.EligbleAssignmentRules[0].AllowPermanent = *rule.IsExpirationRequired + model.EligbleAssignmentRules[0].ExpireAfter = *rule.MaximumDuration case "Enablement_Admin_Assignment": - model.ActiveAssignmentRules.RequireMultiFactorAuth = false - model.ActiveAssignmentRules.RequireJustification = false + model.ActiveAssignmentRules[0].RequireMultiFactorAuth = false + model.ActiveAssignmentRules[0].RequireJustification = false for _, enabledRule := range *rule.EnabledRules { switch enabledRule { case "MultiFactorAuthentication": - model.ActiveAssignmentRules.RequireMultiFactorAuth = true + model.ActiveAssignmentRules[0].RequireMultiFactorAuth = true case "Justification": - model.ActiveAssignmentRules.RequireJustification = true + model.ActiveAssignmentRules[0].RequireJustification = true } } case "Expiration_Admin_Assignment": - model.ActiveAssignmentRules.AllowPermanent = *rule.IsExpirationRequired - model.ActiveAssignmentRules.ExpireAfter = *rule.MaximumDuration + model.ActiveAssignmentRules[0].AllowPermanent = *rule.IsExpirationRequired + model.ActiveAssignmentRules[0].ExpireAfter = *rule.MaximumDuration case "Expiration_EndUser_Assignment": - model.ActivationRules.MaximumDuration = *rule.MaximumDuration + model.ActivationRules[0].MaximumDuration = *rule.MaximumDuration case "Approval_EndUser_Assignment": - model.ActivationRules.RequireApproval = *rule.Setting.IsApprovalRequired - model.ActivationRules.ApprovalStages = make([]ApprovalStage, 0) + model.ActivationRules[0].RequireApproval = *rule.Setting.IsApprovalRequired + model.ActivationRules[0].ApprovalStages = make([]ApprovalStage, 0) for _, stage := range *rule.Setting.ApprovalStages { primaryApprovers := make([]Approver, 0) for _, approver := range *stage.PrimaryApprovers { @@ -784,76 +776,78 @@ func (r RoleManagementPolicyResource) Read() sdk.ResourceFunc { ObjectType: "group", }) default: - return fmt.Errorf("unknown approver type: %s", approver.ODataType) + return fmt.Errorf("unknown approver type: %s", *approver.ODataType) } - model.ActivationRules.ApprovalStages = append(model.ActivationRules.ApprovalStages, ApprovalStage{ + model.ActivationRules[0].ApprovalStages = append(model.ActivationRules[0].ApprovalStages, ApprovalStage{ PrimaryApprovers: primaryApprovers, }) } } case "AuthenticationContext_EndUser_Assignment": - model.ActivationRules.RequireConditionalAccessContext = *rule.ClaimValue + if rule.ClaimValue != nil && *rule.ClaimValue != "" { + model.ActivationRules[0].RequireConditionalAccessContext = *rule.ClaimValue + } case "Enablement_EndUser_Assignment": - model.ActivationRules.RequireMultiFactorAuth = false - model.ActivationRules.RequireJustification = false - model.ActivationRules.RequireTicketInfo = false + model.ActivationRules[0].RequireMultiFactorAuth = false + model.ActivationRules[0].RequireJustification = false + model.ActivationRules[0].RequireTicketInfo = false for _, enabledRule := range *rule.EnabledRules { switch enabledRule { case "MultiFactorAuthentication": - model.ActivationRules.RequireMultiFactorAuth = true + model.ActivationRules[0].RequireMultiFactorAuth = true case "Justification": - model.ActivationRules.RequireJustification = true + model.ActivationRules[0].RequireJustification = true case "Ticketing": - model.ActivationRules.RequireTicketInfo = true + model.ActivationRules[0].RequireTicketInfo = true } } case "Notification_Admin_Admin_Eligibility": - model.NotificationRules.AdminNotifications.EligibleAssignments.NotificationLevel = rule.NotificationLevel - model.NotificationRules.AdminNotifications.EligibleAssignments.DefaultRecipients = *rule.IsDefaultRecipientsEnabled - model.NotificationRules.AdminNotifications.EligibleAssignments.AdditionalRecipients = *rule.NotificationRecipients + model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].NotificationLevel = rule.NotificationLevel + model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled + model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].AdditionalRecipients = *rule.NotificationRecipients case "Notification_Admin_Admin_Assignment": - model.NotificationRules.AdminNotifications.ActiveAssignments.NotificationLevel = rule.NotificationLevel - model.NotificationRules.AdminNotifications.ActiveAssignments.DefaultRecipients = *rule.IsDefaultRecipientsEnabled - model.NotificationRules.AdminNotifications.ActiveAssignments.AdditionalRecipients = *rule.NotificationRecipients + model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0].NotificationLevel = rule.NotificationLevel + model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled + model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0].AdditionalRecipients = *rule.NotificationRecipients case "Notification_Admin_EndUser_Assignment": - model.NotificationRules.AdminNotifications.Activations.NotificationLevel = rule.NotificationLevel - model.NotificationRules.AdminNotifications.Activations.DefaultRecipients = *rule.IsDefaultRecipientsEnabled - model.NotificationRules.AdminNotifications.Activations.AdditionalRecipients = *rule.NotificationRecipients + model.NotificationRules[0].AdminNotifications[0].Activations[0].NotificationLevel = rule.NotificationLevel + model.NotificationRules[0].AdminNotifications[0].Activations[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled + model.NotificationRules[0].AdminNotifications[0].Activations[0].AdditionalRecipients = *rule.NotificationRecipients case "Notification_Approver_Admin_Eligibility": - model.NotificationRules.ApproverNotifications.EligibleAssignments.NotificationLevel = rule.NotificationLevel - model.NotificationRules.ApproverNotifications.EligibleAssignments.DefaultRecipients = *rule.IsDefaultRecipientsEnabled - model.NotificationRules.ApproverNotifications.EligibleAssignments.AdditionalRecipients = *rule.NotificationRecipients + model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments[0].NotificationLevel = rule.NotificationLevel + model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled + model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments[0].AdditionalRecipients = *rule.NotificationRecipients case "Notification_Approver_Admin_Assignment": - model.NotificationRules.ApproverNotifications.ActiveAssignments.NotificationLevel = rule.NotificationLevel - model.NotificationRules.ApproverNotifications.ActiveAssignments.DefaultRecipients = *rule.IsDefaultRecipientsEnabled - model.NotificationRules.ApproverNotifications.ActiveAssignments.AdditionalRecipients = *rule.NotificationRecipients + model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0].NotificationLevel = rule.NotificationLevel + model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled + model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0].AdditionalRecipients = *rule.NotificationRecipients case "Notification_Approver_EndUser_Assignment": - model.NotificationRules.ApproverNotifications.Activations.NotificationLevel = rule.NotificationLevel - model.NotificationRules.ApproverNotifications.Activations.DefaultRecipients = *rule.IsDefaultRecipientsEnabled - model.NotificationRules.ApproverNotifications.Activations.AdditionalRecipients = *rule.NotificationRecipients + model.NotificationRules[0].ApproverNotifications[0].Activations[0].NotificationLevel = rule.NotificationLevel + model.NotificationRules[0].ApproverNotifications[0].Activations[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled + model.NotificationRules[0].ApproverNotifications[0].Activations[0].AdditionalRecipients = *rule.NotificationRecipients case "Notification_Requestor_Admin_Eligibility": - model.NotificationRules.AssigneeNotifications.EligibleAssignments.NotificationLevel = rule.NotificationLevel - model.NotificationRules.AssigneeNotifications.EligibleAssignments.DefaultRecipients = *rule.IsDefaultRecipientsEnabled - model.NotificationRules.AssigneeNotifications.EligibleAssignments.AdditionalRecipients = *rule.NotificationRecipients + model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0].NotificationLevel = rule.NotificationLevel + model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled + model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0].AdditionalRecipients = *rule.NotificationRecipients case "Notification_Requestor_Admin_Assignment": - model.NotificationRules.AssigneeNotifications.ActiveAssignments.NotificationLevel = rule.NotificationLevel - model.NotificationRules.AssigneeNotifications.ActiveAssignments.DefaultRecipients = *rule.IsDefaultRecipientsEnabled - model.NotificationRules.AssigneeNotifications.ActiveAssignments.AdditionalRecipients = *rule.NotificationRecipients + model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0].NotificationLevel = rule.NotificationLevel + model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled + model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0].AdditionalRecipients = *rule.NotificationRecipients case "Notification_Requestor_EndUser_Assignment": - model.NotificationRules.AssigneeNotifications.Activations.NotificationLevel = rule.NotificationLevel - model.NotificationRules.AssigneeNotifications.Activations.DefaultRecipients = *rule.IsDefaultRecipientsEnabled - model.NotificationRules.AssigneeNotifications.Activations.AdditionalRecipients = *rule.NotificationRecipients + model.NotificationRules[0].AssigneeNotifications[0].Activations[0].NotificationLevel = rule.NotificationLevel + model.NotificationRules[0].AssigneeNotifications[0].Activations[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled + model.NotificationRules[0].AssigneeNotifications[0].Activations[0].AdditionalRecipients = *rule.NotificationRecipients } } @@ -924,48 +918,52 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie for _, rule := range *policy.Rules { policyRules[*rule.ID] = rule } + updatedRules := make([]msgraph.UnifiedRoleManagementPolicyRule, 0) if metadata.ResourceData.HasChange("eligible_assignment_rules") { - rule := policyRules["Expiration_Admin_Eligibility"] - rule.IsExpirationRequired = pointer.To(model.EligbleAssignmentRules.AllowPermanent) - rule.MaximumDuration = pointer.To(model.EligbleAssignmentRules.ExpireAfter) - policyRules["Expiration_Admin_Eligibility"] = rule + rule := msgraph.UnifiedRoleManagementPolicyRule{ + ID: pointer.To("Expiration_Admin_Eligibility"), + ODataType: pointer.To("#microsoft.graph.unifiedRoleManagementPolicyExpirationRule"), + IsExpirationRequired: pointer.To(model.EligbleAssignmentRules[0].AllowPermanent), + MaximumDuration: pointer.To(model.EligbleAssignmentRules[0].ExpireAfter), + } + updatedRules = append(updatedRules, rule) } - if metadata.ResourceData.HasChange("active_assignment_rules.require_multifactor_authentication") || - metadata.ResourceData.HasChange("active_assignment_rules.require_justification") { + if metadata.ResourceData.HasChange("active_assignment_rules.0.require_multifactor_authentication") || + metadata.ResourceData.HasChange("active_assignment_rules.0.require_justification") { enabledRules := make([]string, 0) - if model.EligbleAssignmentRules.RequireMultiFactorAuth { + if model.ActiveAssignmentRules[0].RequireMultiFactorAuth { enabledRules = append(enabledRules, "MultiFactorAuthentication") } - if model.EligbleAssignmentRules.RequireJustification { + if model.ActiveAssignmentRules[0].RequireJustification { enabledRules = append(enabledRules, "Justification") } rule := policyRules["Enablement_Admin_Assignment"] rule.EnabledRules = pointer.To(enabledRules) - policyRules["Enablement_Admin_Assignment"] = rule + updatedRules = append(updatedRules, rule) } - if metadata.ResourceData.HasChange("active_assignment_rules.allow_permanent") || - metadata.ResourceData.HasChange("active_assignment_rules.expire_after") { + if metadata.ResourceData.HasChange("active_assignment_rules.0.allow_permanent") || + metadata.ResourceData.HasChange("active_assignment_rules.0.expire_after") { rule := policyRules["Expiration_Admin_Assignment"] - rule.IsExpirationRequired = pointer.To(model.EligbleAssignmentRules.AllowPermanent) - rule.MaximumDuration = pointer.To(model.EligbleAssignmentRules.ExpireAfter) - policyRules["Expiration_Admin_Assignment"] = rule + rule.IsExpirationRequired = pointer.To(model.ActiveAssignmentRules[0].AllowPermanent) + rule.MaximumDuration = pointer.To(model.ActiveAssignmentRules[0].ExpireAfter) + updatedRules = append(updatedRules, rule) } - if metadata.ResourceData.HasChange("activation_rules.maximum_duration") { + if metadata.ResourceData.HasChange("activation_rules.0.maximum_duration") { rule := policyRules["Expiration_EndUser_Assignment"] - rule.MaximumDuration = pointer.To(model.ActivationRules.MaximumDuration) - policyRules["Expiration_EndUser_Assignment"] = rule + rule.MaximumDuration = pointer.To(model.ActivationRules[0].MaximumDuration) + updatedRules = append(updatedRules, rule) } - if metadata.ResourceData.HasChange("activation_rules.require_approval") || - metadata.ResourceData.HasChange("activation_rules.approval_stages") { + if metadata.ResourceData.HasChange("activation_rules.0.require_approval") || + metadata.ResourceData.HasChange("activation_rules.0.approval_stages") { rule := policyRules["Approval_EndUser_Assignment"] approvalStages := make([]msgraph.ApprovalStage, 0) - for _, stage := range model.ActivationRules.ApprovalStages { + for _, stage := range model.ActivationRules[0].ApprovalStages { primaryApprovers := make([]msgraph.UserSet, 0) for _, approver := range stage.PrimaryApprovers { if approver.ObjectType == "user" { @@ -990,117 +988,112 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie }) } - rule.Setting.IsApprovalRequired = pointer.To(model.ActivationRules.RequireApproval) + rule.Setting.IsApprovalRequired = pointer.To(model.ActivationRules[0].RequireApproval) rule.Setting.ApprovalStages = &approvalStages - policyRules["Approval_EndUser_Assignment"] = rule + updatedRules = append(updatedRules, rule) } - if metadata.ResourceData.HasChange("activation_rules.require_conditional_access_authentication_context") { + if metadata.ResourceData.HasChange("activation_rules.0.require_conditional_access_authentication_context") { rule := policyRules["AuthenticationContext_EndUser_Assignment"] - _, set := metadata.ResourceData.GetOk("activation_rules.require_conditional_access_authentication_context") + _, set := metadata.ResourceData.GetOk("activation_rules.0.require_conditional_access_authentication_context") rule.IsEnabled = pointer.To(set) - rule.ClaimValue = pointer.To(model.ActivationRules.RequireConditionalAccessContext) - policyRules["AuthenticationContext_EndUser_Assignment"] = rule + rule.ClaimValue = pointer.To(model.ActivationRules[0].RequireConditionalAccessContext) + updatedRules = append(updatedRules, rule) } - if metadata.ResourceData.HasChange("activation_rules.require_multifactor_authentication") || - metadata.ResourceData.HasChange("activation_rules.require_justification") || - metadata.ResourceData.HasChange("activation_rules.require_ticket_info") { + if metadata.ResourceData.HasChange("activation_rules.0.require_multifactor_authentication") || + metadata.ResourceData.HasChange("activation_rules.0.require_justification") || + metadata.ResourceData.HasChange("activation_rules.0.require_ticket_info") { enabledRules := make([]string, 0) - if model.ActivationRules.RequireMultiFactorAuth { + if model.ActivationRules[0].RequireMultiFactorAuth { enabledRules = append(enabledRules, "MultiFactorAuthentication") } - if model.ActivationRules.RequireJustification { + if model.ActivationRules[0].RequireJustification { enabledRules = append(enabledRules, "Justification") } - if model.ActivationRules.RequireTicketInfo { + if model.ActivationRules[0].RequireTicketInfo { enabledRules = append(enabledRules, "Ticketing") } rule := policyRules["Enablement_EndUser_Assignment"] rule.EnabledRules = pointer.To(enabledRules) - policyRules["Enablement_EndUser_Assignment"] = rule + updatedRules = append(updatedRules, rule) } - if metadata.ResourceData.HasChange("notification_rules.admin_notifications.eligible_assignments") { + if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.eligible_assignments") { rule := policyRules["Notification_Admin_Admin_Eligibility"] - rule.NotificationLevel = model.NotificationRules.AdminNotifications.EligibleAssignments.NotificationLevel - rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules.AdminNotifications.EligibleAssignments.DefaultRecipients) - rule.NotificationRecipients = &model.NotificationRules.AdminNotifications.EligibleAssignments.AdditionalRecipients - policyRules["Notification_Admin_Admin_Eligibility"] = rule + rule.NotificationLevel = model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].NotificationLevel + rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].DefaultRecipients) + rule.NotificationRecipients = &model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].AdditionalRecipients + updatedRules = append(updatedRules, rule) } - if metadata.ResourceData.HasChange("notification_rules.admin_notifications.active_assignments") { + if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.active_assignments") { rule := policyRules["Notification_Admin_Admin_Assignment"] - rule.NotificationLevel = model.NotificationRules.AdminNotifications.ActiveAssignments.NotificationLevel - rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules.AdminNotifications.ActiveAssignments.DefaultRecipients) - rule.NotificationRecipients = &model.NotificationRules.AdminNotifications.ActiveAssignments.AdditionalRecipients - policyRules["Notification_Admin_Admin_Assignment"] = rule + rule.NotificationLevel = model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0].NotificationLevel + rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0].DefaultRecipients) + rule.NotificationRecipients = &model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0].AdditionalRecipients + updatedRules = append(updatedRules, rule) } - if metadata.ResourceData.HasChange("notification_rules.admin_notifications.activations") { + if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.activations") { rule := policyRules["Notification_Admin_EndUser_Assignment"] - rule.NotificationLevel = model.NotificationRules.AdminNotifications.Activations.NotificationLevel - rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules.AdminNotifications.Activations.DefaultRecipients) - rule.NotificationRecipients = &model.NotificationRules.AdminNotifications.Activations.AdditionalRecipients - policyRules["Notification_Admin_EndUser_Assignment"] = rule + rule.NotificationLevel = model.NotificationRules[0].AdminNotifications[0].Activations[0].NotificationLevel + rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules[0].AdminNotifications[0].Activations[0].DefaultRecipients) + rule.NotificationRecipients = &model.NotificationRules[0].AdminNotifications[0].Activations[0].AdditionalRecipients + updatedRules = append(updatedRules, rule) } - if metadata.ResourceData.HasChange("notification_rules.approver_notifications.eligible_assignments") { + if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.eligible_assignments") { rule := policyRules["Notification_Approver_Admin_Eligibility"] - rule.NotificationLevel = model.NotificationRules.ApproverNotifications.EligibleAssignments.NotificationLevel - rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules.ApproverNotifications.EligibleAssignments.DefaultRecipients) - rule.NotificationRecipients = &model.NotificationRules.ApproverNotifications.EligibleAssignments.AdditionalRecipients - policyRules["Notification_Approver_Admin_Eligibility"] = rule + rule.NotificationLevel = model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments[0].NotificationLevel + rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments[0].DefaultRecipients) + rule.NotificationRecipients = &model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments[0].AdditionalRecipients + updatedRules = append(updatedRules, rule) } - if metadata.ResourceData.HasChange("notification_rules.approver_notifications.active_assignments") { + if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.active_assignments") { rule := policyRules["Notification_Approver_Admin_Assignment"] - rule.NotificationLevel = model.NotificationRules.ApproverNotifications.ActiveAssignments.NotificationLevel - rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules.ApproverNotifications.ActiveAssignments.DefaultRecipients) - rule.NotificationRecipients = &model.NotificationRules.ApproverNotifications.ActiveAssignments.AdditionalRecipients - policyRules["Notification_Approver_Admin_Assignment"] = rule + rule.NotificationLevel = model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0].NotificationLevel + rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0].DefaultRecipients) + rule.NotificationRecipients = &model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0].AdditionalRecipients + updatedRules = append(updatedRules, rule) } - if metadata.ResourceData.HasChange("notification_rules.approver_notifications.activations") { + if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.activations") { rule := policyRules["Notification_Approver_EndUser_Assignment"] - rule.NotificationLevel = model.NotificationRules.ApproverNotifications.Activations.NotificationLevel - rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules.ApproverNotifications.Activations.DefaultRecipients) - rule.NotificationRecipients = &model.NotificationRules.ApproverNotifications.Activations.AdditionalRecipients - policyRules["Notification_Approver_EndUser_Assignment"] = rule + rule.NotificationLevel = model.NotificationRules[0].ApproverNotifications[0].Activations[0].NotificationLevel + rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules[0].ApproverNotifications[0].Activations[0].DefaultRecipients) + rule.NotificationRecipients = &model.NotificationRules[0].ApproverNotifications[0].Activations[0].AdditionalRecipients + updatedRules = append(updatedRules, rule) } - if metadata.ResourceData.HasChange("notification_rules.assignee_notifications.eligible_assignments") { + if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.eligible_assignments") { rule := policyRules["Notification_Requestor_Admin_Eligibility"] - rule.NotificationLevel = model.NotificationRules.AssigneeNotifications.EligibleAssignments.NotificationLevel - rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules.AssigneeNotifications.EligibleAssignments.DefaultRecipients) - rule.NotificationRecipients = &model.NotificationRules.AssigneeNotifications.EligibleAssignments.AdditionalRecipients - policyRules["Notification_Requestor_Admin_Eligibility"] = rule + rule.NotificationLevel = model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0].NotificationLevel + rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0].DefaultRecipients) + rule.NotificationRecipients = &model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0].AdditionalRecipients + updatedRules = append(updatedRules, rule) } - if metadata.ResourceData.HasChange("notification_rules.assignee_notifications.active_assignments") { + if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.active_assignments") { rule := policyRules["Notification_Requestor_Admin_Assignment"] - rule.NotificationLevel = model.NotificationRules.AssigneeNotifications.ActiveAssignments.NotificationLevel - rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules.AssigneeNotifications.ActiveAssignments.DefaultRecipients) - rule.NotificationRecipients = &model.NotificationRules.AssigneeNotifications.ActiveAssignments.AdditionalRecipients - policyRules["Notification_Requestor_Admin_Assignment"] = rule + rule.NotificationLevel = model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0].NotificationLevel + rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0].DefaultRecipients) + rule.NotificationRecipients = &model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0].AdditionalRecipients + updatedRules = append(updatedRules, rule) } - if metadata.ResourceData.HasChange("notification_rules.assignee_notifications.activations") { + if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.activations") { rule := policyRules["Notification_Requestor_EndUser_Assignment"] - rule.NotificationLevel = model.NotificationRules.AssigneeNotifications.Activations.NotificationLevel - rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules.AssigneeNotifications.Activations.DefaultRecipients) - rule.NotificationRecipients = &model.NotificationRules.AssigneeNotifications.Activations.AdditionalRecipients - policyRules["Notification_Requestor_EndUser_Assignment"] = rule - } - - returnRules := make([]msgraph.UnifiedRoleManagementPolicyRule, 0) - for _, rule := range policyRules { - returnRules = append(returnRules, rule) + rule.NotificationLevel = model.NotificationRules[0].AssigneeNotifications[0].Activations[0].NotificationLevel + rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules[0].AssigneeNotifications[0].Activations[0].DefaultRecipients) + rule.NotificationRecipients = &model.NotificationRules[0].AssigneeNotifications[0].Activations[0].AdditionalRecipients + updatedRules = append(updatedRules, rule) } return &msgraph.UnifiedRoleManagementPolicy{ ID: policy.ID, - Rules: pointer.To(returnRules), + Rules: pointer.To(updatedRules), }, nil } From 08b6a3d298af91f702a7bf6a30dee06df85e1112 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Tue, 5 Mar 2024 10:29:23 +1300 Subject: [PATCH 17/55] Rename AllowPermanent to ExpirationRequired Better matches the underlying resource and means we don't need to negate the bool --- .../group_role_management_policy_resource.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index a8801da6c4..a47cdd76af 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -29,7 +29,7 @@ type RoleManagementPolicyModel struct { } type AssignmentRules struct { - AllowPermanent bool `tfschema:"allow_permanent"` + ExpirationRequired bool `tfschema:"expiration_required"` ExpireAfter string `tfschema:"expire_after"` RequireMultiFactorAuth bool `tfschema:"require_multifactor_authentication"` RequireJustification bool `tfschema:"require_justification"` @@ -119,8 +119,8 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { MaxItems: 1, Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ - "allow_permanent": { - Description: "Whether assignments can be permanent", + "expiration_required": { + Description: "Must the assignment have an expiry date", Type: pluginsdk.TypeBool, Optional: true, }, @@ -143,8 +143,8 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { MaxItems: 1, Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ - "allow_permanent": { - Description: "Whether assignments can be permanent", + "expiration_required": { + Description: "Must the assignment have an expiry date", Type: pluginsdk.TypeBool, Optional: true, }, @@ -734,7 +734,7 @@ func (r RoleManagementPolicyResource) Read() sdk.ResourceFunc { for _, rule := range *result.Rules { switch *rule.ID { case "Expiration_Admin_Eligibility": - model.EligbleAssignmentRules[0].AllowPermanent = *rule.IsExpirationRequired + model.EligbleAssignmentRules[0].ExpirationRequired = *rule.IsExpirationRequired model.EligbleAssignmentRules[0].ExpireAfter = *rule.MaximumDuration case "Enablement_Admin_Assignment": @@ -750,7 +750,7 @@ func (r RoleManagementPolicyResource) Read() sdk.ResourceFunc { } case "Expiration_Admin_Assignment": - model.ActiveAssignmentRules[0].AllowPermanent = *rule.IsExpirationRequired + model.ActiveAssignmentRules[0].ExpirationRequired = *rule.IsExpirationRequired model.ActiveAssignmentRules[0].ExpireAfter = *rule.MaximumDuration case "Expiration_EndUser_Assignment": From 1dbc70a2e8064d36ae0f89082e134e96b6d0becf Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Tue, 5 Mar 2024 10:29:42 +1300 Subject: [PATCH 18/55] Update policy build code to actually work --- .../group_role_management_policy_resource.go | 391 ++++++++++++++---- 1 file changed, 309 insertions(+), 82 deletions(-) diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index a47cdd76af..4592422dd8 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -129,7 +129,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { Description: "The duration after which assignments expire", Type: pluginsdk.TypeString, Optional: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"P15D", "P1M", "P3M", "P6M", "P1Y"}, false)), + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"P15D", "P30D", "P90D", "P180D", "P365D"}, false)), }, }, }, @@ -153,7 +153,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { Description: "The duration after which assignments expire", Type: pluginsdk.TypeString, Optional: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"P15D", "P1M", "P3M", "P6M", "P1Y"}, false)), + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"P15D", "P30D", "P90D", "P180D", "P365D"}, false)), }, "require_multifactor_authentication": { @@ -182,7 +182,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ "maximum_duration": { - Description: "The maximum duration an activation can be valid for", + Description: "The time after which the an activation can be valid for", Type: pluginsdk.TypeString, Optional: true, Computed: true, @@ -921,11 +921,22 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie updatedRules := make([]msgraph.UnifiedRoleManagementPolicyRule, 0) if metadata.ResourceData.HasChange("eligible_assignment_rules") { + expirationRequired := policyRules["Expiration_Admin_Eligibility"].IsExpirationRequired + maximumDuration := policyRules["Expiration_Admin_Eligibility"].MaximumDuration + + if metadata.ResourceData.HasChange("eligible_assignment_rules.0.expiration_required") { + expirationRequired = pointer.To(model.EligbleAssignmentRules[0].ExpirationRequired) + } + if metadata.ResourceData.HasChange("eligible_assignment_rules.0.expire_after") { + maximumDuration = pointer.To(model.EligbleAssignmentRules[0].ExpireAfter) + } + rule := msgraph.UnifiedRoleManagementPolicyRule{ - ID: pointer.To("Expiration_Admin_Eligibility"), - ODataType: pointer.To("#microsoft.graph.unifiedRoleManagementPolicyExpirationRule"), - IsExpirationRequired: pointer.To(model.EligbleAssignmentRules[0].AllowPermanent), - MaximumDuration: pointer.To(model.EligbleAssignmentRules[0].ExpireAfter), + ID: policyRules["Expiration_Admin_Eligibility"].ID, + ODataType: policyRules["Expiration_Admin_Eligibility"].ODataType, + Target: policyRules["Expiration_Admin_Eligibility"].Target, + IsExpirationRequired: expirationRequired, + MaximumDuration: maximumDuration, } updatedRules = append(updatedRules, rule) } @@ -940,64 +951,114 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie enabledRules = append(enabledRules, "Justification") } - rule := policyRules["Enablement_Admin_Assignment"] - rule.EnabledRules = pointer.To(enabledRules) + rule := msgraph.UnifiedRoleManagementPolicyRule{ + ID: policyRules["Enablement_Admin_Assignment"].ID, + ODataType: policyRules["Enablement_Admin_Assignment"].ODataType, + Target: policyRules["Enablement_Admin_Assignment"].Target, + EnabledRules: pointer.To(enabledRules), + } updatedRules = append(updatedRules, rule) } - if metadata.ResourceData.HasChange("active_assignment_rules.0.allow_permanent") || + if metadata.ResourceData.HasChange("active_assignment_rules.0.expiration_required") || metadata.ResourceData.HasChange("active_assignment_rules.0.expire_after") { - rule := policyRules["Expiration_Admin_Assignment"] - rule.IsExpirationRequired = pointer.To(model.ActiveAssignmentRules[0].AllowPermanent) - rule.MaximumDuration = pointer.To(model.ActiveAssignmentRules[0].ExpireAfter) + expirationRequired := policyRules["Expiration_Admin_Assignment"].IsExpirationRequired + maximumDuration := policyRules["Expiration_Admin_Assignment"].MaximumDuration + + if metadata.ResourceData.HasChange("active_assignment_rules.0.expiration_required") { + expirationRequired = pointer.To(model.EligbleAssignmentRules[0].ExpirationRequired) + } + if metadata.ResourceData.HasChange("active_assignment_rules.0.expire_after") { + maximumDuration = pointer.To(model.EligbleAssignmentRules[0].ExpireAfter) + } + + rule := msgraph.UnifiedRoleManagementPolicyRule{ + ID: policyRules["Expiration_Admin_Assignment"].ID, + ODataType: policyRules["Expiration_Admin_Assignment"].ODataType, + Target: policyRules["Expiration_Admin_Assignment"].Target, + IsExpirationRequired: expirationRequired, + MaximumDuration: maximumDuration, + } updatedRules = append(updatedRules, rule) } if metadata.ResourceData.HasChange("activation_rules.0.maximum_duration") { - rule := policyRules["Expiration_EndUser_Assignment"] - rule.MaximumDuration = pointer.To(model.ActivationRules[0].MaximumDuration) + rule := msgraph.UnifiedRoleManagementPolicyRule{ + ID: policyRules["Expiration_EndUser_Assignment"].ID, + ODataType: policyRules["Expiration_EndUser_Assignment"].ODataType, + Target: policyRules["Expiration_EndUser_Assignment"].Target, + MaximumDuration: pointer.To(model.EligbleAssignmentRules[0].ExpireAfter), + } updatedRules = append(updatedRules, rule) } if metadata.ResourceData.HasChange("activation_rules.0.require_approval") || metadata.ResourceData.HasChange("activation_rules.0.approval_stages") { - rule := policyRules["Approval_EndUser_Assignment"] - approvalStages := make([]msgraph.ApprovalStage, 0) - for _, stage := range model.ActivationRules[0].ApprovalStages { - primaryApprovers := make([]msgraph.UserSet, 0) - for _, approver := range stage.PrimaryApprovers { - if approver.ObjectType == "user" { - primaryApprovers = append(primaryApprovers, msgraph.UserSet{ - ODataType: pointer.To("#microsoft.graph.singleUser"), - ID: &approver.ObjectId, - Description: &approver.Description, - }) - } else if approver.ObjectType == "group" { - primaryApprovers = append(primaryApprovers, msgraph.UserSet{ - ODataType: pointer.To("#microsoft.graph.groupMembers"), - ID: &approver.ObjectId, - Description: &approver.Description, - }) - } else { - return nil, fmt.Errorf("either user_id or group_id must be set") + isApprovalRequired := policyRules["Approval_EndUser_Assignment"].Setting.IsApprovalRequired + var approvalStages []msgraph.ApprovalStage + if metadata.ResourceData.HasChange("activation_rules.0.require_approval") { + isApprovalRequired = pointer.To(model.ActivationRules[0].RequireApproval) + } + if metadata.ResourceData.HasChange("activation_rules.0.approval_stages") { + approvalStages = make([]msgraph.ApprovalStage, 0) + for _, stage := range model.ActivationRules[0].ApprovalStages { + primaryApprovers := make([]msgraph.UserSet, 0) + for _, approver := range stage.PrimaryApprovers { + if approver.ObjectType == "user" { + primaryApprovers = append(primaryApprovers, msgraph.UserSet{ + ODataType: pointer.To("#microsoft.graph.singleUser"), + ID: &approver.ObjectId, + Description: &approver.Description, + }) + } else if approver.ObjectType == "group" { + primaryApprovers = append(primaryApprovers, msgraph.UserSet{ + ODataType: pointer.To("#microsoft.graph.groupMembers"), + ID: &approver.ObjectId, + Description: &approver.Description, + }) + } else { + return nil, fmt.Errorf("either user_id or group_id must be set") + } } - } - approvalStages = append(approvalStages, msgraph.ApprovalStage{ - PrimaryApprovers: &primaryApprovers, - }) + approvalStages = append(approvalStages, msgraph.ApprovalStage{ + PrimaryApprovers: &primaryApprovers, + }) + } + } else { + approvalStages = *policyRules["Approval_EndUser_Assignment"].Setting.ApprovalStages } - rule.Setting.IsApprovalRequired = pointer.To(model.ActivationRules[0].RequireApproval) - rule.Setting.ApprovalStages = &approvalStages + rule := msgraph.UnifiedRoleManagementPolicyRule{ + ID: policyRules["Approval_EndUser_Assignment"].ID, + ODataType: policyRules["Approval_EndUser_Assignment"].ODataType, + Target: policyRules["Approval_EndUser_Assignment"].Target, + Setting: pointer.To(msgraph.ApprovalSettings{ + IsApprovalRequired: isApprovalRequired, + ApprovalStages: &approvalStages, + }), + } updatedRules = append(updatedRules, rule) } if metadata.ResourceData.HasChange("activation_rules.0.require_conditional_access_authentication_context") { - rule := policyRules["AuthenticationContext_EndUser_Assignment"] - _, set := metadata.ResourceData.GetOk("activation_rules.0.require_conditional_access_authentication_context") - rule.IsEnabled = pointer.To(set) - rule.ClaimValue = pointer.To(model.ActivationRules[0].RequireConditionalAccessContext) + isEnabled := policyRules["AuthenticationContext_EndUser_Assignment"].IsEnabled + claimValue := policyRules["AuthenticationContext_EndUser_Assignment"].ClaimValue + + if _, set := metadata.ResourceData.GetOk("activation_rules.0.require_conditional_access_authentication_context"); set { + isEnabled = pointer.To(true) + claimValue = pointer.To(model.ActivationRules[0].RequireConditionalAccessContext) + } else { + isEnabled = pointer.To(false) + } + + rule := msgraph.UnifiedRoleManagementPolicyRule{ + ID: policyRules["AuthenticationContext_EndUser_Assignment"].ID, + ODataType: policyRules["AuthenticationContext_EndUser_Assignment"].ODataType, + Target: policyRules["AuthenticationContext_EndUser_Assignment"].Target, + IsEnabled: isEnabled, + ClaimValue: claimValue, + } updatedRules = append(updatedRules, rule) } @@ -1015,80 +1076,246 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie enabledRules = append(enabledRules, "Ticketing") } - rule := policyRules["Enablement_EndUser_Assignment"] - rule.EnabledRules = pointer.To(enabledRules) + rule := msgraph.UnifiedRoleManagementPolicyRule{ + ID: policyRules["Enablement_EndUser_Assignment"].ID, + ODataType: policyRules["Enablement_EndUser_Assignment"].ODataType, + Target: policyRules["Enablement_EndUser_Assignment"].Target, + EnabledRules: &enabledRules, + } updatedRules = append(updatedRules, rule) } if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.eligible_assignments") { - rule := policyRules["Notification_Admin_Admin_Eligibility"] - rule.NotificationLevel = model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].NotificationLevel - rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].DefaultRecipients) - rule.NotificationRecipients = &model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].AdditionalRecipients + level := policyRules["Notification_Admin_Admin_Eligibility"].NotificationLevel + defaultRecipients := policyRules["Notification_Admin_Admin_Eligibility"].IsDefaultRecipientsEnabled + additionalRecipients := policyRules["Notification_Admin_Admin_Eligibility"].NotificationRecipients + + if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.eligible_assignments.0.notification_level") { + level = model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].NotificationLevel + } + if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.eligible_assignments.0.default_recipients") { + defaultRecipients = pointer.To(model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].DefaultRecipients) + } + if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.eligible_assignments.0.additional_recipients") { + additionalRecipients = pointer.To(model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].AdditionalRecipients) + } + + rule := msgraph.UnifiedRoleManagementPolicyRule{ + ID: policyRules["Notification_Admin_Admin_Eligibility"].ID, + ODataType: policyRules["Notification_Admin_Admin_Eligibility"].ODataType, + Target: policyRules["Notification_Admin_Admin_Eligibility"].Target, + NotificationLevel: level, + IsDefaultRecipientsEnabled: defaultRecipients, + NotificationRecipients: additionalRecipients, + } updatedRules = append(updatedRules, rule) } if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.active_assignments") { - rule := policyRules["Notification_Admin_Admin_Assignment"] - rule.NotificationLevel = model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0].NotificationLevel - rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0].DefaultRecipients) - rule.NotificationRecipients = &model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0].AdditionalRecipients + level := policyRules["Notification_Admin_Admin_Assignment"].NotificationLevel + defaultRecipients := policyRules["Notification_Admin_Admin_Assignment"].IsDefaultRecipientsEnabled + additionalRecipients := policyRules["Notification_Admin_Admin_Assignment"].NotificationRecipients + + if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.active_assignments.0.notification_level") { + level = model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0].NotificationLevel + } + if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.active_assignments.0.default_recipients") { + defaultRecipients = pointer.To(model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0].DefaultRecipients) + } + if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.active_assignments.0.additional_recipients") { + additionalRecipients = pointer.To(model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0].AdditionalRecipients) + } + + rule := msgraph.UnifiedRoleManagementPolicyRule{ + ID: policyRules["Notification_Admin_Admin_Assignment"].ID, + ODataType: policyRules["Notification_Admin_Admin_Assignment"].ODataType, + Target: policyRules["Notification_Admin_Admin_Assignment"].Target, + NotificationLevel: level, + IsDefaultRecipientsEnabled: defaultRecipients, + NotificationRecipients: additionalRecipients, + } updatedRules = append(updatedRules, rule) } if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.activations") { - rule := policyRules["Notification_Admin_EndUser_Assignment"] - rule.NotificationLevel = model.NotificationRules[0].AdminNotifications[0].Activations[0].NotificationLevel - rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules[0].AdminNotifications[0].Activations[0].DefaultRecipients) - rule.NotificationRecipients = &model.NotificationRules[0].AdminNotifications[0].Activations[0].AdditionalRecipients + level := policyRules["Notification_Admin_EndUser_Assignment"].NotificationLevel + defaultRecipients := policyRules["Notification_Admin_EndUser_Assignment"].IsDefaultRecipientsEnabled + additionalRecipients := policyRules["Notification_Admin_EndUser_Assignment"].NotificationRecipients + + if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.activations.0.notification_level") { + level = model.NotificationRules[0].AdminNotifications[0].Activations[0].NotificationLevel + } + if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.activations.0.default_recipients") { + defaultRecipients = pointer.To(model.NotificationRules[0].AdminNotifications[0].Activations[0].DefaultRecipients) + } + if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.activations.0.additional_recipients") { + additionalRecipients = pointer.To(model.NotificationRules[0].AdminNotifications[0].Activations[0].AdditionalRecipients) + } + + rule := msgraph.UnifiedRoleManagementPolicyRule{ + ID: policyRules["Notification_Admin_EndUser_Assignment"].ID, + ODataType: policyRules["Notification_Admin_EndUser_Assignment"].ODataType, + Target: policyRules["Notification_Admin_EndUser_Assignment"].Target, + NotificationLevel: level, + IsDefaultRecipientsEnabled: defaultRecipients, + NotificationRecipients: additionalRecipients, + } updatedRules = append(updatedRules, rule) } if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.eligible_assignments") { - rule := policyRules["Notification_Approver_Admin_Eligibility"] - rule.NotificationLevel = model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments[0].NotificationLevel - rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments[0].DefaultRecipients) - rule.NotificationRecipients = &model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments[0].AdditionalRecipients + level := policyRules["Notification_Approver_Admin_Eligibility"].NotificationLevel + defaultRecipients := policyRules["Notification_Approver_Admin_Eligibility"].IsDefaultRecipientsEnabled + additionalRecipients := policyRules["Notification_Approver_Admin_Eligibility"].NotificationRecipients + + if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.eligible_assignments.0.notification_level") { + level = model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].NotificationLevel + } + if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.eligible_assignments.0.default_recipients") { + defaultRecipients = pointer.To(model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].DefaultRecipients) + } + if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.eligible_assignments.0.additional_recipients") { + additionalRecipients = pointer.To(model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].AdditionalRecipients) + } + + rule := msgraph.UnifiedRoleManagementPolicyRule{ + ID: policyRules["Notification_Approver_Admin_Eligibility"].ID, + ODataType: policyRules["Notification_Approver_Admin_Eligibility"].ODataType, + Target: policyRules["Notification_Approver_Admin_Eligibility"].Target, + NotificationLevel: level, + IsDefaultRecipientsEnabled: defaultRecipients, + NotificationRecipients: additionalRecipients, + } updatedRules = append(updatedRules, rule) } if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.active_assignments") { - rule := policyRules["Notification_Approver_Admin_Assignment"] - rule.NotificationLevel = model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0].NotificationLevel - rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0].DefaultRecipients) - rule.NotificationRecipients = &model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0].AdditionalRecipients + level := policyRules["Notification_Approver_Admin_Assignment"].NotificationLevel + defaultRecipients := policyRules["Notification_Approver_Admin_Assignment"].IsDefaultRecipientsEnabled + additionalRecipients := policyRules["Notification_Approver_Admin_Assignment"].NotificationRecipients + + if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.active_assignments.0.notification_level") { + level = model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0].NotificationLevel + } + if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.active_assignments.0.default_recipients") { + defaultRecipients = pointer.To(model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0].DefaultRecipients) + } + if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.active_assignments.0.additional_recipients") { + additionalRecipients = pointer.To(model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0].AdditionalRecipients) + } + + rule := msgraph.UnifiedRoleManagementPolicyRule{ + ID: policyRules["Notification_Approver_Admin_Assignment"].ID, + ODataType: policyRules["Notification_Approver_Admin_Assignment"].ODataType, + Target: policyRules["Notification_Approver_Admin_Assignment"].Target, + NotificationLevel: level, + IsDefaultRecipientsEnabled: defaultRecipients, + NotificationRecipients: additionalRecipients, + } updatedRules = append(updatedRules, rule) } if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.activations") { - rule := policyRules["Notification_Approver_EndUser_Assignment"] - rule.NotificationLevel = model.NotificationRules[0].ApproverNotifications[0].Activations[0].NotificationLevel - rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules[0].ApproverNotifications[0].Activations[0].DefaultRecipients) - rule.NotificationRecipients = &model.NotificationRules[0].ApproverNotifications[0].Activations[0].AdditionalRecipients + level := policyRules["Notification_Approver_EndUser_Assignment"].NotificationLevel + defaultRecipients := policyRules["Notification_Approver_EndUser_Assignment"].IsDefaultRecipientsEnabled + additionalRecipients := policyRules["Notification_Approver_EndUser_Assignment"].NotificationRecipients + + if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.activations.0.notification_level") { + level = model.NotificationRules[0].ApproverNotifications[0].Activations[0].NotificationLevel + } + if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.activations.0.default_recipients") { + defaultRecipients = pointer.To(model.NotificationRules[0].ApproverNotifications[0].Activations[0].DefaultRecipients) + } + if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.activations.0.additional_recipients") { + additionalRecipients = pointer.To(model.NotificationRules[0].ApproverNotifications[0].Activations[0].AdditionalRecipients) + } + + rule := msgraph.UnifiedRoleManagementPolicyRule{ + ID: policyRules["Notification_Approver_EndUser_Assignment"].ID, + ODataType: policyRules["Notification_Approver_EndUser_Assignment"].ODataType, + Target: policyRules["Notification_Approver_EndUser_Assignment"].Target, + NotificationLevel: level, + IsDefaultRecipientsEnabled: defaultRecipients, + NotificationRecipients: additionalRecipients, + } updatedRules = append(updatedRules, rule) } if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.eligible_assignments") { - rule := policyRules["Notification_Requestor_Admin_Eligibility"] - rule.NotificationLevel = model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0].NotificationLevel - rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0].DefaultRecipients) - rule.NotificationRecipients = &model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0].AdditionalRecipients + level := policyRules["Notification_Requestor_Admin_Eligibility"].NotificationLevel + defaultRecipients := policyRules["Notification_Requestor_Admin_Eligibility"].IsDefaultRecipientsEnabled + additionalRecipients := policyRules["Notification_Requestor_Admin_Eligibility"].NotificationRecipients + + if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.eligible_assignments.0.notification_level") { + level = model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0].NotificationLevel + } + if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.eligible_assignments.0.default_recipients") { + defaultRecipients = pointer.To(model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0].DefaultRecipients) + } + if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.eligible_assignments.0.additional_recipients") { + additionalRecipients = pointer.To(model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0].AdditionalRecipients) + } + + rule := msgraph.UnifiedRoleManagementPolicyRule{ + ID: policyRules["Notification_Requestor_Admin_Eligibility"].ID, + ODataType: policyRules["Notification_Requestor_Admin_Eligibility"].ODataType, + Target: policyRules["Notification_Requestor_Admin_Eligibility"].Target, + NotificationLevel: level, + IsDefaultRecipientsEnabled: defaultRecipients, + NotificationRecipients: additionalRecipients, + } updatedRules = append(updatedRules, rule) } if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.active_assignments") { - rule := policyRules["Notification_Requestor_Admin_Assignment"] - rule.NotificationLevel = model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0].NotificationLevel - rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0].DefaultRecipients) - rule.NotificationRecipients = &model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0].AdditionalRecipients + level := policyRules["Notification_Requestor_Admin_Assignment"].NotificationLevel + defaultRecipients := policyRules["Notification_Requestor_Admin_Assignment"].IsDefaultRecipientsEnabled + additionalRecipients := policyRules["Notification_Requestor_Admin_Assignment"].NotificationRecipients + + if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.active_assignments.0.notification_level") { + level = model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0].NotificationLevel + } + if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.active_assignments.0.default_recipients") { + defaultRecipients = pointer.To(model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0].DefaultRecipients) + } + if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.active_assignments.0.additional_recipients") { + additionalRecipients = pointer.To(model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0].AdditionalRecipients) + } + + rule := msgraph.UnifiedRoleManagementPolicyRule{ + ID: policyRules["Notification_Requestor_Admin_Assignment"].ID, + ODataType: policyRules["Notification_Requestor_Admin_Assignment"].ODataType, + Target: policyRules["Notification_Requestor_Admin_Assignment"].Target, + NotificationLevel: level, + IsDefaultRecipientsEnabled: defaultRecipients, + NotificationRecipients: additionalRecipients, + } updatedRules = append(updatedRules, rule) } if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.activations") { - rule := policyRules["Notification_Requestor_EndUser_Assignment"] - rule.NotificationLevel = model.NotificationRules[0].AssigneeNotifications[0].Activations[0].NotificationLevel - rule.IsDefaultRecipientsEnabled = pointer.To(model.NotificationRules[0].AssigneeNotifications[0].Activations[0].DefaultRecipients) - rule.NotificationRecipients = &model.NotificationRules[0].AssigneeNotifications[0].Activations[0].AdditionalRecipients + level := policyRules["Notification_Requestor_EndUser_Assignment"].NotificationLevel + defaultRecipients := policyRules["Notification_Requestor_EndUser_Assignment"].IsDefaultRecipientsEnabled + additionalRecipients := policyRules["Notification_Requestor_EndUser_Assignment"].NotificationRecipients + + if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.activations.0.notification_level") { + level = model.NotificationRules[0].AssigneeNotifications[0].Activations[0].NotificationLevel + } + if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.activations.0.default_recipients") { + defaultRecipients = pointer.To(model.NotificationRules[0].AssigneeNotifications[0].Activations[0].DefaultRecipients) + } + if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.activations.0.additional_recipients") { + additionalRecipients = pointer.To(model.NotificationRules[0].AssigneeNotifications[0].Activations[0].AdditionalRecipients) + } + + rule := msgraph.UnifiedRoleManagementPolicyRule{ + ID: policyRules["Notification_Requestor_EndUser_Assignment"].ID, + ODataType: policyRules["Notification_Requestor_EndUser_Assignment"].ODataType, + Target: policyRules["Notification_Requestor_EndUser_Assignment"].Target, + NotificationLevel: level, + IsDefaultRecipientsEnabled: defaultRecipients, + NotificationRecipients: additionalRecipients, + } updatedRules = append(updatedRules, rule) } From 66ac6af8115214b6a23496fe5118bc8b715b5999 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Tue, 5 Mar 2024 11:20:21 +1300 Subject: [PATCH 19/55] Read now works also --- .../group_role_management_policy_resource.go | 61 +++++++++++++++++-- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index 4592422dd8..720a5b6058 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -22,19 +22,24 @@ type RoleManagementPolicyModel struct { DisplayName string `tfschema:"display_name"` GroupId string `tfschema:"object_id"` ScopeType msgraph.UnifiedRoleManagementPolicyScope `tfschema:"assignment_type"` - ActiveAssignmentRules []AssignmentRules `tfschema:"active_assignment_rules"` - EligbleAssignmentRules []AssignmentRules `tfschema:"eligible_assignment_rules"` + ActiveAssignmentRules []ActiveAssignmentRules `tfschema:"active_assignment_rules"` + EligbleAssignmentRules []EligibleAssignmentRules `tfschema:"eligible_assignment_rules"` ActivationRules []ActivationRules `tfschema:"activation_rules"` NotificationRules []NotificationRules `tfschema:"notification_rules"` } -type AssignmentRules struct { +type ActiveAssignmentRules struct { ExpirationRequired bool `tfschema:"expiration_required"` ExpireAfter string `tfschema:"expire_after"` RequireMultiFactorAuth bool `tfschema:"require_multifactor_authentication"` RequireJustification bool `tfschema:"require_justification"` } +type EligibleAssignmentRules struct { + ExpirationRequired bool `tfschema:"expiration_required"` + ExpireAfter string `tfschema:"expire_after"` +} + type ActivationRules struct { MaximumDuration string `tfschema:"maximum_duration"` RequireApproval bool `tfschema:"require_approval"` @@ -729,7 +734,28 @@ func (r RoleManagementPolicyResource) Read() sdk.ResourceFunc { model.Description = *result.Description model.DisplayName = *result.DisplayName model.GroupId = *result.ScopeId - model.ScopeType = result.ScopeType + + if len(model.EligbleAssignmentRules) == 0 { + model.EligbleAssignmentRules = make([]EligibleAssignmentRules, 1) + } + if len(model.ActiveAssignmentRules) == 0 { + model.ActiveAssignmentRules = make([]ActiveAssignmentRules, 1) + } + if len(model.ActivationRules) == 0 { + model.ActivationRules = make([]ActivationRules, 1) + } + if len(model.NotificationRules) == 0 { + model.NotificationRules = make([]NotificationRules, 1) + } + if len(model.NotificationRules[0].AdminNotifications) == 0 { + model.NotificationRules[0].AdminNotifications = make([]NotificationRule, 1) + } + if len(model.NotificationRules[0].ApproverNotifications) == 0 { + model.NotificationRules[0].ApproverNotifications = make([]NotificationRule, 1) + } + if len(model.NotificationRules[0].AssigneeNotifications) == 0 { + model.NotificationRules[0].AssigneeNotifications = make([]NotificationRule, 1) + } for _, rule := range *result.Rules { switch *rule.ID { @@ -805,46 +831,73 @@ func (r RoleManagementPolicyResource) Read() sdk.ResourceFunc { } case "Notification_Admin_Admin_Eligibility": + if len(model.NotificationRules[0].AdminNotifications[0].EligibleAssignments) == 0 { + model.NotificationRules[0].AdminNotifications[0].EligibleAssignments = make([]NotificationSettings, 1) + } model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].NotificationLevel = rule.NotificationLevel model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].AdditionalRecipients = *rule.NotificationRecipients case "Notification_Admin_Admin_Assignment": + if len(model.NotificationRules[0].AdminNotifications[0].ActiveAssignments) == 0 { + model.NotificationRules[0].AdminNotifications[0].ActiveAssignments = make([]NotificationSettings, 1) + } model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0].NotificationLevel = rule.NotificationLevel model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0].AdditionalRecipients = *rule.NotificationRecipients case "Notification_Admin_EndUser_Assignment": + if len(model.NotificationRules[0].AdminNotifications[0].Activations) == 0 { + model.NotificationRules[0].AdminNotifications[0].Activations = make([]NotificationSettings, 1) + } model.NotificationRules[0].AdminNotifications[0].Activations[0].NotificationLevel = rule.NotificationLevel model.NotificationRules[0].AdminNotifications[0].Activations[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled model.NotificationRules[0].AdminNotifications[0].Activations[0].AdditionalRecipients = *rule.NotificationRecipients case "Notification_Approver_Admin_Eligibility": + if len(model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments) == 0 { + model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments = make([]NotificationSettings, 1) + } model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments[0].NotificationLevel = rule.NotificationLevel model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments[0].AdditionalRecipients = *rule.NotificationRecipients case "Notification_Approver_Admin_Assignment": + if len(model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments) == 0 { + model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments = make([]NotificationSettings, 1) + } model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0].NotificationLevel = rule.NotificationLevel model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0].AdditionalRecipients = *rule.NotificationRecipients case "Notification_Approver_EndUser_Assignment": + if len(model.NotificationRules[0].ApproverNotifications[0].Activations) == 0 { + model.NotificationRules[0].ApproverNotifications[0].Activations = make([]NotificationSettings, 1) + } model.NotificationRules[0].ApproverNotifications[0].Activations[0].NotificationLevel = rule.NotificationLevel model.NotificationRules[0].ApproverNotifications[0].Activations[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled model.NotificationRules[0].ApproverNotifications[0].Activations[0].AdditionalRecipients = *rule.NotificationRecipients case "Notification_Requestor_Admin_Eligibility": + if len(model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments) == 0 { + model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments = make([]NotificationSettings, 1) + } model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0].NotificationLevel = rule.NotificationLevel model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0].AdditionalRecipients = *rule.NotificationRecipients case "Notification_Requestor_Admin_Assignment": + if len(model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments) == 0 { + model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments = make([]NotificationSettings, 1) + } model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0].NotificationLevel = rule.NotificationLevel model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0].AdditionalRecipients = *rule.NotificationRecipients case "Notification_Requestor_EndUser_Assignment": + if len(model.NotificationRules[0].AssigneeNotifications[0].Activations) == 0 { + model.NotificationRules[0].AssigneeNotifications[0].Activations = make([]NotificationSettings, 1) + } model.NotificationRules[0].AssigneeNotifications[0].Activations[0].NotificationLevel = rule.NotificationLevel model.NotificationRules[0].AssigneeNotifications[0].Activations[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled model.NotificationRules[0].AssigneeNotifications[0].Activations[0].AdditionalRecipients = *rule.NotificationRecipients From d370422124e2d3c9186975a6ef9307c8e6b58a2d Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Tue, 5 Mar 2024 12:04:38 +1300 Subject: [PATCH 20/55] Update parameter requirements --- .../group_role_management_policy_resource.go | 59 ++++++++----------- 1 file changed, 23 insertions(+), 36 deletions(-) diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index 720a5b6058..c340142050 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -128,12 +128,14 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { Description: "Must the assignment have an expiry date", Type: pluginsdk.TypeBool, Optional: true, + Computed: true, }, "expire_after": { Description: "The duration after which assignments expire", Type: pluginsdk.TypeString, Optional: true, + Computed: true, ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"P15D", "P30D", "P90D", "P180D", "P365D"}, false)), }, }, @@ -152,12 +154,14 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { Description: "Must the assignment have an expiry date", Type: pluginsdk.TypeBool, Optional: true, + Computed: true, }, "expire_after": { Description: "The duration after which assignments expire", Type: pluginsdk.TypeString, Optional: true, + Computed: true, ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"P15D", "P30D", "P90D", "P180D", "P365D"}, false)), }, @@ -308,8 +312,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { "notification_level": { Description: "What level of notifications are sent", Type: pluginsdk.TypeString, - Optional: true, - Computed: true, + Required: true, ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelAll, msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelCritical, @@ -318,8 +321,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { "default_recipients": { Description: "Whether the default recipients are notified", Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, + Required: true, }, "additional_recipients": { Description: "The additional recipients to notify", @@ -344,8 +346,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { "notification_level": { Description: "What level of notifications are sent", Type: pluginsdk.TypeString, - Optional: true, - Computed: true, + Required: true, ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelAll, msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelCritical, @@ -354,13 +355,13 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { "default_recipients": { Description: "Whether the default recipients are notified", Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, + Required: true, }, "additional_recipients": { Description: "The additional recipients to notify", Type: pluginsdk.TypeList, Optional: true, + Computed: true, Elem: &pluginsdk.Schema{ Type: pluginsdk.TypeString, }, @@ -380,8 +381,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { "notification_level": { Description: "What level of notifications are sent", Type: pluginsdk.TypeString, - Optional: true, - Computed: true, + Required: true, ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelAll, msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelCritical, @@ -390,8 +390,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { "default_recipients": { Description: "Whether the default recipients are notified", Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, + Required: true, }, "additional_recipients": { Description: "The additional recipients to notify", @@ -427,8 +426,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { "notification_level": { Description: "What level of notifications are sent", Type: pluginsdk.TypeString, - Optional: true, - Computed: true, + Required: true, ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelAll, msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelCritical, @@ -437,8 +435,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { "default_recipients": { Description: "Whether the default recipients are notified", Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, + Required: true, }, "additional_recipients": { Description: "The additional recipients to notify", @@ -463,8 +460,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { "notification_level": { Description: "What level of notifications are sent", Type: pluginsdk.TypeString, - Optional: true, - Computed: true, + Required: true, ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelAll, msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelCritical, @@ -473,8 +469,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { "default_recipients": { Description: "Whether the default recipients are notified", Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, + Required: true, }, "additional_recipients": { Description: "The additional recipients to notify", @@ -499,8 +494,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { "notification_level": { Description: "What level of notifications are sent", Type: pluginsdk.TypeString, - Optional: true, - Computed: true, + Required: true, ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelAll, msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelCritical, @@ -509,8 +503,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { "default_recipients": { Description: "Whether the default recipients are notified", Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, + Required: true, }, "additional_recipients": { Description: "The additional recipients to notify", @@ -546,15 +539,13 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { "notification_level": { Description: "What level of notifications are sent", Type: pluginsdk.TypeString, - Optional: true, - Computed: true, + Required: true, ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), }, "default_recipients": { Description: "Whether the default recipients are notified", Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, + Required: true, }, "additional_recipients": { Description: "The additional recipients to notify", @@ -579,15 +570,13 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { "notification_level": { Description: "What level of notifications are sent", Type: pluginsdk.TypeString, - Optional: true, - Computed: true, + Required: true, ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), }, "default_recipients": { Description: "Whether the default recipients are notified", Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, + Required: true, }, "additional_recipients": { Description: "The additional recipients to notify", @@ -612,15 +601,13 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { "notification_level": { Description: "What level of notifications are sent", Type: pluginsdk.TypeString, - Optional: true, - Computed: true, + Required: true, ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), }, "default_recipients": { Description: "Whether the default recipients are notified", Type: pluginsdk.TypeBool, - Optional: true, - Computed: true, + Required: true, }, "additional_recipients": { Description: "The additional recipients to notify", From 7fcfbce04fa2f9bf6ad040c3f82632728585428d Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Tue, 5 Mar 2024 12:13:36 +1300 Subject: [PATCH 21/55] Rename all resource etc with Group prefix Preparation for doing Directory and DirectoryRole resources --- .../group_role_management_policy_resource.go | 132 +++++++++--------- internal/services/policies/registration.go | 5 +- 2 files changed, 68 insertions(+), 69 deletions(-) diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index c340142050..1857b16020 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -17,84 +17,84 @@ import ( "github.com/manicminer/hamilton/msgraph" ) -type RoleManagementPolicyModel struct { - Description string `tfschema:"description"` - DisplayName string `tfschema:"display_name"` - GroupId string `tfschema:"object_id"` - ScopeType msgraph.UnifiedRoleManagementPolicyScope `tfschema:"assignment_type"` - ActiveAssignmentRules []ActiveAssignmentRules `tfschema:"active_assignment_rules"` - EligbleAssignmentRules []EligibleAssignmentRules `tfschema:"eligible_assignment_rules"` - ActivationRules []ActivationRules `tfschema:"activation_rules"` - NotificationRules []NotificationRules `tfschema:"notification_rules"` +type GroupRoleManagementPolicyModel struct { + Description string `tfschema:"description"` + DisplayName string `tfschema:"display_name"` + GroupId string `tfschema:"object_id"` + ScopeType msgraph.UnifiedRoleManagementPolicyScope `tfschema:"assignment_type"` + ActiveAssignmentRules []GroupRoleManagementPolicyActiveAssignmentRules `tfschema:"active_assignment_rules"` + EligbleAssignmentRules []GroupRoleManagementPolicyEligibleAssignmentRules `tfschema:"eligible_assignment_rules"` + ActivationRules []GroupRoleManagementPolicyActivationRules `tfschema:"activation_rules"` + NotificationRules []GroupRoleManagementPolicyNotificationRules `tfschema:"notification_rules"` } -type ActiveAssignmentRules struct { +type GroupRoleManagementPolicyActiveAssignmentRules struct { ExpirationRequired bool `tfschema:"expiration_required"` ExpireAfter string `tfschema:"expire_after"` RequireMultiFactorAuth bool `tfschema:"require_multifactor_authentication"` RequireJustification bool `tfschema:"require_justification"` } -type EligibleAssignmentRules struct { +type GroupRoleManagementPolicyEligibleAssignmentRules struct { ExpirationRequired bool `tfschema:"expiration_required"` ExpireAfter string `tfschema:"expire_after"` } -type ActivationRules struct { - MaximumDuration string `tfschema:"maximum_duration"` - RequireApproval bool `tfschema:"require_approval"` - ApprovalStages []ApprovalStage `tfschema:"approval_stages"` - RequireConditionalAccessContext string `tfschema:"require_conditional_access_authentication_context"` - RequireMultiFactorAuth bool `tfschema:"require_multifactor_authentication"` - RequireJustification bool `tfschema:"require_justification"` - RequireTicketInfo bool `tfschema:"require_ticket_info"` +type GroupRoleManagementPolicyActivationRules struct { + MaximumDuration string `tfschema:"maximum_duration"` + RequireApproval bool `tfschema:"require_approval"` + ApprovalStages []GroupRoleManagementPolicyApprovalStage `tfschema:"approval_stages"` + RequireConditionalAccessContext string `tfschema:"require_conditional_access_authentication_context"` + RequireMultiFactorAuth bool `tfschema:"require_multifactor_authentication"` + RequireJustification bool `tfschema:"require_justification"` + RequireTicketInfo bool `tfschema:"require_ticket_info"` } -type ApprovalStage struct { - PrimaryApprovers []Approver `tfschema:"primary_approvers"` +type GroupRoleManagementPolicyApprovalStage struct { + PrimaryApprovers []GroupRoleManagementPolicyApprover `tfschema:"primary_approvers"` } -type Approver struct { +type GroupRoleManagementPolicyApprover struct { Description string `tfschema:"description"` ObjectId string `tfschema:"object_id"` ObjectType string `tfschema:"object_type"` } -type NotificationRules struct { - AdminNotifications []NotificationRule `tfschema:"admin_notifications"` - ApproverNotifications []NotificationRule `tfschema:"approver_notifications"` - AssigneeNotifications []NotificationRule `tfschema:"assignee_notifications"` +type GroupRoleManagementPolicyNotificationRules struct { + AdminNotifications []GroupRoleManagementPolicyNotificationRule `tfschema:"admin_notifications"` + ApproverNotifications []GroupRoleManagementPolicyNotificationRule `tfschema:"approver_notifications"` + AssigneeNotifications []GroupRoleManagementPolicyNotificationRule `tfschema:"assignee_notifications"` } -type NotificationRule struct { - EligibleAssignments []NotificationSettings `tfschema:"eligible_assignments"` - ActiveAssignments []NotificationSettings `tfschema:"active_assignments"` - Activations []NotificationSettings `tfschema:"activations"` +type GroupRoleManagementPolicyNotificationRule struct { + EligibleAssignments []GroupRoleManagementPolicyNotificationSettings `tfschema:"eligible_assignments"` + ActiveAssignments []GroupRoleManagementPolicyNotificationSettings `tfschema:"active_assignments"` + Activations []GroupRoleManagementPolicyNotificationSettings `tfschema:"activations"` } -type NotificationSettings struct { +type GroupRoleManagementPolicyNotificationSettings struct { NotificationLevel msgraph.UnifiedRoleManagementPolicyRuleNotificationLevel `tfschema:"notification_level"` DefaultRecipients bool `tfschema:"default_recipients"` AdditionalRecipients []string `tfschema:"additional_recipients"` } -type RoleManagementPolicyResource struct{} +type GroupRoleManagementPolicyResource struct{} -func (r RoleManagementPolicyResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { +func (r GroupRoleManagementPolicyResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { return validation.IsUUID } -var _ sdk.Resource = RoleManagementPolicyResource{} +var _ sdk.Resource = GroupRoleManagementPolicyResource{} -func (r RoleManagementPolicyResource) ResourceType() string { +func (r GroupRoleManagementPolicyResource) ResourceType() string { return "azuread_group_role_management_policy" } -func (r RoleManagementPolicyResource) ModelObject() interface{} { - return &RoleManagementPolicyModel{} +func (r GroupRoleManagementPolicyResource) ModelObject() interface{} { + return &GroupRoleManagementPolicyModel{} } -func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { +func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { return map[string]*pluginsdk.Schema{ "object_id": { Description: "ID of the group to which this policy is assigned", @@ -629,7 +629,7 @@ func (r RoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { } } -func (r RoleManagementPolicyResource) Attributes() map[string]*pluginsdk.Schema { +func (r GroupRoleManagementPolicyResource) Attributes() map[string]*pluginsdk.Schema { return map[string]*pluginsdk.Schema{ "display_name": { Description: "The display name of the policy", @@ -645,7 +645,7 @@ func (r RoleManagementPolicyResource) Attributes() map[string]*pluginsdk.Schema } } -func (r RoleManagementPolicyResource) Create() sdk.ResourceFunc { +func (r GroupRoleManagementPolicyResource) Create() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 5 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { @@ -694,7 +694,7 @@ func (r RoleManagementPolicyResource) Create() sdk.ResourceFunc { } } -func (r RoleManagementPolicyResource) Read() sdk.ResourceFunc { +func (r GroupRoleManagementPolicyResource) Read() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 5 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { @@ -705,7 +705,7 @@ func (r RoleManagementPolicyResource) Read() sdk.ResourceFunc { return fmt.Errorf("Could not parse policy ID, %+v", err) } - var model RoleManagementPolicyModel + var model GroupRoleManagementPolicyModel if err := metadata.Decode(&model); err != nil { return fmt.Errorf("decoding: %+v", err) } @@ -723,25 +723,25 @@ func (r RoleManagementPolicyResource) Read() sdk.ResourceFunc { model.GroupId = *result.ScopeId if len(model.EligbleAssignmentRules) == 0 { - model.EligbleAssignmentRules = make([]EligibleAssignmentRules, 1) + model.EligbleAssignmentRules = make([]GroupRoleManagementPolicyEligibleAssignmentRules, 1) } if len(model.ActiveAssignmentRules) == 0 { - model.ActiveAssignmentRules = make([]ActiveAssignmentRules, 1) + model.ActiveAssignmentRules = make([]GroupRoleManagementPolicyActiveAssignmentRules, 1) } if len(model.ActivationRules) == 0 { - model.ActivationRules = make([]ActivationRules, 1) + model.ActivationRules = make([]GroupRoleManagementPolicyActivationRules, 1) } if len(model.NotificationRules) == 0 { - model.NotificationRules = make([]NotificationRules, 1) + model.NotificationRules = make([]GroupRoleManagementPolicyNotificationRules, 1) } if len(model.NotificationRules[0].AdminNotifications) == 0 { - model.NotificationRules[0].AdminNotifications = make([]NotificationRule, 1) + model.NotificationRules[0].AdminNotifications = make([]GroupRoleManagementPolicyNotificationRule, 1) } if len(model.NotificationRules[0].ApproverNotifications) == 0 { - model.NotificationRules[0].ApproverNotifications = make([]NotificationRule, 1) + model.NotificationRules[0].ApproverNotifications = make([]GroupRoleManagementPolicyNotificationRule, 1) } if len(model.NotificationRules[0].AssigneeNotifications) == 0 { - model.NotificationRules[0].AssigneeNotifications = make([]NotificationRule, 1) + model.NotificationRules[0].AssigneeNotifications = make([]GroupRoleManagementPolicyNotificationRule, 1) } for _, rule := range *result.Rules { @@ -771,19 +771,19 @@ func (r RoleManagementPolicyResource) Read() sdk.ResourceFunc { case "Approval_EndUser_Assignment": model.ActivationRules[0].RequireApproval = *rule.Setting.IsApprovalRequired - model.ActivationRules[0].ApprovalStages = make([]ApprovalStage, 0) + model.ActivationRules[0].ApprovalStages = make([]GroupRoleManagementPolicyApprovalStage, 0) for _, stage := range *rule.Setting.ApprovalStages { - primaryApprovers := make([]Approver, 0) + primaryApprovers := make([]GroupRoleManagementPolicyApprover, 0) for _, approver := range *stage.PrimaryApprovers { switch { case *approver.ODataType == "#microsoft.graph.singleUser": - primaryApprovers = append(primaryApprovers, Approver{ + primaryApprovers = append(primaryApprovers, GroupRoleManagementPolicyApprover{ Description: *approver.Description, ObjectId: *approver.ID, ObjectType: "user", }) case *approver.ODataType == "#microsoft.graph.groupMembers": - primaryApprovers = append(primaryApprovers, Approver{ + primaryApprovers = append(primaryApprovers, GroupRoleManagementPolicyApprover{ Description: *approver.Description, ObjectId: *approver.ID, ObjectType: "group", @@ -791,7 +791,7 @@ func (r RoleManagementPolicyResource) Read() sdk.ResourceFunc { default: return fmt.Errorf("unknown approver type: %s", *approver.ODataType) } - model.ActivationRules[0].ApprovalStages = append(model.ActivationRules[0].ApprovalStages, ApprovalStage{ + model.ActivationRules[0].ApprovalStages = append(model.ActivationRules[0].ApprovalStages, GroupRoleManagementPolicyApprovalStage{ PrimaryApprovers: primaryApprovers, }) } @@ -819,7 +819,7 @@ func (r RoleManagementPolicyResource) Read() sdk.ResourceFunc { case "Notification_Admin_Admin_Eligibility": if len(model.NotificationRules[0].AdminNotifications[0].EligibleAssignments) == 0 { - model.NotificationRules[0].AdminNotifications[0].EligibleAssignments = make([]NotificationSettings, 1) + model.NotificationRules[0].AdminNotifications[0].EligibleAssignments = make([]GroupRoleManagementPolicyNotificationSettings, 1) } model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].NotificationLevel = rule.NotificationLevel model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled @@ -827,7 +827,7 @@ func (r RoleManagementPolicyResource) Read() sdk.ResourceFunc { case "Notification_Admin_Admin_Assignment": if len(model.NotificationRules[0].AdminNotifications[0].ActiveAssignments) == 0 { - model.NotificationRules[0].AdminNotifications[0].ActiveAssignments = make([]NotificationSettings, 1) + model.NotificationRules[0].AdminNotifications[0].ActiveAssignments = make([]GroupRoleManagementPolicyNotificationSettings, 1) } model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0].NotificationLevel = rule.NotificationLevel model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled @@ -835,7 +835,7 @@ func (r RoleManagementPolicyResource) Read() sdk.ResourceFunc { case "Notification_Admin_EndUser_Assignment": if len(model.NotificationRules[0].AdminNotifications[0].Activations) == 0 { - model.NotificationRules[0].AdminNotifications[0].Activations = make([]NotificationSettings, 1) + model.NotificationRules[0].AdminNotifications[0].Activations = make([]GroupRoleManagementPolicyNotificationSettings, 1) } model.NotificationRules[0].AdminNotifications[0].Activations[0].NotificationLevel = rule.NotificationLevel model.NotificationRules[0].AdminNotifications[0].Activations[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled @@ -843,7 +843,7 @@ func (r RoleManagementPolicyResource) Read() sdk.ResourceFunc { case "Notification_Approver_Admin_Eligibility": if len(model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments) == 0 { - model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments = make([]NotificationSettings, 1) + model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments = make([]GroupRoleManagementPolicyNotificationSettings, 1) } model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments[0].NotificationLevel = rule.NotificationLevel model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled @@ -851,7 +851,7 @@ func (r RoleManagementPolicyResource) Read() sdk.ResourceFunc { case "Notification_Approver_Admin_Assignment": if len(model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments) == 0 { - model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments = make([]NotificationSettings, 1) + model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments = make([]GroupRoleManagementPolicyNotificationSettings, 1) } model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0].NotificationLevel = rule.NotificationLevel model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled @@ -859,7 +859,7 @@ func (r RoleManagementPolicyResource) Read() sdk.ResourceFunc { case "Notification_Approver_EndUser_Assignment": if len(model.NotificationRules[0].ApproverNotifications[0].Activations) == 0 { - model.NotificationRules[0].ApproverNotifications[0].Activations = make([]NotificationSettings, 1) + model.NotificationRules[0].ApproverNotifications[0].Activations = make([]GroupRoleManagementPolicyNotificationSettings, 1) } model.NotificationRules[0].ApproverNotifications[0].Activations[0].NotificationLevel = rule.NotificationLevel model.NotificationRules[0].ApproverNotifications[0].Activations[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled @@ -867,7 +867,7 @@ func (r RoleManagementPolicyResource) Read() sdk.ResourceFunc { case "Notification_Requestor_Admin_Eligibility": if len(model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments) == 0 { - model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments = make([]NotificationSettings, 1) + model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments = make([]GroupRoleManagementPolicyNotificationSettings, 1) } model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0].NotificationLevel = rule.NotificationLevel model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled @@ -875,7 +875,7 @@ func (r RoleManagementPolicyResource) Read() sdk.ResourceFunc { case "Notification_Requestor_Admin_Assignment": if len(model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments) == 0 { - model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments = make([]NotificationSettings, 1) + model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments = make([]GroupRoleManagementPolicyNotificationSettings, 1) } model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0].NotificationLevel = rule.NotificationLevel model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled @@ -883,7 +883,7 @@ func (r RoleManagementPolicyResource) Read() sdk.ResourceFunc { case "Notification_Requestor_EndUser_Assignment": if len(model.NotificationRules[0].AssigneeNotifications[0].Activations) == 0 { - model.NotificationRules[0].AssigneeNotifications[0].Activations = make([]NotificationSettings, 1) + model.NotificationRules[0].AssigneeNotifications[0].Activations = make([]GroupRoleManagementPolicyNotificationSettings, 1) } model.NotificationRules[0].AssigneeNotifications[0].Activations[0].NotificationLevel = rule.NotificationLevel model.NotificationRules[0].AssigneeNotifications[0].Activations[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled @@ -897,7 +897,7 @@ func (r RoleManagementPolicyResource) Read() sdk.ResourceFunc { } } -func (r RoleManagementPolicyResource) Update() sdk.ResourceFunc { +func (r GroupRoleManagementPolicyResource) Update() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 5 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { @@ -933,7 +933,7 @@ func (r RoleManagementPolicyResource) Update() sdk.ResourceFunc { } } -func (r RoleManagementPolicyResource) Delete() sdk.ResourceFunc { +func (r GroupRoleManagementPolicyResource) Delete() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 5 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { @@ -948,7 +948,7 @@ func (r RoleManagementPolicyResource) Delete() sdk.ResourceFunc { } func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.UnifiedRoleManagementPolicy) (*msgraph.UnifiedRoleManagementPolicy, error) { - var model RoleManagementPolicyModel + var model GroupRoleManagementPolicyModel if err := metadata.Decode(&model); err != nil { return nil, fmt.Errorf("decoding: %+v", err) } diff --git a/internal/services/policies/registration.go b/internal/services/policies/registration.go index 814c0355ab..78f76372c3 100644 --- a/internal/services/policies/registration.go +++ b/internal/services/policies/registration.go @@ -42,13 +42,12 @@ func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { // DataSources returns the typed DataSources supported by this service func (r Registration) DataSources() []sdk.DataSource { - return []sdk.DataSource{ - } + return []sdk.DataSource{} } // Resources returns the typed Resources supported by this service func (r Registration) Resources() []sdk.Resource { return []sdk.Resource{ - RoleManagementPolicyResource{}, + GroupRoleManagementPolicyResource{}, } } From 2acc9de9d90f041baa04467f2f98877d0b7918fb Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Tue, 5 Mar 2024 14:46:03 +1300 Subject: [PATCH 22/55] We have working tests --- .../group_role_management_policy_resource.go | 180 +++++++++++------- ...up_role_management_policy_resource_test.go | 162 ++++++++++++++++ 2 files changed, 269 insertions(+), 73 deletions(-) create mode 100644 internal/services/policies/group_role_management_policy_resource_test.go diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index 1857b16020..29e65ade84 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -18,14 +18,14 @@ import ( ) type GroupRoleManagementPolicyModel struct { - Description string `tfschema:"description"` - DisplayName string `tfschema:"display_name"` - GroupId string `tfschema:"object_id"` - ScopeType msgraph.UnifiedRoleManagementPolicyScope `tfschema:"assignment_type"` - ActiveAssignmentRules []GroupRoleManagementPolicyActiveAssignmentRules `tfschema:"active_assignment_rules"` - EligbleAssignmentRules []GroupRoleManagementPolicyEligibleAssignmentRules `tfschema:"eligible_assignment_rules"` - ActivationRules []GroupRoleManagementPolicyActivationRules `tfschema:"activation_rules"` - NotificationRules []GroupRoleManagementPolicyNotificationRules `tfschema:"notification_rules"` + Description string `tfschema:"description"` + DisplayName string `tfschema:"display_name"` + GroupId string `tfschema:"object_id"` + ScopeType msgraph.UnifiedRoleManagementPolicyScope `tfschema:"assignment_type"` + ActiveAssignmentRules []GroupRoleManagementPolicyActiveAssignmentRules `tfschema:"active_assignment_rules"` + EligibleAssignmentRules []GroupRoleManagementPolicyEligibleAssignmentRules `tfschema:"eligible_assignment_rules"` + ActivationRules []GroupRoleManagementPolicyActivationRules `tfschema:"activation_rules"` + NotificationRules []GroupRoleManagementPolicyNotificationRules `tfschema:"notification_rules"` } type GroupRoleManagementPolicyActiveAssignmentRules struct { @@ -51,7 +51,7 @@ type GroupRoleManagementPolicyActivationRules struct { } type GroupRoleManagementPolicyApprovalStage struct { - PrimaryApprovers []GroupRoleManagementPolicyApprover `tfschema:"primary_approvers"` + PrimaryApprovers []GroupRoleManagementPolicyApprover `tfschema:"primary_approver"` } type GroupRoleManagementPolicyApprover struct { @@ -217,7 +217,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch MaxItems: 1, Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ - "primary_approvers": { + "primary_approver": { Description: "The IDs of the users or groups who can approve the activation", Type: pluginsdk.TypeList, Required: true, @@ -722,8 +722,8 @@ func (r GroupRoleManagementPolicyResource) Read() sdk.ResourceFunc { model.DisplayName = *result.DisplayName model.GroupId = *result.ScopeId - if len(model.EligbleAssignmentRules) == 0 { - model.EligbleAssignmentRules = make([]GroupRoleManagementPolicyEligibleAssignmentRules, 1) + if len(model.EligibleAssignmentRules) == 0 { + model.EligibleAssignmentRules = make([]GroupRoleManagementPolicyEligibleAssignmentRules, 1) } if len(model.ActiveAssignmentRules) == 0 { model.ActiveAssignmentRules = make([]GroupRoleManagementPolicyActiveAssignmentRules, 1) @@ -747,8 +747,8 @@ func (r GroupRoleManagementPolicyResource) Read() sdk.ResourceFunc { for _, rule := range *result.Rules { switch *rule.ID { case "Expiration_Admin_Eligibility": - model.EligbleAssignmentRules[0].ExpirationRequired = *rule.IsExpirationRequired - model.EligbleAssignmentRules[0].ExpireAfter = *rule.MaximumDuration + model.EligibleAssignmentRules[0].ExpirationRequired = *rule.IsExpirationRequired + model.EligibleAssignmentRules[0].ExpireAfter = *rule.MaximumDuration case "Enablement_Admin_Assignment": model.ActiveAssignmentRules[0].RequireMultiFactorAuth = false @@ -779,13 +779,13 @@ func (r GroupRoleManagementPolicyResource) Read() sdk.ResourceFunc { case *approver.ODataType == "#microsoft.graph.singleUser": primaryApprovers = append(primaryApprovers, GroupRoleManagementPolicyApprover{ Description: *approver.Description, - ObjectId: *approver.ID, + ObjectId: *approver.UserID, ObjectType: "user", }) case *approver.ODataType == "#microsoft.graph.groupMembers": primaryApprovers = append(primaryApprovers, GroupRoleManagementPolicyApprover{ Description: *approver.Description, - ObjectId: *approver.ID, + ObjectId: *approver.GroupID, ObjectType: "group", }) default: @@ -964,11 +964,12 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie expirationRequired := policyRules["Expiration_Admin_Eligibility"].IsExpirationRequired maximumDuration := policyRules["Expiration_Admin_Eligibility"].MaximumDuration - if metadata.ResourceData.HasChange("eligible_assignment_rules.0.expiration_required") { - expirationRequired = pointer.To(model.EligbleAssignmentRules[0].ExpirationRequired) + if *expirationRequired != model.EligibleAssignmentRules[0].ExpirationRequired { + expirationRequired = pointer.To(model.EligibleAssignmentRules[0].ExpirationRequired) } - if metadata.ResourceData.HasChange("eligible_assignment_rules.0.expire_after") { - maximumDuration = pointer.To(model.EligbleAssignmentRules[0].ExpireAfter) + if *maximumDuration != model.EligibleAssignmentRules[0].ExpireAfter && + model.EligibleAssignmentRules[0].ExpireAfter != "" { + maximumDuration = pointer.To(model.EligibleAssignmentRules[0].ExpireAfter) } rule := msgraph.UnifiedRoleManagementPolicyRule{ @@ -1005,11 +1006,12 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie expirationRequired := policyRules["Expiration_Admin_Assignment"].IsExpirationRequired maximumDuration := policyRules["Expiration_Admin_Assignment"].MaximumDuration - if metadata.ResourceData.HasChange("active_assignment_rules.0.expiration_required") { - expirationRequired = pointer.To(model.EligbleAssignmentRules[0].ExpirationRequired) + if *expirationRequired != model.ActiveAssignmentRules[0].ExpirationRequired { + expirationRequired = pointer.To(model.ActiveAssignmentRules[0].ExpirationRequired) } - if metadata.ResourceData.HasChange("active_assignment_rules.0.expire_after") { - maximumDuration = pointer.To(model.EligbleAssignmentRules[0].ExpireAfter) + if *maximumDuration != model.ActiveAssignmentRules[0].ExpireAfter && + model.ActiveAssignmentRules[0].ExpireAfter != "" { + maximumDuration = pointer.To(model.ActiveAssignmentRules[0].ExpireAfter) } rule := msgraph.UnifiedRoleManagementPolicyRule{ @@ -1027,16 +1029,20 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie ID: policyRules["Expiration_EndUser_Assignment"].ID, ODataType: policyRules["Expiration_EndUser_Assignment"].ODataType, Target: policyRules["Expiration_EndUser_Assignment"].Target, - MaximumDuration: pointer.To(model.EligbleAssignmentRules[0].ExpireAfter), + MaximumDuration: pointer.To(model.ActivationRules[0].MaximumDuration), } updatedRules = append(updatedRules, rule) } if metadata.ResourceData.HasChange("activation_rules.0.require_approval") || metadata.ResourceData.HasChange("activation_rules.0.approval_stages") { + if model.ActivationRules[0].RequireApproval && len(model.ActivationRules[0].ApprovalStages[0].PrimaryApprovers) > 1 { + return nil, fmt.Errorf("require_approval is true, but no approvers are provided") + } + isApprovalRequired := policyRules["Approval_EndUser_Assignment"].Setting.IsApprovalRequired var approvalStages []msgraph.ApprovalStage - if metadata.ResourceData.HasChange("activation_rules.0.require_approval") { + if *isApprovalRequired != model.ActivationRules[0].RequireApproval { isApprovalRequired = pointer.To(model.ActivationRules[0].RequireApproval) } if metadata.ResourceData.HasChange("activation_rules.0.approval_stages") { @@ -1047,13 +1053,13 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie if approver.ObjectType == "user" { primaryApprovers = append(primaryApprovers, msgraph.UserSet{ ODataType: pointer.To("#microsoft.graph.singleUser"), - ID: &approver.ObjectId, + UserID: &approver.ObjectId, Description: &approver.Description, }) } else if approver.ObjectType == "group" { primaryApprovers = append(primaryApprovers, msgraph.UserSet{ ODataType: pointer.To("#microsoft.graph.groupMembers"), - ID: &approver.ObjectId, + GroupID: &approver.ObjectId, Description: &approver.Description, }) } else { @@ -1129,21 +1135,24 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie level := policyRules["Notification_Admin_Admin_Eligibility"].NotificationLevel defaultRecipients := policyRules["Notification_Admin_Admin_Eligibility"].IsDefaultRecipientsEnabled additionalRecipients := policyRules["Notification_Admin_Admin_Eligibility"].NotificationRecipients + data := model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0] - if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.eligible_assignments.0.notification_level") { - level = model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].NotificationLevel + if level != data.NotificationLevel { + level = data.NotificationLevel } - if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.eligible_assignments.0.default_recipients") { - defaultRecipients = pointer.To(model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].DefaultRecipients) + if *defaultRecipients != data.DefaultRecipients { + defaultRecipients = pointer.To(data.DefaultRecipients) } if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.eligible_assignments.0.additional_recipients") { - additionalRecipients = pointer.To(model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].AdditionalRecipients) + additionalRecipients = pointer.To(data.AdditionalRecipients) } rule := msgraph.UnifiedRoleManagementPolicyRule{ ID: policyRules["Notification_Admin_Admin_Eligibility"].ID, ODataType: policyRules["Notification_Admin_Admin_Eligibility"].ODataType, Target: policyRules["Notification_Admin_Admin_Eligibility"].Target, + RecipientType: policyRules["Notification_Admin_Admin_Eligibility"].RecipientType, + NotificationType: policyRules["Notification_Admin_Admin_Eligibility"].NotificationType, NotificationLevel: level, IsDefaultRecipientsEnabled: defaultRecipients, NotificationRecipients: additionalRecipients, @@ -1156,20 +1165,24 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie defaultRecipients := policyRules["Notification_Admin_Admin_Assignment"].IsDefaultRecipientsEnabled additionalRecipients := policyRules["Notification_Admin_Admin_Assignment"].NotificationRecipients - if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.active_assignments.0.notification_level") { - level = model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0].NotificationLevel + data := model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0] + + if level != data.NotificationLevel { + level = data.NotificationLevel } - if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.active_assignments.0.default_recipients") { - defaultRecipients = pointer.To(model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0].DefaultRecipients) + if *defaultRecipients != data.DefaultRecipients { + defaultRecipients = pointer.To(data.DefaultRecipients) } if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.active_assignments.0.additional_recipients") { - additionalRecipients = pointer.To(model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0].AdditionalRecipients) + additionalRecipients = pointer.To(data.AdditionalRecipients) } rule := msgraph.UnifiedRoleManagementPolicyRule{ ID: policyRules["Notification_Admin_Admin_Assignment"].ID, ODataType: policyRules["Notification_Admin_Admin_Assignment"].ODataType, Target: policyRules["Notification_Admin_Admin_Assignment"].Target, + RecipientType: policyRules["Notification_Admin_Admin_Assignment"].RecipientType, + NotificationType: policyRules["Notification_Admin_Admin_Assignment"].NotificationType, NotificationLevel: level, IsDefaultRecipientsEnabled: defaultRecipients, NotificationRecipients: additionalRecipients, @@ -1181,21 +1194,24 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie level := policyRules["Notification_Admin_EndUser_Assignment"].NotificationLevel defaultRecipients := policyRules["Notification_Admin_EndUser_Assignment"].IsDefaultRecipientsEnabled additionalRecipients := policyRules["Notification_Admin_EndUser_Assignment"].NotificationRecipients + data := model.NotificationRules[0].AdminNotifications[0].Activations[0] - if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.activations.0.notification_level") { - level = model.NotificationRules[0].AdminNotifications[0].Activations[0].NotificationLevel + if level != data.NotificationLevel { + level = data.NotificationLevel } - if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.activations.0.default_recipients") { - defaultRecipients = pointer.To(model.NotificationRules[0].AdminNotifications[0].Activations[0].DefaultRecipients) + if *defaultRecipients != data.DefaultRecipients { + defaultRecipients = pointer.To(data.DefaultRecipients) } if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.activations.0.additional_recipients") { - additionalRecipients = pointer.To(model.NotificationRules[0].AdminNotifications[0].Activations[0].AdditionalRecipients) + additionalRecipients = pointer.To(data.AdditionalRecipients) } rule := msgraph.UnifiedRoleManagementPolicyRule{ ID: policyRules["Notification_Admin_EndUser_Assignment"].ID, ODataType: policyRules["Notification_Admin_EndUser_Assignment"].ODataType, Target: policyRules["Notification_Admin_EndUser_Assignment"].Target, + RecipientType: policyRules["Notification_Admin_EndUser_Assignment"].RecipientType, + NotificationType: policyRules["Notification_Admin_EndUser_Assignment"].NotificationType, NotificationLevel: level, IsDefaultRecipientsEnabled: defaultRecipients, NotificationRecipients: additionalRecipients, @@ -1207,21 +1223,24 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie level := policyRules["Notification_Approver_Admin_Eligibility"].NotificationLevel defaultRecipients := policyRules["Notification_Approver_Admin_Eligibility"].IsDefaultRecipientsEnabled additionalRecipients := policyRules["Notification_Approver_Admin_Eligibility"].NotificationRecipients + data := model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments[0] - if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.eligible_assignments.0.notification_level") { - level = model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].NotificationLevel + if level != data.NotificationLevel { + level = data.NotificationLevel } - if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.eligible_assignments.0.default_recipients") { - defaultRecipients = pointer.To(model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].DefaultRecipients) + if *defaultRecipients != data.DefaultRecipients { + defaultRecipients = pointer.To(data.DefaultRecipients) } if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.eligible_assignments.0.additional_recipients") { - additionalRecipients = pointer.To(model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].AdditionalRecipients) + additionalRecipients = pointer.To(data.AdditionalRecipients) } rule := msgraph.UnifiedRoleManagementPolicyRule{ ID: policyRules["Notification_Approver_Admin_Eligibility"].ID, ODataType: policyRules["Notification_Approver_Admin_Eligibility"].ODataType, Target: policyRules["Notification_Approver_Admin_Eligibility"].Target, + RecipientType: policyRules["Notification_Approver_Admin_Eligibility"].RecipientType, + NotificationType: policyRules["Notification_Approver_Admin_Eligibility"].NotificationType, NotificationLevel: level, IsDefaultRecipientsEnabled: defaultRecipients, NotificationRecipients: additionalRecipients, @@ -1233,21 +1252,24 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie level := policyRules["Notification_Approver_Admin_Assignment"].NotificationLevel defaultRecipients := policyRules["Notification_Approver_Admin_Assignment"].IsDefaultRecipientsEnabled additionalRecipients := policyRules["Notification_Approver_Admin_Assignment"].NotificationRecipients + data := model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0] - if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.active_assignments.0.notification_level") { - level = model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0].NotificationLevel + if level != data.NotificationLevel { + level = data.NotificationLevel } - if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.active_assignments.0.default_recipients") { - defaultRecipients = pointer.To(model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0].DefaultRecipients) + if *defaultRecipients != data.DefaultRecipients { + defaultRecipients = pointer.To(data.DefaultRecipients) } if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.active_assignments.0.additional_recipients") { - additionalRecipients = pointer.To(model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0].AdditionalRecipients) + additionalRecipients = pointer.To(data.AdditionalRecipients) } rule := msgraph.UnifiedRoleManagementPolicyRule{ ID: policyRules["Notification_Approver_Admin_Assignment"].ID, ODataType: policyRules["Notification_Approver_Admin_Assignment"].ODataType, Target: policyRules["Notification_Approver_Admin_Assignment"].Target, + RecipientType: policyRules["Notification_Approver_Admin_Assignment"].RecipientType, + NotificationType: policyRules["Notification_Approver_Admin_Assignment"].NotificationType, NotificationLevel: level, IsDefaultRecipientsEnabled: defaultRecipients, NotificationRecipients: additionalRecipients, @@ -1259,21 +1281,24 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie level := policyRules["Notification_Approver_EndUser_Assignment"].NotificationLevel defaultRecipients := policyRules["Notification_Approver_EndUser_Assignment"].IsDefaultRecipientsEnabled additionalRecipients := policyRules["Notification_Approver_EndUser_Assignment"].NotificationRecipients + data := model.NotificationRules[0].ApproverNotifications[0].Activations[0] - if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.activations.0.notification_level") { - level = model.NotificationRules[0].ApproverNotifications[0].Activations[0].NotificationLevel + if level != data.NotificationLevel { + level = data.NotificationLevel } - if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.activations.0.default_recipients") { - defaultRecipients = pointer.To(model.NotificationRules[0].ApproverNotifications[0].Activations[0].DefaultRecipients) + if *defaultRecipients != data.DefaultRecipients { + defaultRecipients = pointer.To(data.DefaultRecipients) } if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.activations.0.additional_recipients") { - additionalRecipients = pointer.To(model.NotificationRules[0].ApproverNotifications[0].Activations[0].AdditionalRecipients) + additionalRecipients = pointer.To(data.AdditionalRecipients) } rule := msgraph.UnifiedRoleManagementPolicyRule{ ID: policyRules["Notification_Approver_EndUser_Assignment"].ID, ODataType: policyRules["Notification_Approver_EndUser_Assignment"].ODataType, Target: policyRules["Notification_Approver_EndUser_Assignment"].Target, + RecipientType: policyRules["Notification_Approver_EndUser_Assignment"].RecipientType, + NotificationType: policyRules["Notification_Approver_EndUser_Assignment"].NotificationType, NotificationLevel: level, IsDefaultRecipientsEnabled: defaultRecipients, NotificationRecipients: additionalRecipients, @@ -1285,21 +1310,24 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie level := policyRules["Notification_Requestor_Admin_Eligibility"].NotificationLevel defaultRecipients := policyRules["Notification_Requestor_Admin_Eligibility"].IsDefaultRecipientsEnabled additionalRecipients := policyRules["Notification_Requestor_Admin_Eligibility"].NotificationRecipients + data := model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0] - if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.eligible_assignments.0.notification_level") { - level = model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0].NotificationLevel + if level != data.NotificationLevel { + level = data.NotificationLevel } - if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.eligible_assignments.0.default_recipients") { - defaultRecipients = pointer.To(model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0].DefaultRecipients) + if *defaultRecipients != data.DefaultRecipients { + defaultRecipients = pointer.To(data.DefaultRecipients) } if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.eligible_assignments.0.additional_recipients") { - additionalRecipients = pointer.To(model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0].AdditionalRecipients) + additionalRecipients = pointer.To(data.AdditionalRecipients) } rule := msgraph.UnifiedRoleManagementPolicyRule{ ID: policyRules["Notification_Requestor_Admin_Eligibility"].ID, ODataType: policyRules["Notification_Requestor_Admin_Eligibility"].ODataType, Target: policyRules["Notification_Requestor_Admin_Eligibility"].Target, + RecipientType: policyRules["Notification_Requestor_Admin_Eligibility"].RecipientType, + NotificationType: policyRules["Notification_Requestor_Admin_Eligibility"].NotificationType, NotificationLevel: level, IsDefaultRecipientsEnabled: defaultRecipients, NotificationRecipients: additionalRecipients, @@ -1311,21 +1339,24 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie level := policyRules["Notification_Requestor_Admin_Assignment"].NotificationLevel defaultRecipients := policyRules["Notification_Requestor_Admin_Assignment"].IsDefaultRecipientsEnabled additionalRecipients := policyRules["Notification_Requestor_Admin_Assignment"].NotificationRecipients + data := model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0] - if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.active_assignments.0.notification_level") { - level = model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0].NotificationLevel + if level != data.NotificationLevel { + level = data.NotificationLevel } - if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.active_assignments.0.default_recipients") { - defaultRecipients = pointer.To(model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0].DefaultRecipients) + if *defaultRecipients != data.DefaultRecipients { + defaultRecipients = pointer.To(data.DefaultRecipients) } if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.active_assignments.0.additional_recipients") { - additionalRecipients = pointer.To(model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0].AdditionalRecipients) + additionalRecipients = pointer.To(data.AdditionalRecipients) } rule := msgraph.UnifiedRoleManagementPolicyRule{ ID: policyRules["Notification_Requestor_Admin_Assignment"].ID, ODataType: policyRules["Notification_Requestor_Admin_Assignment"].ODataType, Target: policyRules["Notification_Requestor_Admin_Assignment"].Target, + RecipientType: policyRules["Notification_Requestor_Admin_Assignment"].RecipientType, + NotificationType: policyRules["Notification_Requestor_Admin_Assignment"].NotificationType, NotificationLevel: level, IsDefaultRecipientsEnabled: defaultRecipients, NotificationRecipients: additionalRecipients, @@ -1337,21 +1368,24 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie level := policyRules["Notification_Requestor_EndUser_Assignment"].NotificationLevel defaultRecipients := policyRules["Notification_Requestor_EndUser_Assignment"].IsDefaultRecipientsEnabled additionalRecipients := policyRules["Notification_Requestor_EndUser_Assignment"].NotificationRecipients + data := model.NotificationRules[0].AssigneeNotifications[0].Activations[0] - if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.activations.0.notification_level") { - level = model.NotificationRules[0].AssigneeNotifications[0].Activations[0].NotificationLevel + if level != data.NotificationLevel { + level = data.NotificationLevel } - if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.activations.0.default_recipients") { - defaultRecipients = pointer.To(model.NotificationRules[0].AssigneeNotifications[0].Activations[0].DefaultRecipients) + if *defaultRecipients != data.DefaultRecipients { + defaultRecipients = pointer.To(data.DefaultRecipients) } if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.activations.0.additional_recipients") { - additionalRecipients = pointer.To(model.NotificationRules[0].AssigneeNotifications[0].Activations[0].AdditionalRecipients) + additionalRecipients = pointer.To(data.AdditionalRecipients) } rule := msgraph.UnifiedRoleManagementPolicyRule{ ID: policyRules["Notification_Requestor_EndUser_Assignment"].ID, ODataType: policyRules["Notification_Requestor_EndUser_Assignment"].ODataType, Target: policyRules["Notification_Requestor_EndUser_Assignment"].Target, + RecipientType: policyRules["Notification_Requestor_EndUser_Assignment"].RecipientType, + NotificationType: policyRules["Notification_Requestor_EndUser_Assignment"].NotificationType, NotificationLevel: level, IsDefaultRecipientsEnabled: defaultRecipients, NotificationRecipients: additionalRecipients, diff --git a/internal/services/policies/group_role_management_policy_resource_test.go b/internal/services/policies/group_role_management_policy_resource_test.go new file mode 100644 index 0000000000..086f92358f --- /dev/null +++ b/internal/services/policies/group_role_management_policy_resource_test.go @@ -0,0 +1,162 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package policies_test + +import ( + "context" + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/terraform-plugin-testing/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" +) + +type GroupRoleManagementPolicyResource struct{} + +func TestGroupRoleManagementPolicy_member(t *testing.T) { + data := acceptance.BuildTestData(t, "azuread_group_role_management_policy", "test") + r := GroupRoleManagementPolicyResource{} + + // Ignore the dangling resource post-test as the policy remains while the group is in a pending deletion state + data.ResourceTestIgnoreDangling(t, r, []acceptance.TestStep{ + { + Config: r.member(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("active_assignment_rules.0.expire_after").Exists(), + check.That(data.ResourceName).Key("eligible_assignment_rules.0.expiration_required").Exists(), + ), + }, + }) +} + +func TestGroupRoleManagementPolicy_owner(t *testing.T) { + data := acceptance.BuildTestData(t, "azuread_group_role_management_policy", "test") + r := GroupRoleManagementPolicyResource{} + + // Ignore the dangling resource post-test as the policy remains while the group is in a pending deletion state + data.ResourceTestIgnoreDangling(t, r, []acceptance.TestStep{ + { + Config: r.owner(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("active_assignment_rules.0.expire_after").Exists(), + check.That(data.ResourceName).Key("eligible_assignment_rules.0.expiration_required").Exists(), + ), + }, + }) + +} + +func (GroupRoleManagementPolicyResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { + client := clients.Policies.RoleManagementPolicyClient + client.BaseClient.DisableRetries = true + defer func() { client.BaseClient.DisableRetries = false }() + + _, status, err := client.Get(ctx, state.ID) + if err != nil { + if status == http.StatusNotFound { + return pointer.To(false), nil + } + return nil, fmt.Errorf("failed to retrieve role management policy with ID %q: %+v", state.ID, err) + } + + return pointer.To(true), nil +} + +func (GroupRoleManagementPolicyResource) member(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azuread" {} + +resource "azuread_group" "pam" { + display_name = "PAM Member Test %[1]s" + mail_enabled = false + security_enabled = true +} + +resource "azuread_group_role_management_policy" "test" { + object_id = azuread_group.pam.object_id + assignment_type = "member" + + eligible_assignment_rules { + expiration_required = false + } + + active_assignment_rules { + expire_after = "P365D" + } + + notification_rules { + approver_notifications { + eligible_assignments { + notification_level = "Critical" + default_recipients = false + additional_recipients = ["someone@example.com"] + } + } + } +} +`, data.RandomString) +} + +func (GroupRoleManagementPolicyResource) owner(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azuread" {} + +data "azuread_domains" "test" { + only_initial = true +} + +resource "azuread_user" "approver" { + user_principal_name = "pam-approver-%[1]s@${data.azuread_domains.test.domains.0.domain_name}" + display_name = "PAM Approver Test %[1]s" + password = "%[2]s" +} + +resource "azuread_group" "pam" { + display_name = "PAM Owner Test %[1]s" + mail_enabled = false + security_enabled = true +} + +resource "azuread_group_role_management_policy" "test" { + object_id = azuread_group.pam.object_id + assignment_type = "owner" + + eligible_assignment_rules { + expiration_required = false + } + + active_assignment_rules { + expire_after = "P90D" + } + + activation_rules { + maximum_duration = "PT1H" + require_approval = true + approval_stages { + primary_approver { + description = azuread_user.approver.display_name + object_id = azuread_user.approver.object_id + object_type = "user" + } + } + } + + notification_rules { + admin_notifications { + active_assignments { + notification_level = "Critical" + default_recipients = false + additional_recipients = ["someone@example.com"] + } + } + } +} +`, data.RandomString, data.RandomPassword) +} From 8dede2172e4a050f826c3e0b3846dbc70e1d04da Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Tue, 5 Mar 2024 15:41:52 +1300 Subject: [PATCH 23/55] Add documentation Rename some fields to make more sense --- .../resources/group_role_management_policy.md | 173 ++++++++++++++++++ .../group_role_management_policy_resource.go | 62 ++++--- ...up_role_management_policy_resource_test.go | 3 +- 3 files changed, 211 insertions(+), 27 deletions(-) create mode 100644 docs/resources/group_role_management_policy.md diff --git a/docs/resources/group_role_management_policy.md b/docs/resources/group_role_management_policy.md new file mode 100644 index 0000000000..fa15dcffe3 --- /dev/null +++ b/docs/resources/group_role_management_policy.md @@ -0,0 +1,173 @@ +--- +subcategory: "Policies" +--- + +# Resource: azuread_group_role_management_policy + +Manage a role policy for an Azure AD group. + +## API Permissions + +The following API permissions are required in order to use this resource. + +When authenticated with a service principal, this resource requires the `RoleManagementPolicy.ReadWrite.AzureADGroup` Microsoft Graph API permissions. + +When authenticated with a user principal, this resource requires `Global Administrator` directory role, or the `Privileged Role Administrator` role in Identity Governance. + +## Example Usage + +```terraform +resource "azuread_group" "example" { + display_name = "group-name" + security_enabled = true +} + +resource "azuread_user" "member" { + user_principal_name = "jdoe@hashicorp.com" + display_name = "J. Doe" + mail_nickname = "jdoe" + password = "SecretP@sswd99!" +} + +resource "azuread_group_role_management_policy" "example" { + object_id = azuread_group.example.id + assignment_type = "member" + + eligible_assignment_rules { + expiration_required = false + } + + active_assignment_rules { + expire_after = "P365D" + } + + notification_rules { + approver_notifications { + eligible_assignments { + notification_level = "Critical" + default_recipients = false + additional_recipients = [ + "someone@example.com", + "someone.else@example.com", + ] + } + } + } +} +``` + +## Argument Reference + +* `object_id` - (Required) The Object ID of the Azure AD group for which the policy applies. +* `assignment_type` - (Required) The type of assignment this policy coveres. Can be either `member` or `owner`. +* `active_assignment_rules` - (Optional) An `active_assignment_rules` block as defined below. +* `activation_rules` - (Optional) An `activation_rules` block as defined below. +* `eligible_assignment_rules` - (Optional) An `eligible_assignment_rules` block as defined below. +* `notification_rules` - (Optional) An `notification_rules` block as defined below. + +--- + +An `active_assignment_rules` block supports the following: + +* `expiration_required` - (Optional) Must an assignment have an expiry date. `false` allows permanent assignment. +* `expire_after` - (Optional) The maximum length of time an assignment can be valid, as an ISO8601 duration. Permitted values: `P15D`, `P30D`, `P90D`, `P180D`, or `P365D`. +* `require_multifactor_authentication` - (Optional) Is multi-factor authentication required to create new assignments. +* `require_justification` - (Optional) Is a justification required to create new assignments. +* `require_ticket_info` - (Optional) Is ticket information required to create new assignments. + +One of `expiration_required` or `expire_after` must be provided. + +--- + +An `activation_rules` block supports the following: + +* `maximum_duration` - (Optional) The maximum length of time an activated role can be valid, in an IS)8601 Duration format (e.g. `PT8H`). Valid range is `PT30M` to `PT23H30M`, in 30 minute increments, or `PT1D`. +* `approval_stages` - (Optional) An `approval_stages` block as defined below. +* `require_approval` - (Optional) Is approval required for activation. If `true` an `approval_stages` block must be provided. +* `required_conditional_access_authentication_context` - (Optional) The Entra ID Conditional Access context that must be present for activation. Conflicts with `require_multifactor_authentication`. +* `require_multifactor_authentication` - (Optional) Is multi-factor authentication required to activate the role. Conflicts with `required_conditional_access_authentication_context`. +* `require_justification` - (Optional) Is a justification required during activation of the role. +* `require_ticket_info` - (Optional) Is ticket information requrired during activation of the role. + +--- + +An `admin_notifications` block supports the following: + +* `activations` An optional `notification_settings` block as defined below for configuring notifications to adminstrators of role activations. +* `active_assignments` An optional `notification_settings` block as defined below for configuring notifications to adminstrators of new active assignments. +* `eligible_assignments` An optional `notification_settings` block as defined below for configuring notifications to adminstrators of new eligible assignments. + +--- + +An `approval_stages` block supports the following: + +* One or more `primary_approver` blocks as defined below. + +--- + +An `approver_notifications` block supports the following: + +* `activations` - An optional `notification_settings` block as defined below for configuring notifications to approvers of new activation request. +* `active_assignments` - An optional `notification_settings` block as defined below for configuring notifications to approvers of new active assignment requests. +* `eligible_assignments` - An optional `notification_settings` block as defined below for configuring notifications to approvers of new eligible assignment requests. + +--- + +An `assignee_notifications` block supports the following: + +* `activations` - An optional `notification_settings` block as defined below for configuring notifications to assignees of role activations. +* `active_assignments` - An optional `notification_settings` block as defined below for configuring notifications to assignees of new active assignments. +* `eligible_assignments` - An optional `notification_settings` block as defined below for configuring notifications to assignees of new eligible assignments. + +--- + +An `eligible_assignment_rules` block supports the following: + +* `expiration_required`- Must an assignment have an expiry date. `false` allows permanent assignment. +* `expire_after` - The maximum length of time an assignment can be valid, as an ISO8601 duration. Permitted values: `P15D`, `P30D`, `P90D`, `P180D`, or `P365D`. + +One of `expiration_required` or `expire_after` must be provided. + +--- + +A `notification_rules` block supports the following: + +* `admin_notifications` - (Optional) An `admin_notifications` block as defined above. +* `approver_notifications` - (Optional) An `approver_notifications` block as defined above. +* `assignee_notifications` - (Optional) An `assignee_notifications` block as defined above. + +--- + +A `notification_settings` block supports the following: + +* `notification_level` - (Required) What level of notifications should be sent. Options are `All` or `Critical`. +* `default_recipients` - (Required) Should the default recipients receive these notifications. +* `additional_recipients` - (Optional) A list of additional email addresses that will receive these notifications. + +--- + +A `primary_approver` block supports the following: + +* `user_id` - (Required) The ID of the user or group which will act as an approver. +* `group_id` - (Required) The ID of the user or group which will act as an approver. +* `description` - (Required) A description of the approver. + +Only one of `user_id` or `group_id` can be supplied per block. Multiple approvers can be set by providing multiple `primary_approver` blocks. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` (String) The ID of this policy. +* `display_name` (String) The display name of this policy. +* `description` (String) The description of this policy. + +## Import + +An assignment schedule can be imported using the ID, e.g. + +```shell +terraform import azuread_privileged_access_group_eligibility_schedule_request.example Group_00000000-0000-0000-0000-000000000000_00000000-0000-0000-0000-000000000000 +``` + +Because these policies are created automatically by Entra ID, they will auto-import on first use. diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index 29e65ade84..cdb95ecd5b 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -33,6 +33,7 @@ type GroupRoleManagementPolicyActiveAssignmentRules struct { ExpireAfter string `tfschema:"expire_after"` RequireMultiFactorAuth bool `tfschema:"require_multifactor_authentication"` RequireJustification bool `tfschema:"require_justification"` + RequireTicketInfo bool `tfschema:"require_ticket_info"` } type GroupRoleManagementPolicyEligibleAssignmentRules struct { @@ -44,7 +45,7 @@ type GroupRoleManagementPolicyActivationRules struct { MaximumDuration string `tfschema:"maximum_duration"` RequireApproval bool `tfschema:"require_approval"` ApprovalStages []GroupRoleManagementPolicyApprovalStage `tfschema:"approval_stages"` - RequireConditionalAccessContext string `tfschema:"require_conditional_access_authentication_context"` + RequireConditionalAccessContext string `tfschema:"required_conditional_access_authentication_context"` RequireMultiFactorAuth bool `tfschema:"require_multifactor_authentication"` RequireJustification bool `tfschema:"require_justification"` RequireTicketInfo bool `tfschema:"require_ticket_info"` @@ -56,8 +57,8 @@ type GroupRoleManagementPolicyApprovalStage struct { type GroupRoleManagementPolicyApprover struct { Description string `tfschema:"description"` - ObjectId string `tfschema:"object_id"` - ObjectType string `tfschema:"object_type"` + GroupId string `tfschema:"group_id"` + UserId string `tfschema:"user_id"` } type GroupRoleManagementPolicyNotificationRules struct { @@ -178,6 +179,13 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Optional: true, Computed: true, }, + + "require_ticket_info": { + Description: "Whether ticket information is required to make an assignment", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, }, }, }, @@ -231,18 +239,18 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), }, - "object_id": { - Description: "The ID of the useror group to act as an approver", + "group_id": { + Description: "The ID of the group to act as an approver", Type: pluginsdk.TypeString, - Required: true, + Optional: true, ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), }, - "object_type": { - Description: "The type of the object to act as an approver", + "user_id": { + Description: "The ID of the user to act as an approver", Type: pluginsdk.TypeString, - Required: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"user", "group"}, false)), + Optional: true, + ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), }, }, }, @@ -251,7 +259,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch }, }, - "require_conditional_access_authentication_context": { + "required_conditional_access_authentication_context": { Description: "Whether a conditional access context is required during activation", Type: pluginsdk.TypeString, Optional: true, @@ -265,7 +273,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Type: pluginsdk.TypeBool, Optional: true, Computed: true, - ConflictsWith: []string{"activation_rules.0.require_conditional_access_authentication_context"}, + ConflictsWith: []string{"activation_rules.0.required_conditional_access_authentication_context"}, }, "require_justification": { @@ -779,14 +787,12 @@ func (r GroupRoleManagementPolicyResource) Read() sdk.ResourceFunc { case *approver.ODataType == "#microsoft.graph.singleUser": primaryApprovers = append(primaryApprovers, GroupRoleManagementPolicyApprover{ Description: *approver.Description, - ObjectId: *approver.UserID, - ObjectType: "user", + UserId: *approver.UserID, }) case *approver.ODataType == "#microsoft.graph.groupMembers": primaryApprovers = append(primaryApprovers, GroupRoleManagementPolicyApprover{ Description: *approver.Description, - ObjectId: *approver.GroupID, - ObjectType: "group", + GroupId: *approver.GroupID, }) default: return fmt.Errorf("unknown approver type: %s", *approver.ODataType) @@ -991,6 +997,9 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie if model.ActiveAssignmentRules[0].RequireJustification { enabledRules = append(enabledRules, "Justification") } + if model.ActiveAssignmentRules[0].RequireTicketInfo { + enabledRules = append(enabledRules, "Ticketing") + } rule := msgraph.UnifiedRoleManagementPolicyRule{ ID: policyRules["Enablement_Admin_Assignment"].ID, @@ -1050,20 +1059,23 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie for _, stage := range model.ActivationRules[0].ApprovalStages { primaryApprovers := make([]msgraph.UserSet, 0) for _, approver := range stage.PrimaryApprovers { - if approver.ObjectType == "user" { + if approver.UserId != "" && approver.GroupId != "" { + return nil, fmt.Errorf("Only one of user_id or group_id can be set in a block") + } else if approver.UserId == "" && approver.GroupId == "" { + return nil, fmt.Errorf("One of user_id or group_id must be set in a block") + } + if approver.UserId != "" { primaryApprovers = append(primaryApprovers, msgraph.UserSet{ ODataType: pointer.To("#microsoft.graph.singleUser"), - UserID: &approver.ObjectId, + UserID: &approver.UserId, Description: &approver.Description, }) - } else if approver.ObjectType == "group" { + } else if approver.GroupId != "" { primaryApprovers = append(primaryApprovers, msgraph.UserSet{ - ODataType: pointer.To("#microsoft.graph.groupMembers"), - GroupID: &approver.ObjectId, + ODataType: pointer.To("#microsoft.graph.singleUser"), + GroupID: &approver.GroupId, Description: &approver.Description, }) - } else { - return nil, fmt.Errorf("either user_id or group_id must be set") } } @@ -1087,11 +1099,11 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie updatedRules = append(updatedRules, rule) } - if metadata.ResourceData.HasChange("activation_rules.0.require_conditional_access_authentication_context") { + if metadata.ResourceData.HasChange("activation_rules.0.required_conditional_access_authentication_context") { isEnabled := policyRules["AuthenticationContext_EndUser_Assignment"].IsEnabled claimValue := policyRules["AuthenticationContext_EndUser_Assignment"].ClaimValue - if _, set := metadata.ResourceData.GetOk("activation_rules.0.require_conditional_access_authentication_context"); set { + if _, set := metadata.ResourceData.GetOk("activation_rules.0.required_conditional_access_authentication_context"); set { isEnabled = pointer.To(true) claimValue = pointer.To(model.ActivationRules[0].RequireConditionalAccessContext) } else { diff --git a/internal/services/policies/group_role_management_policy_resource_test.go b/internal/services/policies/group_role_management_policy_resource_test.go index 086f92358f..b0a604c194 100644 --- a/internal/services/policies/group_role_management_policy_resource_test.go +++ b/internal/services/policies/group_role_management_policy_resource_test.go @@ -141,9 +141,8 @@ resource "azuread_group_role_management_policy" "test" { require_approval = true approval_stages { primary_approver { + user_id = azuread_user.approver.object_id description = azuread_user.approver.display_name - object_id = azuread_user.approver.object_id - object_type = "user" } } } From 65c76572f32aeb1392c627d2e215df556e38c8a3 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Wed, 6 Mar 2024 09:46:52 +1300 Subject: [PATCH 24/55] Rename `approval_stages` to `approval_stage` --- docs/resources/group_role_management_policy.md | 6 +++--- .../group_role_management_policy_resource.go | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/resources/group_role_management_policy.md b/docs/resources/group_role_management_policy.md index fa15dcffe3..75d5168742 100644 --- a/docs/resources/group_role_management_policy.md +++ b/docs/resources/group_role_management_policy.md @@ -82,8 +82,8 @@ One of `expiration_required` or `expire_after` must be provided. An `activation_rules` block supports the following: * `maximum_duration` - (Optional) The maximum length of time an activated role can be valid, in an IS)8601 Duration format (e.g. `PT8H`). Valid range is `PT30M` to `PT23H30M`, in 30 minute increments, or `PT1D`. -* `approval_stages` - (Optional) An `approval_stages` block as defined below. -* `require_approval` - (Optional) Is approval required for activation. If `true` an `approval_stages` block must be provided. +* `approval_stage` - (Optional) An `approval_stage` block as defined below. +* `require_approval` - (Optional) Is approval required for activation. If `true` an `approval_stage` block must be provided. * `required_conditional_access_authentication_context` - (Optional) The Entra ID Conditional Access context that must be present for activation. Conflicts with `require_multifactor_authentication`. * `require_multifactor_authentication` - (Optional) Is multi-factor authentication required to activate the role. Conflicts with `required_conditional_access_authentication_context`. * `require_justification` - (Optional) Is a justification required during activation of the role. @@ -99,7 +99,7 @@ An `admin_notifications` block supports the following: --- -An `approval_stages` block supports the following: +An `approval_stage` block supports the following: * One or more `primary_approver` blocks as defined below. diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index cdb95ecd5b..4adefb44d2 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -44,7 +44,7 @@ type GroupRoleManagementPolicyEligibleAssignmentRules struct { type GroupRoleManagementPolicyActivationRules struct { MaximumDuration string `tfschema:"maximum_duration"` RequireApproval bool `tfschema:"require_approval"` - ApprovalStages []GroupRoleManagementPolicyApprovalStage `tfschema:"approval_stages"` + ApprovalStages []GroupRoleManagementPolicyApprovalStage `tfschema:"approval_stage"` RequireConditionalAccessContext string `tfschema:"required_conditional_access_authentication_context"` RequireMultiFactorAuth bool `tfschema:"require_multifactor_authentication"` RequireJustification bool `tfschema:"require_justification"` @@ -218,7 +218,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Computed: true, }, - "approval_stages": { + "approval_stage": { Description: "The approval stages for the activation", Type: pluginsdk.TypeList, Optional: true, @@ -1044,9 +1044,9 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie } if metadata.ResourceData.HasChange("activation_rules.0.require_approval") || - metadata.ResourceData.HasChange("activation_rules.0.approval_stages") { - if model.ActivationRules[0].RequireApproval && len(model.ActivationRules[0].ApprovalStages[0].PrimaryApprovers) > 1 { - return nil, fmt.Errorf("require_approval is true, but no approvers are provided") + metadata.ResourceData.HasChange("activation_rules.0.approval_stage") { + if model.ActivationRules[0].RequireApproval && len(model.ActivationRules[0].ApprovalStages) != 1 { + return nil, fmt.Errorf("require_approval is true, but no approval_stages are provided") } isApprovalRequired := policyRules["Approval_EndUser_Assignment"].Setting.IsApprovalRequired @@ -1054,15 +1054,15 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie if *isApprovalRequired != model.ActivationRules[0].RequireApproval { isApprovalRequired = pointer.To(model.ActivationRules[0].RequireApproval) } - if metadata.ResourceData.HasChange("activation_rules.0.approval_stages") { + if metadata.ResourceData.HasChange("activation_rules.0.approval_stage") { approvalStages = make([]msgraph.ApprovalStage, 0) for _, stage := range model.ActivationRules[0].ApprovalStages { primaryApprovers := make([]msgraph.UserSet, 0) for _, approver := range stage.PrimaryApprovers { if approver.UserId != "" && approver.GroupId != "" { - return nil, fmt.Errorf("Only one of user_id or group_id can be set in a block") + return nil, fmt.Errorf("only one of user_id or group_id can be set in a primary_approver block") } else if approver.UserId == "" && approver.GroupId == "" { - return nil, fmt.Errorf("One of user_id or group_id must be set in a block") + return nil, fmt.Errorf("one of user_id or group_id must be set in a primary_approver block") } if approver.UserId != "" { primaryApprovers = append(primaryApprovers, msgraph.UserSet{ From 2a655fe7fc6158e382a3b921795121b5f275b4c7 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Wed, 6 Mar 2024 09:47:42 +1300 Subject: [PATCH 25/55] Ensure all additional_recipients blocks have an item --- .../policies/group_role_management_policy_resource.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index 4adefb44d2..5aa56b410c 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -335,6 +335,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Description: "The additional recipients to notify", Type: pluginsdk.TypeList, Optional: true, + MinItems: 1, Elem: &pluginsdk.Schema{ Type: pluginsdk.TypeString, }, @@ -370,6 +371,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Type: pluginsdk.TypeList, Optional: true, Computed: true, + MinItems: 1, Elem: &pluginsdk.Schema{ Type: pluginsdk.TypeString, }, @@ -404,6 +406,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Description: "The additional recipients to notify", Type: pluginsdk.TypeList, Optional: true, + MinItems: 1, Elem: &pluginsdk.Schema{ Type: pluginsdk.TypeString, }, @@ -449,6 +452,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Description: "The additional recipients to notify", Type: pluginsdk.TypeList, Optional: true, + MinItems: 1, Elem: &pluginsdk.Schema{ Type: pluginsdk.TypeString, }, @@ -483,6 +487,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Description: "The additional recipients to notify", Type: pluginsdk.TypeList, Optional: true, + MinItems: 1, Elem: &pluginsdk.Schema{ Type: pluginsdk.TypeString, }, @@ -517,6 +522,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Description: "The additional recipients to notify", Type: pluginsdk.TypeList, Optional: true, + MinItems: 1, Elem: &pluginsdk.Schema{ Type: pluginsdk.TypeString, }, @@ -559,6 +565,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Description: "The additional recipients to notify", Type: pluginsdk.TypeList, Optional: true, + MinItems: 1, Elem: &pluginsdk.Schema{ Type: pluginsdk.TypeString, }, From a6b7c042bb622ceb28d18a7321bcb2fe544e28e0 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Wed, 6 Mar 2024 10:16:46 +1300 Subject: [PATCH 26/55] Rename base `object_id` to `group_id` --- docs/resources/group_role_management_policy.md | 2 +- .../policies/group_role_management_policy_resource.go | 6 +++--- .../policies/group_role_management_policy_resource_test.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/resources/group_role_management_policy.md b/docs/resources/group_role_management_policy.md index 75d5168742..8dc18395df 100644 --- a/docs/resources/group_role_management_policy.md +++ b/docs/resources/group_role_management_policy.md @@ -58,7 +58,7 @@ resource "azuread_group_role_management_policy" "example" { ## Argument Reference -* `object_id` - (Required) The Object ID of the Azure AD group for which the policy applies. +* `group_id` - (Required) The ID of the Azure AD group for which the policy applies. * `assignment_type` - (Required) The type of assignment this policy coveres. Can be either `member` or `owner`. * `active_assignment_rules` - (Optional) An `active_assignment_rules` block as defined below. * `activation_rules` - (Optional) An `activation_rules` block as defined below. diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index 5aa56b410c..53e6bb31b7 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -20,7 +20,7 @@ import ( type GroupRoleManagementPolicyModel struct { Description string `tfschema:"description"` DisplayName string `tfschema:"display_name"` - GroupId string `tfschema:"object_id"` + GroupId string `tfschema:"group_id"` ScopeType msgraph.UnifiedRoleManagementPolicyScope `tfschema:"assignment_type"` ActiveAssignmentRules []GroupRoleManagementPolicyActiveAssignmentRules `tfschema:"active_assignment_rules"` EligibleAssignmentRules []GroupRoleManagementPolicyEligibleAssignmentRules `tfschema:"eligible_assignment_rules"` @@ -97,7 +97,7 @@ func (r GroupRoleManagementPolicyResource) ModelObject() interface{} { func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { return map[string]*pluginsdk.Schema{ - "object_id": { + "group_id": { Description: "ID of the group to which this policy is assigned", Type: pluginsdk.TypeString, Required: true, @@ -669,7 +669,7 @@ func (r GroupRoleManagementPolicyResource) Create() sdk.ResourceFunc { // Fetch the existing policy, as they already exist policies, _, err := assignmentClient.List(ctx, odata.Query{ - Filter: fmt.Sprintf("scopeId eq '%s' and scopeType eq 'Group' and roleDefinitionId eq '%s'", metadata.ResourceData.Get("object_id").(string), metadata.ResourceData.Get("assignment_type").(string)), + Filter: fmt.Sprintf("scopeId eq '%s' and scopeType eq 'Group' and roleDefinitionId eq '%s'", metadata.ResourceData.Get("group_id").(string), metadata.ResourceData.Get("assignment_type").(string)), }) if err != nil { return fmt.Errorf("Could not list existing policy, %+v", err) diff --git a/internal/services/policies/group_role_management_policy_resource_test.go b/internal/services/policies/group_role_management_policy_resource_test.go index b0a604c194..5570fa0e28 100644 --- a/internal/services/policies/group_role_management_policy_resource_test.go +++ b/internal/services/policies/group_role_management_policy_resource_test.go @@ -80,7 +80,7 @@ resource "azuread_group" "pam" { } resource "azuread_group_role_management_policy" "test" { - object_id = azuread_group.pam.object_id + group_id = azuread_group.pam.id assignment_type = "member" eligible_assignment_rules { @@ -125,7 +125,7 @@ resource "azuread_group" "pam" { } resource "azuread_group_role_management_policy" "test" { - object_id = azuread_group.pam.object_id + group_id = azuread_group.pam.id assignment_type = "owner" eligible_assignment_rules { From 78ac258e7e39f43779dc60d2e1700fb4a4607cf7 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Wed, 6 Mar 2024 10:17:16 +1300 Subject: [PATCH 27/55] Update primary_approver block to better match the underlying API --- .../resources/group_role_management_policy.md | 7 +-- .../group_role_management_policy_resource.go | 50 +++++++------------ ...up_role_management_policy_resource_test.go | 6 +-- 3 files changed, 23 insertions(+), 40 deletions(-) diff --git a/docs/resources/group_role_management_policy.md b/docs/resources/group_role_management_policy.md index 8dc18395df..1101b43bbd 100644 --- a/docs/resources/group_role_management_policy.md +++ b/docs/resources/group_role_management_policy.md @@ -148,11 +148,8 @@ A `notification_settings` block supports the following: A `primary_approver` block supports the following: -* `user_id` - (Required) The ID of the user or group which will act as an approver. -* `group_id` - (Required) The ID of the user or group which will act as an approver. -* `description` - (Required) A description of the approver. - -Only one of `user_id` or `group_id` can be supplied per block. Multiple approvers can be set by providing multiple `primary_approver` blocks. +* `object_id` - (Required) The ID of the object which will act as an approver. +* `type` - (Required) The type of object acting as an approver. Possible options are `singleUser` and `groupMember`. ## Attributes Reference diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index 53e6bb31b7..a4e783d9b8 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -56,9 +56,8 @@ type GroupRoleManagementPolicyApprovalStage struct { } type GroupRoleManagementPolicyApprover struct { - Description string `tfschema:"description"` - GroupId string `tfschema:"group_id"` - UserId string `tfschema:"user_id"` + ID string `tfschema:"object_id"` + Type string `tfschema:"type"` } type GroupRoleManagementPolicyNotificationRules struct { @@ -232,25 +231,18 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch MinItems: 1, Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ - "description": { - Description: "The description of the approver", + "object_id": { + Description: "The ID of the object to act as an approver", Type: pluginsdk.TypeString, Required: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), - }, - - "group_id": { - Description: "The ID of the group to act as an approver", - Type: pluginsdk.TypeString, - Optional: true, ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), }, - "user_id": { - Description: "The ID of the user to act as an approver", + "type": { + Description: "The type of object acting as an approver", Type: pluginsdk.TypeString, Optional: true, - ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"singleUser", "groupMembers"}, false)), }, }, }, @@ -793,13 +785,13 @@ func (r GroupRoleManagementPolicyResource) Read() sdk.ResourceFunc { switch { case *approver.ODataType == "#microsoft.graph.singleUser": primaryApprovers = append(primaryApprovers, GroupRoleManagementPolicyApprover{ - Description: *approver.Description, - UserId: *approver.UserID, + ID: *approver.UserID, + Type: "singleUser", }) case *approver.ODataType == "#microsoft.graph.groupMembers": primaryApprovers = append(primaryApprovers, GroupRoleManagementPolicyApprover{ - Description: *approver.Description, - GroupId: *approver.GroupID, + ID: *approver.GroupID, + Type: "groupMembers", }) default: return fmt.Errorf("unknown approver type: %s", *approver.ODataType) @@ -1066,22 +1058,16 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie for _, stage := range model.ActivationRules[0].ApprovalStages { primaryApprovers := make([]msgraph.UserSet, 0) for _, approver := range stage.PrimaryApprovers { - if approver.UserId != "" && approver.GroupId != "" { - return nil, fmt.Errorf("only one of user_id or group_id can be set in a primary_approver block") - } else if approver.UserId == "" && approver.GroupId == "" { - return nil, fmt.Errorf("one of user_id or group_id must be set in a primary_approver block") - } - if approver.UserId != "" { + switch approver.Type { + case "singleUser": primaryApprovers = append(primaryApprovers, msgraph.UserSet{ - ODataType: pointer.To("#microsoft.graph.singleUser"), - UserID: &approver.UserId, - Description: &approver.Description, + ODataType: pointer.To("#microsoft.graph.singleUser"), + UserID: &approver.ID, }) - } else if approver.GroupId != "" { + case "groupMembers": primaryApprovers = append(primaryApprovers, msgraph.UserSet{ - ODataType: pointer.To("#microsoft.graph.singleUser"), - GroupID: &approver.GroupId, - Description: &approver.Description, + ODataType: pointer.To("#microsoft.graph.groupMembers"), + GroupID: &approver.ID, }) } } diff --git a/internal/services/policies/group_role_management_policy_resource_test.go b/internal/services/policies/group_role_management_policy_resource_test.go index 5570fa0e28..7b0150124a 100644 --- a/internal/services/policies/group_role_management_policy_resource_test.go +++ b/internal/services/policies/group_role_management_policy_resource_test.go @@ -139,10 +139,10 @@ resource "azuread_group_role_management_policy" "test" { activation_rules { maximum_duration = "PT1H" require_approval = true - approval_stages { + approval_stage { primary_approver { - user_id = azuread_user.approver.object_id - description = azuread_user.approver.display_name + object_id = azuread_user.approver.object_id + type = "singleUser" } } } From 0fd0004c948446db98cfd605fa3c9c543f01e29e Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Wed, 6 Mar 2024 11:24:04 +1300 Subject: [PATCH 28/55] Fix duplicate `approval_stages` being saved in the state file --- .../group_role_management_policy_resource.go | 40 +++++++++---------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index a4e783d9b8..f0af528b3f 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -778,29 +778,25 @@ func (r GroupRoleManagementPolicyResource) Read() sdk.ResourceFunc { case "Approval_EndUser_Assignment": model.ActivationRules[0].RequireApproval = *rule.Setting.IsApprovalRequired - model.ActivationRules[0].ApprovalStages = make([]GroupRoleManagementPolicyApprovalStage, 0) - for _, stage := range *rule.Setting.ApprovalStages { - primaryApprovers := make([]GroupRoleManagementPolicyApprover, 0) - for _, approver := range *stage.PrimaryApprovers { - switch { - case *approver.ODataType == "#microsoft.graph.singleUser": - primaryApprovers = append(primaryApprovers, GroupRoleManagementPolicyApprover{ - ID: *approver.UserID, - Type: "singleUser", - }) - case *approver.ODataType == "#microsoft.graph.groupMembers": - primaryApprovers = append(primaryApprovers, GroupRoleManagementPolicyApprover{ - ID: *approver.GroupID, - Type: "groupMembers", - }) - default: - return fmt.Errorf("unknown approver type: %s", *approver.ODataType) - } - model.ActivationRules[0].ApprovalStages = append(model.ActivationRules[0].ApprovalStages, GroupRoleManagementPolicyApprovalStage{ - PrimaryApprovers: primaryApprovers, + + primaryApprovers := make([]GroupRoleManagementPolicyApprover, 0) + for _, approver := range *(*rule.Setting.ApprovalStages)[0].PrimaryApprovers { + switch { + case *approver.ODataType == "#microsoft.graph.singleUser": + primaryApprovers = append(primaryApprovers, GroupRoleManagementPolicyApprover{ + ID: pointer.ToString(approver.UserID), + Type: "singleUser", + }) + case *approver.ODataType == "#microsoft.graph.groupMembers": + primaryApprovers = append(primaryApprovers, GroupRoleManagementPolicyApprover{ + ID: pointer.ToString(approver.GroupID), + Type: "groupMembers", }) + default: + return fmt.Errorf("unknown approver type: %s", *approver.ODataType) } } + model.ActivationRules[0].ApprovalStages = []GroupRoleManagementPolicyApprovalStage{{PrimaryApprovers: primaryApprovers}} case "AuthenticationContext_EndUser_Assignment": if rule.ClaimValue != nil && *rule.ClaimValue != "" { @@ -1062,12 +1058,12 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie case "singleUser": primaryApprovers = append(primaryApprovers, msgraph.UserSet{ ODataType: pointer.To("#microsoft.graph.singleUser"), - UserID: &approver.ID, + UserID: pointer.To(approver.ID), }) case "groupMembers": primaryApprovers = append(primaryApprovers, msgraph.UserSet{ ODataType: pointer.To("#microsoft.graph.groupMembers"), - GroupID: &approver.ID, + GroupID: pointer.To(approver.ID), }) } } From 2dd6c6a95b34163dadcb287c1e64c0c10145be89 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Wed, 6 Mar 2024 11:41:26 +1300 Subject: [PATCH 29/55] Allow `additional_recipients` to be an empty list --- .../group_role_management_policy_resource.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index f0af528b3f..6be491e474 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -327,7 +327,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Description: "The additional recipients to notify", Type: pluginsdk.TypeList, Optional: true, - MinItems: 1, + Computed: true, Elem: &pluginsdk.Schema{ Type: pluginsdk.TypeString, }, @@ -363,7 +363,6 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Type: pluginsdk.TypeList, Optional: true, Computed: true, - MinItems: 1, Elem: &pluginsdk.Schema{ Type: pluginsdk.TypeString, }, @@ -398,7 +397,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Description: "The additional recipients to notify", Type: pluginsdk.TypeList, Optional: true, - MinItems: 1, + Computed: true, Elem: &pluginsdk.Schema{ Type: pluginsdk.TypeString, }, @@ -444,7 +443,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Description: "The additional recipients to notify", Type: pluginsdk.TypeList, Optional: true, - MinItems: 1, + Computed: true, Elem: &pluginsdk.Schema{ Type: pluginsdk.TypeString, }, @@ -479,7 +478,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Description: "The additional recipients to notify", Type: pluginsdk.TypeList, Optional: true, - MinItems: 1, + Computed: true, Elem: &pluginsdk.Schema{ Type: pluginsdk.TypeString, }, @@ -514,7 +513,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Description: "The additional recipients to notify", Type: pluginsdk.TypeList, Optional: true, - MinItems: 1, + Computed: true, Elem: &pluginsdk.Schema{ Type: pluginsdk.TypeString, }, @@ -557,7 +556,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Description: "The additional recipients to notify", Type: pluginsdk.TypeList, Optional: true, - MinItems: 1, + Computed: true, Elem: &pluginsdk.Schema{ Type: pluginsdk.TypeString, }, @@ -589,6 +588,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Description: "The additional recipients to notify", Type: pluginsdk.TypeList, Optional: true, + Computed: true, Elem: &pluginsdk.Schema{ Type: pluginsdk.TypeString, }, @@ -620,6 +620,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Description: "The additional recipients to notify", Type: pluginsdk.TypeList, Optional: true, + Computed: true, Elem: &pluginsdk.Schema{ Type: pluginsdk.TypeString, }, From 13187985e1a9aaaa5ec9de8c9a56747bc688a364 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Wed, 6 Mar 2024 11:47:52 +1300 Subject: [PATCH 30/55] Convert Lists to Sets where order doesn't matter --- .../group_role_management_policy_resource.go | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index 6be491e474..46601f4ce4 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -226,7 +226,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Schema: map[string]*pluginsdk.Schema{ "primary_approver": { Description: "The IDs of the users or groups who can approve the activation", - Type: pluginsdk.TypeList, + Type: pluginsdk.TypeSet, Required: true, MinItems: 1, Elem: &pluginsdk.Resource{ @@ -325,7 +325,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch }, "additional_recipients": { Description: "The additional recipients to notify", - Type: pluginsdk.TypeList, + Type: pluginsdk.TypeSet, Optional: true, Computed: true, Elem: &pluginsdk.Schema{ @@ -360,7 +360,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch }, "additional_recipients": { Description: "The additional recipients to notify", - Type: pluginsdk.TypeList, + Type: pluginsdk.TypeSet, Optional: true, Computed: true, Elem: &pluginsdk.Schema{ @@ -395,7 +395,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch }, "additional_recipients": { Description: "The additional recipients to notify", - Type: pluginsdk.TypeList, + Type: pluginsdk.TypeSet, Optional: true, Computed: true, Elem: &pluginsdk.Schema{ @@ -441,7 +441,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch }, "additional_recipients": { Description: "The additional recipients to notify", - Type: pluginsdk.TypeList, + Type: pluginsdk.TypeSet, Optional: true, Computed: true, Elem: &pluginsdk.Schema{ @@ -476,7 +476,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch }, "additional_recipients": { Description: "The additional recipients to notify", - Type: pluginsdk.TypeList, + Type: pluginsdk.TypeSet, Optional: true, Computed: true, Elem: &pluginsdk.Schema{ @@ -511,7 +511,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch }, "additional_recipients": { Description: "The additional recipients to notify", - Type: pluginsdk.TypeList, + Type: pluginsdk.TypeSet, Optional: true, Computed: true, Elem: &pluginsdk.Schema{ @@ -554,7 +554,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch }, "additional_recipients": { Description: "The additional recipients to notify", - Type: pluginsdk.TypeList, + Type: pluginsdk.TypeSet, Optional: true, Computed: true, Elem: &pluginsdk.Schema{ @@ -586,7 +586,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch }, "additional_recipients": { Description: "The additional recipients to notify", - Type: pluginsdk.TypeList, + Type: pluginsdk.TypeSet, Optional: true, Computed: true, Elem: &pluginsdk.Schema{ @@ -618,7 +618,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch }, "additional_recipients": { Description: "The additional recipients to notify", - Type: pluginsdk.TypeList, + Type: pluginsdk.TypeSet, Optional: true, Computed: true, Elem: &pluginsdk.Schema{ From 43f03a55bd0ee7f66af1fe7d102839f513052a6a Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Wed, 6 Mar 2024 12:49:11 +1300 Subject: [PATCH 31/55] DRY processing notification settings --- .../group_role_management_policy_resource.go | 769 ++++-------------- 1 file changed, 179 insertions(+), 590 deletions(-) diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index 46601f4ce4..7f459f86ec 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -300,112 +300,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Computed: true, MaxItems: 1, Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "eligible_assignments": { - Description: "The admin notifications for eligible assignments", - Type: pluginsdk.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "notification_level": { - Description: "What level of notifications are sent", - Type: pluginsdk.TypeString, - Required: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ - msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelAll, - msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelCritical, - }, false)), - }, - "default_recipients": { - Description: "Whether the default recipients are notified", - Type: pluginsdk.TypeBool, - Required: true, - }, - "additional_recipients": { - Description: "The additional recipients to notify", - Type: pluginsdk.TypeSet, - Optional: true, - Computed: true, - Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - }, - }, - }, - }, - }, - - "active_assignments": { - Description: "The admin notifications for active assignments", - Type: pluginsdk.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "notification_level": { - Description: "What level of notifications are sent", - Type: pluginsdk.TypeString, - Required: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ - msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelAll, - msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelCritical, - }, false)), - }, - "default_recipients": { - Description: "Whether the default recipients are notified", - Type: pluginsdk.TypeBool, - Required: true, - }, - "additional_recipients": { - Description: "The additional recipients to notify", - Type: pluginsdk.TypeSet, - Optional: true, - Computed: true, - Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - }, - }, - }, - }, - }, - - "activations": { - Description: "The admin notifications for role activation", - Type: pluginsdk.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "notification_level": { - Description: "What level of notifications are sent", - Type: pluginsdk.TypeString, - Required: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ - msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelAll, - msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelCritical, - }, false)), - }, - "default_recipients": { - Description: "Whether the default recipients are notified", - Type: pluginsdk.TypeBool, - Required: true, - }, - "additional_recipients": { - Description: "The additional recipients to notify", - Type: pluginsdk.TypeSet, - Optional: true, - Computed: true, - Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - }, - }, - }, - }, - }, - }, + Schema: notificationRuleSchema(), }, }, @@ -416,112 +311,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Computed: true, MaxItems: 1, Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "eligible_assignments": { - Description: "The admin notifications for eligible assignments", - Type: pluginsdk.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "notification_level": { - Description: "What level of notifications are sent", - Type: pluginsdk.TypeString, - Required: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ - msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelAll, - msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelCritical, - }, false)), - }, - "default_recipients": { - Description: "Whether the default recipients are notified", - Type: pluginsdk.TypeBool, - Required: true, - }, - "additional_recipients": { - Description: "The additional recipients to notify", - Type: pluginsdk.TypeSet, - Optional: true, - Computed: true, - Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - }, - }, - }, - }, - }, - - "active_assignments": { - Description: "The admin notifications for active assignments", - Type: pluginsdk.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "notification_level": { - Description: "What level of notifications are sent", - Type: pluginsdk.TypeString, - Required: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ - msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelAll, - msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelCritical, - }, false)), - }, - "default_recipients": { - Description: "Whether the default recipients are notified", - Type: pluginsdk.TypeBool, - Required: true, - }, - "additional_recipients": { - Description: "The additional recipients to notify", - Type: pluginsdk.TypeSet, - Optional: true, - Computed: true, - Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - }, - }, - }, - }, - }, - - "activations": { - Description: "The admin notifications for role activation", - Type: pluginsdk.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "notification_level": { - Description: "What level of notifications are sent", - Type: pluginsdk.TypeString, - Required: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ - msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelAll, - msgraph.UnifiedRoleManagementPolicyRuleNotificationLevelCritical, - }, false)), - }, - "default_recipients": { - Description: "Whether the default recipients are notified", - Type: pluginsdk.TypeBool, - Required: true, - }, - "additional_recipients": { - Description: "The additional recipients to notify", - Type: pluginsdk.TypeSet, - Optional: true, - Computed: true, - Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - }, - }, - }, - }, - }, - }, + Schema: notificationRuleSchema(), }, }, @@ -532,103 +322,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Computed: true, MaxItems: 1, Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "eligible_assignments": { - Description: "The admin notifications for eligible assignments", - Type: pluginsdk.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "notification_level": { - Description: "What level of notifications are sent", - Type: pluginsdk.TypeString, - Required: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), - }, - "default_recipients": { - Description: "Whether the default recipients are notified", - Type: pluginsdk.TypeBool, - Required: true, - }, - "additional_recipients": { - Description: "The additional recipients to notify", - Type: pluginsdk.TypeSet, - Optional: true, - Computed: true, - Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - }, - }, - }, - }, - }, - - "active_assignments": { - Description: "The admin notifications for active assignments", - Type: pluginsdk.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "notification_level": { - Description: "What level of notifications are sent", - Type: pluginsdk.TypeString, - Required: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), - }, - "default_recipients": { - Description: "Whether the default recipients are notified", - Type: pluginsdk.TypeBool, - Required: true, - }, - "additional_recipients": { - Description: "The additional recipients to notify", - Type: pluginsdk.TypeSet, - Optional: true, - Computed: true, - Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - }, - }, - }, - }, - }, - - "activations": { - Description: "The admin notifications for role activation", - Type: pluginsdk.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, - Elem: &pluginsdk.Resource{ - Schema: map[string]*pluginsdk.Schema{ - "notification_level": { - Description: "What level of notifications are sent", - Type: pluginsdk.TypeString, - Required: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), - }, - "default_recipients": { - Description: "Whether the default recipients are notified", - Type: pluginsdk.TypeBool, - Required: true, - }, - "additional_recipients": { - Description: "The additional recipients to notify", - Type: pluginsdk.TypeSet, - Optional: true, - Computed: true, - Elem: &pluginsdk.Schema{ - Type: pluginsdk.TypeString, - }, - }, - }, - }, - }, - }, + Schema: notificationRuleSchema(), }, }, }, @@ -820,77 +514,49 @@ func (r GroupRoleManagementPolicyResource) Read() sdk.ResourceFunc { } case "Notification_Admin_Admin_Eligibility": - if len(model.NotificationRules[0].AdminNotifications[0].EligibleAssignments) == 0 { - model.NotificationRules[0].AdminNotifications[0].EligibleAssignments = make([]GroupRoleManagementPolicyNotificationSettings, 1) + model.NotificationRules[0].AdminNotifications[0].EligibleAssignments = []GroupRoleManagementPolicyNotificationSettings{ + *flattenNotificationSettings(pointer.To(rule)), } - model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].NotificationLevel = rule.NotificationLevel - model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled - model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0].AdditionalRecipients = *rule.NotificationRecipients case "Notification_Admin_Admin_Assignment": - if len(model.NotificationRules[0].AdminNotifications[0].ActiveAssignments) == 0 { - model.NotificationRules[0].AdminNotifications[0].ActiveAssignments = make([]GroupRoleManagementPolicyNotificationSettings, 1) + model.NotificationRules[0].AdminNotifications[0].ActiveAssignments = []GroupRoleManagementPolicyNotificationSettings{ + *flattenNotificationSettings(pointer.To(rule)), } - model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0].NotificationLevel = rule.NotificationLevel - model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled - model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0].AdditionalRecipients = *rule.NotificationRecipients case "Notification_Admin_EndUser_Assignment": - if len(model.NotificationRules[0].AdminNotifications[0].Activations) == 0 { - model.NotificationRules[0].AdminNotifications[0].Activations = make([]GroupRoleManagementPolicyNotificationSettings, 1) + model.NotificationRules[0].AdminNotifications[0].Activations = []GroupRoleManagementPolicyNotificationSettings{ + *flattenNotificationSettings(pointer.To(rule)), } - model.NotificationRules[0].AdminNotifications[0].Activations[0].NotificationLevel = rule.NotificationLevel - model.NotificationRules[0].AdminNotifications[0].Activations[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled - model.NotificationRules[0].AdminNotifications[0].Activations[0].AdditionalRecipients = *rule.NotificationRecipients case "Notification_Approver_Admin_Eligibility": - if len(model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments) == 0 { - model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments = make([]GroupRoleManagementPolicyNotificationSettings, 1) + model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments = []GroupRoleManagementPolicyNotificationSettings{ + *flattenNotificationSettings(pointer.To(rule)), } - model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments[0].NotificationLevel = rule.NotificationLevel - model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled - model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments[0].AdditionalRecipients = *rule.NotificationRecipients case "Notification_Approver_Admin_Assignment": - if len(model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments) == 0 { - model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments = make([]GroupRoleManagementPolicyNotificationSettings, 1) + model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments = []GroupRoleManagementPolicyNotificationSettings{ + *flattenNotificationSettings(pointer.To(rule)), } - model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0].NotificationLevel = rule.NotificationLevel - model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled - model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0].AdditionalRecipients = *rule.NotificationRecipients case "Notification_Approver_EndUser_Assignment": - if len(model.NotificationRules[0].ApproverNotifications[0].Activations) == 0 { - model.NotificationRules[0].ApproverNotifications[0].Activations = make([]GroupRoleManagementPolicyNotificationSettings, 1) + model.NotificationRules[0].ApproverNotifications[0].Activations = []GroupRoleManagementPolicyNotificationSettings{ + *flattenNotificationSettings(pointer.To(rule)), } - model.NotificationRules[0].ApproverNotifications[0].Activations[0].NotificationLevel = rule.NotificationLevel - model.NotificationRules[0].ApproverNotifications[0].Activations[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled - model.NotificationRules[0].ApproverNotifications[0].Activations[0].AdditionalRecipients = *rule.NotificationRecipients case "Notification_Requestor_Admin_Eligibility": - if len(model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments) == 0 { - model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments = make([]GroupRoleManagementPolicyNotificationSettings, 1) + model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments = []GroupRoleManagementPolicyNotificationSettings{ + *flattenNotificationSettings(pointer.To(rule)), } - model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0].NotificationLevel = rule.NotificationLevel - model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled - model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0].AdditionalRecipients = *rule.NotificationRecipients case "Notification_Requestor_Admin_Assignment": - if len(model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments) == 0 { - model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments = make([]GroupRoleManagementPolicyNotificationSettings, 1) + model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments = []GroupRoleManagementPolicyNotificationSettings{ + *flattenNotificationSettings(pointer.To(rule)), } - model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0].NotificationLevel = rule.NotificationLevel - model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled - model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0].AdditionalRecipients = *rule.NotificationRecipients case "Notification_Requestor_EndUser_Assignment": - if len(model.NotificationRules[0].AssigneeNotifications[0].Activations) == 0 { - model.NotificationRules[0].AssigneeNotifications[0].Activations = make([]GroupRoleManagementPolicyNotificationSettings, 1) + model.NotificationRules[0].AssigneeNotifications[0].Activations = []GroupRoleManagementPolicyNotificationSettings{ + *flattenNotificationSettings(pointer.To(rule)), } - model.NotificationRules[0].AssigneeNotifications[0].Activations[0].NotificationLevel = rule.NotificationLevel - model.NotificationRules[0].AssigneeNotifications[0].Activations[0].DefaultRecipients = *rule.IsDefaultRecipientsEnabled - model.NotificationRules[0].AssigneeNotifications[0].Activations[0].AdditionalRecipients = *rule.NotificationRecipients - } } @@ -1134,265 +800,93 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie } if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.eligible_assignments") { - level := policyRules["Notification_Admin_Admin_Eligibility"].NotificationLevel - defaultRecipients := policyRules["Notification_Admin_Admin_Eligibility"].IsDefaultRecipientsEnabled - additionalRecipients := policyRules["Notification_Admin_Admin_Eligibility"].NotificationRecipients - data := model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0] - - if level != data.NotificationLevel { - level = data.NotificationLevel - } - if *defaultRecipients != data.DefaultRecipients { - defaultRecipients = pointer.To(data.DefaultRecipients) - } - if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.eligible_assignments.0.additional_recipients") { - additionalRecipients = pointer.To(data.AdditionalRecipients) - } - - rule := msgraph.UnifiedRoleManagementPolicyRule{ - ID: policyRules["Notification_Admin_Admin_Eligibility"].ID, - ODataType: policyRules["Notification_Admin_Admin_Eligibility"].ODataType, - Target: policyRules["Notification_Admin_Admin_Eligibility"].Target, - RecipientType: policyRules["Notification_Admin_Admin_Eligibility"].RecipientType, - NotificationType: policyRules["Notification_Admin_Admin_Eligibility"].NotificationType, - NotificationLevel: level, - IsDefaultRecipientsEnabled: defaultRecipients, - NotificationRecipients: additionalRecipients, - } - updatedRules = append(updatedRules, rule) + updatedRules = append(updatedRules, + expandNotificationSettings( + policyRules["Notification_Admin_Admin_Eligibility"], + model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0], + metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.eligible_assignments.0.additional_recipients"), + ), + ) } if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.active_assignments") { - level := policyRules["Notification_Admin_Admin_Assignment"].NotificationLevel - defaultRecipients := policyRules["Notification_Admin_Admin_Assignment"].IsDefaultRecipientsEnabled - additionalRecipients := policyRules["Notification_Admin_Admin_Assignment"].NotificationRecipients - - data := model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0] - - if level != data.NotificationLevel { - level = data.NotificationLevel - } - if *defaultRecipients != data.DefaultRecipients { - defaultRecipients = pointer.To(data.DefaultRecipients) - } - if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.active_assignments.0.additional_recipients") { - additionalRecipients = pointer.To(data.AdditionalRecipients) - } - - rule := msgraph.UnifiedRoleManagementPolicyRule{ - ID: policyRules["Notification_Admin_Admin_Assignment"].ID, - ODataType: policyRules["Notification_Admin_Admin_Assignment"].ODataType, - Target: policyRules["Notification_Admin_Admin_Assignment"].Target, - RecipientType: policyRules["Notification_Admin_Admin_Assignment"].RecipientType, - NotificationType: policyRules["Notification_Admin_Admin_Assignment"].NotificationType, - NotificationLevel: level, - IsDefaultRecipientsEnabled: defaultRecipients, - NotificationRecipients: additionalRecipients, - } - updatedRules = append(updatedRules, rule) + updatedRules = append(updatedRules, + expandNotificationSettings( + policyRules["Notification_Admin_Admin_Assignment"], + model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0], + metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.active_assignments.0.additional_recipients"), + ), + ) } if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.activations") { - level := policyRules["Notification_Admin_EndUser_Assignment"].NotificationLevel - defaultRecipients := policyRules["Notification_Admin_EndUser_Assignment"].IsDefaultRecipientsEnabled - additionalRecipients := policyRules["Notification_Admin_EndUser_Assignment"].NotificationRecipients - data := model.NotificationRules[0].AdminNotifications[0].Activations[0] - - if level != data.NotificationLevel { - level = data.NotificationLevel - } - if *defaultRecipients != data.DefaultRecipients { - defaultRecipients = pointer.To(data.DefaultRecipients) - } - if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.activations.0.additional_recipients") { - additionalRecipients = pointer.To(data.AdditionalRecipients) - } - - rule := msgraph.UnifiedRoleManagementPolicyRule{ - ID: policyRules["Notification_Admin_EndUser_Assignment"].ID, - ODataType: policyRules["Notification_Admin_EndUser_Assignment"].ODataType, - Target: policyRules["Notification_Admin_EndUser_Assignment"].Target, - RecipientType: policyRules["Notification_Admin_EndUser_Assignment"].RecipientType, - NotificationType: policyRules["Notification_Admin_EndUser_Assignment"].NotificationType, - NotificationLevel: level, - IsDefaultRecipientsEnabled: defaultRecipients, - NotificationRecipients: additionalRecipients, - } - updatedRules = append(updatedRules, rule) + updatedRules = append(updatedRules, + expandNotificationSettings( + policyRules["Notification_Admin_EndUser_Assignment"], + model.NotificationRules[0].AdminNotifications[0].Activations[0], + metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.activations.0.additional_recipients"), + ), + ) } if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.eligible_assignments") { - level := policyRules["Notification_Approver_Admin_Eligibility"].NotificationLevel - defaultRecipients := policyRules["Notification_Approver_Admin_Eligibility"].IsDefaultRecipientsEnabled - additionalRecipients := policyRules["Notification_Approver_Admin_Eligibility"].NotificationRecipients - data := model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments[0] - - if level != data.NotificationLevel { - level = data.NotificationLevel - } - if *defaultRecipients != data.DefaultRecipients { - defaultRecipients = pointer.To(data.DefaultRecipients) - } - if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.eligible_assignments.0.additional_recipients") { - additionalRecipients = pointer.To(data.AdditionalRecipients) - } - - rule := msgraph.UnifiedRoleManagementPolicyRule{ - ID: policyRules["Notification_Approver_Admin_Eligibility"].ID, - ODataType: policyRules["Notification_Approver_Admin_Eligibility"].ODataType, - Target: policyRules["Notification_Approver_Admin_Eligibility"].Target, - RecipientType: policyRules["Notification_Approver_Admin_Eligibility"].RecipientType, - NotificationType: policyRules["Notification_Approver_Admin_Eligibility"].NotificationType, - NotificationLevel: level, - IsDefaultRecipientsEnabled: defaultRecipients, - NotificationRecipients: additionalRecipients, - } - updatedRules = append(updatedRules, rule) + updatedRules = append(updatedRules, + expandNotificationSettings( + policyRules["Notification_Approver_Admin_Eligibility"], + model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments[0], + metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.eligible_assignments.0.additional_recipients"), + ), + ) } if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.active_assignments") { - level := policyRules["Notification_Approver_Admin_Assignment"].NotificationLevel - defaultRecipients := policyRules["Notification_Approver_Admin_Assignment"].IsDefaultRecipientsEnabled - additionalRecipients := policyRules["Notification_Approver_Admin_Assignment"].NotificationRecipients - data := model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0] - - if level != data.NotificationLevel { - level = data.NotificationLevel - } - if *defaultRecipients != data.DefaultRecipients { - defaultRecipients = pointer.To(data.DefaultRecipients) - } - if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.active_assignments.0.additional_recipients") { - additionalRecipients = pointer.To(data.AdditionalRecipients) - } - - rule := msgraph.UnifiedRoleManagementPolicyRule{ - ID: policyRules["Notification_Approver_Admin_Assignment"].ID, - ODataType: policyRules["Notification_Approver_Admin_Assignment"].ODataType, - Target: policyRules["Notification_Approver_Admin_Assignment"].Target, - RecipientType: policyRules["Notification_Approver_Admin_Assignment"].RecipientType, - NotificationType: policyRules["Notification_Approver_Admin_Assignment"].NotificationType, - NotificationLevel: level, - IsDefaultRecipientsEnabled: defaultRecipients, - NotificationRecipients: additionalRecipients, - } - updatedRules = append(updatedRules, rule) + updatedRules = append(updatedRules, + expandNotificationSettings( + policyRules["Notification_Approver_Admin_Assignment"], + model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0], + metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.active_assignments.0.additional_recipients"), + ), + ) } if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.activations") { - level := policyRules["Notification_Approver_EndUser_Assignment"].NotificationLevel - defaultRecipients := policyRules["Notification_Approver_EndUser_Assignment"].IsDefaultRecipientsEnabled - additionalRecipients := policyRules["Notification_Approver_EndUser_Assignment"].NotificationRecipients - data := model.NotificationRules[0].ApproverNotifications[0].Activations[0] - - if level != data.NotificationLevel { - level = data.NotificationLevel - } - if *defaultRecipients != data.DefaultRecipients { - defaultRecipients = pointer.To(data.DefaultRecipients) - } - if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.activations.0.additional_recipients") { - additionalRecipients = pointer.To(data.AdditionalRecipients) - } - - rule := msgraph.UnifiedRoleManagementPolicyRule{ - ID: policyRules["Notification_Approver_EndUser_Assignment"].ID, - ODataType: policyRules["Notification_Approver_EndUser_Assignment"].ODataType, - Target: policyRules["Notification_Approver_EndUser_Assignment"].Target, - RecipientType: policyRules["Notification_Approver_EndUser_Assignment"].RecipientType, - NotificationType: policyRules["Notification_Approver_EndUser_Assignment"].NotificationType, - NotificationLevel: level, - IsDefaultRecipientsEnabled: defaultRecipients, - NotificationRecipients: additionalRecipients, - } - updatedRules = append(updatedRules, rule) + updatedRules = append(updatedRules, + expandNotificationSettings( + policyRules["Notification_Approver_EndUser_Assignment"], + model.NotificationRules[0].ApproverNotifications[0].Activations[0], + metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.activations.0.additional_recipients"), + ), + ) } if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.eligible_assignments") { - level := policyRules["Notification_Requestor_Admin_Eligibility"].NotificationLevel - defaultRecipients := policyRules["Notification_Requestor_Admin_Eligibility"].IsDefaultRecipientsEnabled - additionalRecipients := policyRules["Notification_Requestor_Admin_Eligibility"].NotificationRecipients - data := model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0] - - if level != data.NotificationLevel { - level = data.NotificationLevel - } - if *defaultRecipients != data.DefaultRecipients { - defaultRecipients = pointer.To(data.DefaultRecipients) - } - if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.eligible_assignments.0.additional_recipients") { - additionalRecipients = pointer.To(data.AdditionalRecipients) - } - - rule := msgraph.UnifiedRoleManagementPolicyRule{ - ID: policyRules["Notification_Requestor_Admin_Eligibility"].ID, - ODataType: policyRules["Notification_Requestor_Admin_Eligibility"].ODataType, - Target: policyRules["Notification_Requestor_Admin_Eligibility"].Target, - RecipientType: policyRules["Notification_Requestor_Admin_Eligibility"].RecipientType, - NotificationType: policyRules["Notification_Requestor_Admin_Eligibility"].NotificationType, - NotificationLevel: level, - IsDefaultRecipientsEnabled: defaultRecipients, - NotificationRecipients: additionalRecipients, - } - updatedRules = append(updatedRules, rule) + updatedRules = append(updatedRules, + expandNotificationSettings( + policyRules["Notification_Requestor_Admin_Eligibility"], + model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0], + metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.eligible_assignments.0.additional_recipients"), + ), + ) } if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.active_assignments") { - level := policyRules["Notification_Requestor_Admin_Assignment"].NotificationLevel - defaultRecipients := policyRules["Notification_Requestor_Admin_Assignment"].IsDefaultRecipientsEnabled - additionalRecipients := policyRules["Notification_Requestor_Admin_Assignment"].NotificationRecipients - data := model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0] - - if level != data.NotificationLevel { - level = data.NotificationLevel - } - if *defaultRecipients != data.DefaultRecipients { - defaultRecipients = pointer.To(data.DefaultRecipients) - } - if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.active_assignments.0.additional_recipients") { - additionalRecipients = pointer.To(data.AdditionalRecipients) - } - - rule := msgraph.UnifiedRoleManagementPolicyRule{ - ID: policyRules["Notification_Requestor_Admin_Assignment"].ID, - ODataType: policyRules["Notification_Requestor_Admin_Assignment"].ODataType, - Target: policyRules["Notification_Requestor_Admin_Assignment"].Target, - RecipientType: policyRules["Notification_Requestor_Admin_Assignment"].RecipientType, - NotificationType: policyRules["Notification_Requestor_Admin_Assignment"].NotificationType, - NotificationLevel: level, - IsDefaultRecipientsEnabled: defaultRecipients, - NotificationRecipients: additionalRecipients, - } - updatedRules = append(updatedRules, rule) + updatedRules = append(updatedRules, + expandNotificationSettings( + policyRules["Notification_Requestor_Admin_Assignment"], + model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0], + metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.active_assignments.0.additional_recipients"), + ), + ) } if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.activations") { - level := policyRules["Notification_Requestor_EndUser_Assignment"].NotificationLevel - defaultRecipients := policyRules["Notification_Requestor_EndUser_Assignment"].IsDefaultRecipientsEnabled - additionalRecipients := policyRules["Notification_Requestor_EndUser_Assignment"].NotificationRecipients - data := model.NotificationRules[0].AssigneeNotifications[0].Activations[0] - - if level != data.NotificationLevel { - level = data.NotificationLevel - } - if *defaultRecipients != data.DefaultRecipients { - defaultRecipients = pointer.To(data.DefaultRecipients) - } - if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.activations.0.additional_recipients") { - additionalRecipients = pointer.To(data.AdditionalRecipients) - } - - rule := msgraph.UnifiedRoleManagementPolicyRule{ - ID: policyRules["Notification_Requestor_EndUser_Assignment"].ID, - ODataType: policyRules["Notification_Requestor_EndUser_Assignment"].ODataType, - Target: policyRules["Notification_Requestor_EndUser_Assignment"].Target, - RecipientType: policyRules["Notification_Requestor_EndUser_Assignment"].RecipientType, - NotificationType: policyRules["Notification_Requestor_EndUser_Assignment"].NotificationType, - NotificationLevel: level, - IsDefaultRecipientsEnabled: defaultRecipients, - NotificationRecipients: additionalRecipients, - } - updatedRules = append(updatedRules, rule) + updatedRules = append(updatedRules, + expandNotificationSettings( + policyRules["Notification_Requestor_EndUser_Assignment"], + model.NotificationRules[0].AssigneeNotifications[0].Activations[0], + metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.activations.0.additional_recipients"), + ), + ) } return &msgraph.UnifiedRoleManagementPolicy{ @@ -1400,3 +894,98 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie Rules: pointer.To(updatedRules), }, nil } + +func expandNotificationSettings(rule msgraph.UnifiedRoleManagementPolicyRule, data GroupRoleManagementPolicyNotificationSettings, recipientChange bool) msgraph.UnifiedRoleManagementPolicyRule { + level := rule.NotificationLevel + defaultRecipients := rule.IsDefaultRecipientsEnabled + additionalRecipients := rule.NotificationRecipients + + if level != data.NotificationLevel { + level = data.NotificationLevel + } + if *defaultRecipients != data.DefaultRecipients { + defaultRecipients = pointer.To(data.DefaultRecipients) + } + if recipientChange { + additionalRecipients = pointer.To(data.AdditionalRecipients) + } + + return msgraph.UnifiedRoleManagementPolicyRule{ + ID: rule.ID, + ODataType: rule.ODataType, + Target: rule.Target, + RecipientType: rule.RecipientType, + NotificationType: rule.NotificationType, + NotificationLevel: level, + IsDefaultRecipientsEnabled: defaultRecipients, + NotificationRecipients: additionalRecipients, + } +} + +func flattenNotificationSettings(rule *msgraph.UnifiedRoleManagementPolicyRule) *GroupRoleManagementPolicyNotificationSettings { + return &GroupRoleManagementPolicyNotificationSettings{ + NotificationLevel: rule.NotificationLevel, + DefaultRecipients: *rule.IsDefaultRecipientsEnabled, + AdditionalRecipients: *rule.NotificationRecipients, + } +} + +func notificationRuleSchema() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "eligible_assignments": { + Description: "The admin notifications for eligible assignments", + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: notificationSettingsSchema(), + }, + }, + "active_assignments": { + Description: "The admin notifications for active assignments", + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: notificationSettingsSchema(), + }, + }, + "activations": { + Description: "The admin notifications for role activation", + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: notificationSettingsSchema(), + }, + }, + } +} + +func notificationSettingsSchema() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "notification_level": { + Description: "What level of notifications are sent", + Type: pluginsdk.TypeString, + Required: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"All", "Critical"}, false)), + }, + "default_recipients": { + Description: "Whether the default recipients are notified", + Type: pluginsdk.TypeBool, + Required: true, + }, + "additional_recipients": { + Description: "The additional recipients to notify", + Type: pluginsdk.TypeSet, + Optional: true, + Computed: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + } +} From 5598a54d1b7562ea7c5825c5e21634c772885b0c Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Wed, 6 Mar 2024 13:21:12 +1300 Subject: [PATCH 32/55] Reverse nesting of notification blocks to match the GUI layout --- .../resources/group_role_management_policy.md | 40 ++-- .../group_role_management_policy_resource.go | 186 +++++++++--------- ...up_role_management_policy_resource_test.go | 15 +- 3 files changed, 115 insertions(+), 126 deletions(-) diff --git a/docs/resources/group_role_management_policy.md b/docs/resources/group_role_management_policy.md index 1101b43bbd..c13bc2c7d0 100644 --- a/docs/resources/group_role_management_policy.md +++ b/docs/resources/group_role_management_policy.md @@ -91,36 +91,12 @@ An `activation_rules` block supports the following: --- -An `admin_notifications` block supports the following: - -* `activations` An optional `notification_settings` block as defined below for configuring notifications to adminstrators of role activations. -* `active_assignments` An optional `notification_settings` block as defined below for configuring notifications to adminstrators of new active assignments. -* `eligible_assignments` An optional `notification_settings` block as defined below for configuring notifications to adminstrators of new eligible assignments. - ---- - An `approval_stage` block supports the following: * One or more `primary_approver` blocks as defined below. --- -An `approver_notifications` block supports the following: - -* `activations` - An optional `notification_settings` block as defined below for configuring notifications to approvers of new activation request. -* `active_assignments` - An optional `notification_settings` block as defined below for configuring notifications to approvers of new active assignment requests. -* `eligible_assignments` - An optional `notification_settings` block as defined below for configuring notifications to approvers of new eligible assignment requests. - ---- - -An `assignee_notifications` block supports the following: - -* `activations` - An optional `notification_settings` block as defined below for configuring notifications to assignees of role activations. -* `active_assignments` - An optional `notification_settings` block as defined below for configuring notifications to assignees of new active assignments. -* `eligible_assignments` - An optional `notification_settings` block as defined below for configuring notifications to assignees of new eligible assignments. - ---- - An `eligible_assignment_rules` block supports the following: * `expiration_required`- Must an assignment have an expiry date. `false` allows permanent assignment. @@ -132,12 +108,20 @@ One of `expiration_required` or `expire_after` must be provided. A `notification_rules` block supports the following: -* `admin_notifications` - (Optional) An `admin_notifications` block as defined above. -* `approver_notifications` - (Optional) An `approver_notifications` block as defined above. -* `assignee_notifications` - (Optional) An `assignee_notifications` block as defined above. +* `active_assignments` - An optional `notification_events` block as defined below to configure notfications on active role assignments. +* `eligible_activations` - An optional `notification_events` block as defined below for configuring notifications on activation of eligible role. +* `eligible_assignments` - An optional `notification_events` block as defined below to configure notification on eligible role assignments. --- +An `notification_events` block supports the following: + +* `admin_notifications` - (Optional) An `notification_settings` block as defined below. +* `approver_notifications` - (Optional) An `notification_settings` block as defined below. +* `assignee_notifications` - (Optional) An `notification_settings` block as defined below. + + +--- A `notification_settings` block supports the following: * `notification_level` - (Required) What level of notifications should be sent. Options are `All` or `Critical`. @@ -149,7 +133,7 @@ A `notification_settings` block supports the following: A `primary_approver` block supports the following: * `object_id` - (Required) The ID of the object which will act as an approver. -* `type` - (Required) The type of object acting as an approver. Possible options are `singleUser` and `groupMember`. +* `type` - (Required) The type of object acting as an approver. Possible options are `singleUser` and `groupMembers`. ## Attributes Reference diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index 7f459f86ec..c5e352886c 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -25,7 +25,7 @@ type GroupRoleManagementPolicyModel struct { ActiveAssignmentRules []GroupRoleManagementPolicyActiveAssignmentRules `tfschema:"active_assignment_rules"` EligibleAssignmentRules []GroupRoleManagementPolicyEligibleAssignmentRules `tfschema:"eligible_assignment_rules"` ActivationRules []GroupRoleManagementPolicyActivationRules `tfschema:"activation_rules"` - NotificationRules []GroupRoleManagementPolicyNotificationRules `tfschema:"notification_rules"` + NotificationRules []GroupRoleManagementPolicyNotificationEvents `tfschema:"notification_rules"` } type GroupRoleManagementPolicyActiveAssignmentRules struct { @@ -60,16 +60,16 @@ type GroupRoleManagementPolicyApprover struct { Type string `tfschema:"type"` } -type GroupRoleManagementPolicyNotificationRules struct { - AdminNotifications []GroupRoleManagementPolicyNotificationRule `tfschema:"admin_notifications"` - ApproverNotifications []GroupRoleManagementPolicyNotificationRule `tfschema:"approver_notifications"` - AssigneeNotifications []GroupRoleManagementPolicyNotificationRule `tfschema:"assignee_notifications"` +type GroupRoleManagementPolicyNotificationEvents struct { + ActiveAssignments []GroupRoleManagementPolicyNotificationRule `tfschema:"active_assignments"` + EligibleActivations []GroupRoleManagementPolicyNotificationRule `tfschema:"eligible_activations"` + EligibleAssignments []GroupRoleManagementPolicyNotificationRule `tfschema:"eligible_assignments"` } type GroupRoleManagementPolicyNotificationRule struct { - EligibleAssignments []GroupRoleManagementPolicyNotificationSettings `tfschema:"eligible_assignments"` - ActiveAssignments []GroupRoleManagementPolicyNotificationSettings `tfschema:"active_assignments"` - Activations []GroupRoleManagementPolicyNotificationSettings `tfschema:"activations"` + AdminNotifications []GroupRoleManagementPolicyNotificationSettings `tfschema:"admin_notifications"` + ApproverNotifications []GroupRoleManagementPolicyNotificationSettings `tfschema:"approver_notifications"` + AssigneeNotifications []GroupRoleManagementPolicyNotificationSettings `tfschema:"assignee_notifications"` } type GroupRoleManagementPolicyNotificationSettings struct { @@ -293,8 +293,8 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch MaxItems: 1, Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ - "admin_notifications": { - Description: "The admin notifications on assignment", + "active_assignments": { + Description: "Notifications about active assignments", Type: pluginsdk.TypeList, Optional: true, Computed: true, @@ -303,9 +303,8 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Schema: notificationRuleSchema(), }, }, - - "approver_notifications": { - Description: "The admin notifications on assignment", + "eligible_activations": { + Description: "Notifications about activations of eligible assignments", Type: pluginsdk.TypeList, Optional: true, Computed: true, @@ -314,9 +313,8 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Schema: notificationRuleSchema(), }, }, - - "assignee_notifications": { - Description: "The admin notifications on assignment", + "eligible_assignments": { + Description: "Notifications about eligible assignments", Type: pluginsdk.TypeList, Optional: true, Computed: true, @@ -434,43 +432,20 @@ func (r GroupRoleManagementPolicyResource) Read() sdk.ResourceFunc { model.ActivationRules = make([]GroupRoleManagementPolicyActivationRules, 1) } if len(model.NotificationRules) == 0 { - model.NotificationRules = make([]GroupRoleManagementPolicyNotificationRules, 1) + model.NotificationRules = make([]GroupRoleManagementPolicyNotificationEvents, 1) } - if len(model.NotificationRules[0].AdminNotifications) == 0 { - model.NotificationRules[0].AdminNotifications = make([]GroupRoleManagementPolicyNotificationRule, 1) + if len(model.NotificationRules[0].EligibleActivations) == 0 { + model.NotificationRules[0].EligibleActivations = make([]GroupRoleManagementPolicyNotificationRule, 1) } - if len(model.NotificationRules[0].ApproverNotifications) == 0 { - model.NotificationRules[0].ApproverNotifications = make([]GroupRoleManagementPolicyNotificationRule, 1) + if len(model.NotificationRules[0].ActiveAssignments) == 0 { + model.NotificationRules[0].ActiveAssignments = make([]GroupRoleManagementPolicyNotificationRule, 1) } - if len(model.NotificationRules[0].AssigneeNotifications) == 0 { - model.NotificationRules[0].AssigneeNotifications = make([]GroupRoleManagementPolicyNotificationRule, 1) + if len(model.NotificationRules[0].EligibleAssignments) == 0 { + model.NotificationRules[0].EligibleAssignments = make([]GroupRoleManagementPolicyNotificationRule, 1) } for _, rule := range *result.Rules { switch *rule.ID { - case "Expiration_Admin_Eligibility": - model.EligibleAssignmentRules[0].ExpirationRequired = *rule.IsExpirationRequired - model.EligibleAssignmentRules[0].ExpireAfter = *rule.MaximumDuration - - case "Enablement_Admin_Assignment": - model.ActiveAssignmentRules[0].RequireMultiFactorAuth = false - model.ActiveAssignmentRules[0].RequireJustification = false - for _, enabledRule := range *rule.EnabledRules { - switch enabledRule { - case "MultiFactorAuthentication": - model.ActiveAssignmentRules[0].RequireMultiFactorAuth = true - case "Justification": - model.ActiveAssignmentRules[0].RequireJustification = true - } - } - - case "Expiration_Admin_Assignment": - model.ActiveAssignmentRules[0].ExpirationRequired = *rule.IsExpirationRequired - model.ActiveAssignmentRules[0].ExpireAfter = *rule.MaximumDuration - - case "Expiration_EndUser_Assignment": - model.ActivationRules[0].MaximumDuration = *rule.MaximumDuration - case "Approval_EndUser_Assignment": model.ActivationRules[0].RequireApproval = *rule.Setting.IsApprovalRequired @@ -498,6 +473,18 @@ func (r GroupRoleManagementPolicyResource) Read() sdk.ResourceFunc { model.ActivationRules[0].RequireConditionalAccessContext = *rule.ClaimValue } + case "Enablement_Admin_Assignment": + model.ActiveAssignmentRules[0].RequireMultiFactorAuth = false + model.ActiveAssignmentRules[0].RequireJustification = false + for _, enabledRule := range *rule.EnabledRules { + switch enabledRule { + case "MultiFactorAuthentication": + model.ActiveAssignmentRules[0].RequireMultiFactorAuth = true + case "Justification": + model.ActiveAssignmentRules[0].RequireJustification = true + } + } + case "Enablement_EndUser_Assignment": model.ActivationRules[0].RequireMultiFactorAuth = false model.ActivationRules[0].RequireJustification = false @@ -513,48 +500,59 @@ func (r GroupRoleManagementPolicyResource) Read() sdk.ResourceFunc { } } - case "Notification_Admin_Admin_Eligibility": - model.NotificationRules[0].AdminNotifications[0].EligibleAssignments = []GroupRoleManagementPolicyNotificationSettings{ + case "Expiration_Admin_Eligibility": + model.EligibleAssignmentRules[0].ExpirationRequired = *rule.IsExpirationRequired + model.EligibleAssignmentRules[0].ExpireAfter = *rule.MaximumDuration + + case "Expiration_Admin_Assignment": + model.ActiveAssignmentRules[0].ExpirationRequired = *rule.IsExpirationRequired + model.ActiveAssignmentRules[0].ExpireAfter = *rule.MaximumDuration + + case "Expiration_EndUser_Assignment": + model.ActivationRules[0].MaximumDuration = *rule.MaximumDuration + + case "Notification_Admin_Admin_Assignment": + model.NotificationRules[0].ActiveAssignments[0].AdminNotifications = []GroupRoleManagementPolicyNotificationSettings{ *flattenNotificationSettings(pointer.To(rule)), } - case "Notification_Admin_Admin_Assignment": - model.NotificationRules[0].AdminNotifications[0].ActiveAssignments = []GroupRoleManagementPolicyNotificationSettings{ + case "Notification_Admin_Admin_Eligibility": + model.NotificationRules[0].EligibleAssignments[0].AdminNotifications = []GroupRoleManagementPolicyNotificationSettings{ *flattenNotificationSettings(pointer.To(rule)), } case "Notification_Admin_EndUser_Assignment": - model.NotificationRules[0].AdminNotifications[0].Activations = []GroupRoleManagementPolicyNotificationSettings{ + model.NotificationRules[0].EligibleActivations[0].AdminNotifications = []GroupRoleManagementPolicyNotificationSettings{ *flattenNotificationSettings(pointer.To(rule)), } - case "Notification_Approver_Admin_Eligibility": - model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments = []GroupRoleManagementPolicyNotificationSettings{ + case "Notification_Approver_Admin_Assignment": + model.NotificationRules[0].ActiveAssignments[0].ApproverNotifications = []GroupRoleManagementPolicyNotificationSettings{ *flattenNotificationSettings(pointer.To(rule)), } - case "Notification_Approver_Admin_Assignment": - model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments = []GroupRoleManagementPolicyNotificationSettings{ + case "Notification_Approver_Admin_Eligibility": + model.NotificationRules[0].EligibleAssignments[0].ApproverNotifications = []GroupRoleManagementPolicyNotificationSettings{ *flattenNotificationSettings(pointer.To(rule)), } case "Notification_Approver_EndUser_Assignment": - model.NotificationRules[0].ApproverNotifications[0].Activations = []GroupRoleManagementPolicyNotificationSettings{ + model.NotificationRules[0].EligibleActivations[0].ApproverNotifications = []GroupRoleManagementPolicyNotificationSettings{ *flattenNotificationSettings(pointer.To(rule)), } - case "Notification_Requestor_Admin_Eligibility": - model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments = []GroupRoleManagementPolicyNotificationSettings{ + case "Notification_Requestor_Admin_Assignment": + model.NotificationRules[0].ActiveAssignments[0].AssigneeNotifications = []GroupRoleManagementPolicyNotificationSettings{ *flattenNotificationSettings(pointer.To(rule)), } - case "Notification_Requestor_Admin_Assignment": - model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments = []GroupRoleManagementPolicyNotificationSettings{ + case "Notification_Requestor_Admin_Eligibility": + model.NotificationRules[0].EligibleAssignments[0].AssigneeNotifications = []GroupRoleManagementPolicyNotificationSettings{ *flattenNotificationSettings(pointer.To(rule)), } case "Notification_Requestor_EndUser_Assignment": - model.NotificationRules[0].AssigneeNotifications[0].Activations = []GroupRoleManagementPolicyNotificationSettings{ + model.NotificationRules[0].EligibleActivations[0].AssigneeNotifications = []GroupRoleManagementPolicyNotificationSettings{ *flattenNotificationSettings(pointer.To(rule)), } } @@ -799,92 +797,92 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie updatedRules = append(updatedRules, rule) } - if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.eligible_assignments") { + if metadata.ResourceData.HasChange("notification_rules.0.eligible_assignments.0.admin_notifications") { updatedRules = append(updatedRules, expandNotificationSettings( policyRules["Notification_Admin_Admin_Eligibility"], - model.NotificationRules[0].AdminNotifications[0].EligibleAssignments[0], - metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.eligible_assignments.0.additional_recipients"), + model.NotificationRules[0].EligibleAssignments[0].AdminNotifications[0], + metadata.ResourceData.HasChange("notification_rules.0.eligible_assignments.0.admin_notifications.0.additional_recipients"), ), ) } - if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.active_assignments") { + if metadata.ResourceData.HasChange("notification_rules.0.active_assignments.0.admin_notifications") { updatedRules = append(updatedRules, expandNotificationSettings( policyRules["Notification_Admin_Admin_Assignment"], - model.NotificationRules[0].AdminNotifications[0].ActiveAssignments[0], - metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.active_assignments.0.additional_recipients"), + model.NotificationRules[0].ActiveAssignments[0].AdminNotifications[0], + metadata.ResourceData.HasChange("notification_rules.0.active_assignments.0.admin_notifications.0.additional_recipients"), ), ) } - if metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.activations") { + if metadata.ResourceData.HasChange("notification_rules.0.eligible_activations.0.admin_notifications") { updatedRules = append(updatedRules, expandNotificationSettings( policyRules["Notification_Admin_EndUser_Assignment"], - model.NotificationRules[0].AdminNotifications[0].Activations[0], - metadata.ResourceData.HasChange("notification_rules.0.admin_notifications.0.activations.0.additional_recipients"), + model.NotificationRules[0].EligibleActivations[0].AdminNotifications[0], + metadata.ResourceData.HasChange("notification_rules.0.eligible_activations.0.admin_notifications.0.additional_recipients"), ), ) } - if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.eligible_assignments") { + if metadata.ResourceData.HasChange("notification_rules.0.eligible_assignments.0.approver_notifications") { updatedRules = append(updatedRules, expandNotificationSettings( policyRules["Notification_Approver_Admin_Eligibility"], - model.NotificationRules[0].ApproverNotifications[0].EligibleAssignments[0], - metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.eligible_assignments.0.additional_recipients"), + model.NotificationRules[0].EligibleAssignments[0].ApproverNotifications[0], + metadata.ResourceData.HasChange("notification_rules.0.eligible_assignments.0.approver_notifications.0.additional_recipients"), ), ) } - if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.active_assignments") { + if metadata.ResourceData.HasChange("notification_rules.0.active_assignments.0.approver_notifications") { updatedRules = append(updatedRules, expandNotificationSettings( policyRules["Notification_Approver_Admin_Assignment"], - model.NotificationRules[0].ApproverNotifications[0].ActiveAssignments[0], - metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.active_assignments.0.additional_recipients"), + model.NotificationRules[0].ActiveAssignments[0].ApproverNotifications[0], + metadata.ResourceData.HasChange("notification_rules.0.active_assignments.0.approver_notifications.0.additional_recipients"), ), ) } - if metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.activations") { + if metadata.ResourceData.HasChange("notification_rules.0.eligible_activations.0.approver_notifications") { updatedRules = append(updatedRules, expandNotificationSettings( policyRules["Notification_Approver_EndUser_Assignment"], - model.NotificationRules[0].ApproverNotifications[0].Activations[0], - metadata.ResourceData.HasChange("notification_rules.0.approver_notifications.0.activations.0.additional_recipients"), + model.NotificationRules[0].EligibleActivations[0].ApproverNotifications[0], + metadata.ResourceData.HasChange("notification_rules.0.eligible_activations.0.approver_notifications.0.additional_recipients"), ), ) } - if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.eligible_assignments") { + if metadata.ResourceData.HasChange("notification_rules.0.eligible_assignments.0.assignee_notifications") { updatedRules = append(updatedRules, expandNotificationSettings( policyRules["Notification_Requestor_Admin_Eligibility"], - model.NotificationRules[0].AssigneeNotifications[0].EligibleAssignments[0], - metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.eligible_assignments.0.additional_recipients"), + model.NotificationRules[0].EligibleAssignments[0].AssigneeNotifications[0], + metadata.ResourceData.HasChange("notification_rules.0.eligible_assignments.0.assignee_notifications.0.additional_recipients"), ), ) } - if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.active_assignments") { + if metadata.ResourceData.HasChange("notification_rules.0.active_assignments.0.assignee_notifications") { updatedRules = append(updatedRules, expandNotificationSettings( policyRules["Notification_Requestor_Admin_Assignment"], - model.NotificationRules[0].AssigneeNotifications[0].ActiveAssignments[0], - metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.active_assignments.0.additional_recipients"), + model.NotificationRules[0].ActiveAssignments[0].AssigneeNotifications[0], + metadata.ResourceData.HasChange("notification_rules.0.active_assignments.0.assignee_notifications.0.additional_recipients"), ), ) } - if metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.activations") { + if metadata.ResourceData.HasChange("notification_rules.0.eligible_activations.0.assignee_notifications") { updatedRules = append(updatedRules, expandNotificationSettings( policyRules["Notification_Requestor_EndUser_Assignment"], - model.NotificationRules[0].AssigneeNotifications[0].Activations[0], - metadata.ResourceData.HasChange("notification_rules.0.assignee_notifications.0.activations.0.additional_recipients"), + model.NotificationRules[0].EligibleActivations[0].AssigneeNotifications[0], + metadata.ResourceData.HasChange("notification_rules.0.eligible_activations.0.assignee_notifications.0.additional_recipients"), ), ) } @@ -932,8 +930,8 @@ func flattenNotificationSettings(rule *msgraph.UnifiedRoleManagementPolicyRule) func notificationRuleSchema() map[string]*pluginsdk.Schema { return map[string]*pluginsdk.Schema{ - "eligible_assignments": { - Description: "The admin notifications for eligible assignments", + "admin_notifications": { + Description: "Admin notification settings", Type: pluginsdk.TypeList, Optional: true, Computed: true, @@ -942,8 +940,8 @@ func notificationRuleSchema() map[string]*pluginsdk.Schema { Schema: notificationSettingsSchema(), }, }, - "active_assignments": { - Description: "The admin notifications for active assignments", + "approver_notifications": { + Description: "Approver notification settings", Type: pluginsdk.TypeList, Optional: true, Computed: true, @@ -952,8 +950,8 @@ func notificationRuleSchema() map[string]*pluginsdk.Schema { Schema: notificationSettingsSchema(), }, }, - "activations": { - Description: "The admin notifications for role activation", + "assignee_notifications": { + Description: "Assignee notification settings", Type: pluginsdk.TypeList, Optional: true, Computed: true, diff --git a/internal/services/policies/group_role_management_policy_resource_test.go b/internal/services/policies/group_role_management_policy_resource_test.go index 7b0150124a..12a7fb082f 100644 --- a/internal/services/policies/group_role_management_policy_resource_test.go +++ b/internal/services/policies/group_role_management_policy_resource_test.go @@ -92,13 +92,20 @@ resource "azuread_group_role_management_policy" "test" { } notification_rules { - approver_notifications { - eligible_assignments { + eligible_assignments { + approver_notifications { notification_level = "Critical" default_recipients = false additional_recipients = ["someone@example.com"] } } + eligible_activations { + assignee_notifications { + notification_level = "All" + default_recipients = true + additional_recipients = ["someone@example.com"] + } + } } } `, data.RandomString) @@ -148,8 +155,8 @@ resource "azuread_group_role_management_policy" "test" { } notification_rules { - admin_notifications { - active_assignments { + active_assignments { + admin_notifications { notification_level = "Critical" default_recipients = false additional_recipients = ["someone@example.com"] From 07037adfe597cd84dcbbcb04a0303d78ae77133f Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Wed, 6 Mar 2024 13:38:13 +1300 Subject: [PATCH 33/55] Update Import section of the documentation --- docs/resources/group_role_management_policy.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/resources/group_role_management_policy.md b/docs/resources/group_role_management_policy.md index c13bc2c7d0..e96e849fe3 100644 --- a/docs/resources/group_role_management_policy.md +++ b/docs/resources/group_role_management_policy.md @@ -145,10 +145,4 @@ In addition to all arguments above, the following attributes are exported: ## Import -An assignment schedule can be imported using the ID, e.g. - -```shell -terraform import azuread_privileged_access_group_eligibility_schedule_request.example Group_00000000-0000-0000-0000-000000000000_00000000-0000-0000-0000-000000000000 -``` - Because these policies are created automatically by Entra ID, they will auto-import on first use. From d409233192ca90e63ae601066f46f20f19cd6a0f Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Thu, 7 Mar 2024 10:12:36 +1300 Subject: [PATCH 34/55] Fix naming issues after merge --- .../identitygovernance/client/client.go | 42 +++++------ .../identitygovernance/helpers/test.go | 15 ++++ ...ccess_group_assignment_schedule_request.go | 69 ++++++++++--------- ..._group_assignment_schedule_request_test.go | 12 +--- ...ccess_group_eligiblity_schedule_request.go | 39 ++++++----- ..._group_eligiblity_schedule_request_test.go | 14 ++-- 6 files changed, 98 insertions(+), 93 deletions(-) create mode 100644 internal/services/identitygovernance/helpers/test.go diff --git a/internal/services/identitygovernance/client/client.go b/internal/services/identitygovernance/client/client.go index 7309770f9f..247cd4254b 100644 --- a/internal/services/identitygovernance/client/client.go +++ b/internal/services/identitygovernance/client/client.go @@ -9,16 +9,16 @@ import ( ) type Client struct { - AccessPackageAssignmentPolicyClient *msgraph.AccessPackageAssignmentPolicyClient - AccessPackageCatalogClient *msgraph.AccessPackageCatalogClient - AccessPackageCatalogRoleAssignmentsClient *msgraph.EntitlementRoleAssignmentsClient - AccessPackageCatalogRoleClient *msgraph.EntitlementRoleDefinitionsClient - AccessPackageClient *msgraph.AccessPackageClient - AccessPackageResourceClient *msgraph.AccessPackageResourceClient - AccessPackageResourceRequestClient *msgraph.AccessPackageResourceRequestClient - AccessPackageResourceRoleScopeClient *msgraph.AccessPackageResourceRoleScopeClient - PrivilegedAccessGroupAssignmentScheduleRequestsClient *msgraph.PrivilegedAccessGroupAssignmentScheduleRequestsClient - PrivilegedAccessGroupEligibilityScheduleRequestClient *msgraph.PrivilegedAccessGroupEligibilityScheduleRequestClient + AccessPackageAssignmentPolicyClient *msgraph.AccessPackageAssignmentPolicyClient + AccessPackageCatalogClient *msgraph.AccessPackageCatalogClient + AccessPackageCatalogRoleAssignmentsClient *msgraph.EntitlementRoleAssignmentsClient + AccessPackageCatalogRoleClient *msgraph.EntitlementRoleDefinitionsClient + AccessPackageClient *msgraph.AccessPackageClient + AccessPackageResourceClient *msgraph.AccessPackageResourceClient + AccessPackageResourceRequestClient *msgraph.AccessPackageResourceRequestClient + AccessPackageResourceRoleScopeClient *msgraph.AccessPackageResourceRoleScopeClient + PrivilegedAccessGroupAssignmentScheduleRequestsClient *msgraph.PrivilegedAccessGroupAssignmentScheduleRequestsClient + PrivilegedAccessGroupEligibilityScheduleRequestsClient *msgraph.PrivilegedAccessGroupEligibilityScheduleRequestsClient } func NewClient(o *common.ClientOptions) *Client { @@ -59,19 +59,19 @@ func NewClient(o *common.ClientOptions) *Client { privilegedAccessGroupAssignmentScheduleRequestsClient := msgraph.NewPrivilegedAccessGroupAssignmentScheduleRequestsClient() o.ConfigureClient(&privilegedAccessGroupAssignmentScheduleRequestsClient.BaseClient) - privilegedAccessGroupEligibilityScheduleRequestsClient := msgraph.NewPrivilegedAccessGroupEligibilityScheduleRequestClient() + privilegedAccessGroupEligibilityScheduleRequestsClient := msgraph.NewPrivilegedAccessGroupEligibilityScheduleRequestsClient() o.ConfigureClient(&privilegedAccessGroupEligibilityScheduleRequestsClient.BaseClient) return &Client{ - AccessPackageAssignmentPolicyClient: accessPackageAssignmentPolicyClient, - AccessPackageCatalogClient: accessPackageCatalogClient, - AccessPackageCatalogRoleAssignmentsClient: accessPackageCatalogRoleAssignmentsClient, - AccessPackageCatalogRoleClient: accessPackageCatalogRoleClient, - AccessPackageClient: accessPackageClient, - AccessPackageResourceClient: accessPackageResourceClient, - AccessPackageResourceRequestClient: accessPackageResourceRequestClient, - AccessPackageResourceRoleScopeClient: accessPackageResourceRoleScopeClient, - PrivilegedAccessGroupAssignmentScheduleRequestsClient: privilegedAccessGroupAssignmentScheduleRequestsClient, - PrivilegedAccessGroupEligibilityScheduleRequestClient: privilegedAccessGroupEligibilityScheduleRequestsClient, + AccessPackageAssignmentPolicyClient: accessPackageAssignmentPolicyClient, + AccessPackageCatalogClient: accessPackageCatalogClient, + AccessPackageCatalogRoleAssignmentsClient: accessPackageCatalogRoleAssignmentsClient, + AccessPackageCatalogRoleClient: accessPackageCatalogRoleClient, + AccessPackageClient: accessPackageClient, + AccessPackageResourceClient: accessPackageResourceClient, + AccessPackageResourceRequestClient: accessPackageResourceRequestClient, + AccessPackageResourceRoleScopeClient: accessPackageResourceRoleScopeClient, + PrivilegedAccessGroupAssignmentScheduleRequestsClient: privilegedAccessGroupAssignmentScheduleRequestsClient, + PrivilegedAccessGroupEligibilityScheduleRequestsClient: privilegedAccessGroupEligibilityScheduleRequestsClient, } } diff --git a/internal/services/identitygovernance/helpers/test.go b/internal/services/identitygovernance/helpers/test.go new file mode 100644 index 0000000000..475817f1e3 --- /dev/null +++ b/internal/services/identitygovernance/helpers/test.go @@ -0,0 +1,15 @@ +package helpers + +import ( + "time" + + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-provider-azuread/internal/acceptance" +) + +func SleepCheck(d time.Duration) acceptance.TestCheckFunc { + return func(s *terraform.State) error { + time.Sleep(d) + return nil + } +} diff --git a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go index 324ce925cb..664289bc31 100644 --- a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go +++ b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go @@ -10,6 +10,7 @@ import ( "slices" "time" + "github.com/hashicorp/go-azure-helpers/lang/pointer" "github.com/hashicorp/terraform-provider-azuread/internal/sdk" "github.com/hashicorp/terraform-provider-azuread/internal/services/identitygovernance/parse" "github.com/hashicorp/terraform-provider-azuread/internal/tf/pluginsdk" @@ -88,30 +89,30 @@ func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Arguments() map[ }, "expiration_date": { - Description: "The date that this assignment expires, formatted as an RFC3339 date string in UTC (e.g. 2018-01-01T01:02:03Z)", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - ConflictsWith: []string{"duration", "permanent_assignment"}, - ValidateFunc: validation.IsRFC3339Time, + Description: "The date that this assignment expires, formatted as an RFC3339 date string in UTC (e.g. 2018-01-01T01:02:03Z)", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ExactlyOneOf: []string{"duration", "expiration_date", "permanent_assignment"}, + ValidateFunc: validation.IsRFC3339Time, }, "duration": { - Description: "The duration of the assignment, formatted as an ISO8601 duration string (e.g. P3D for 3 days)", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - ConflictsWith: []string{"expiration_date", "permanent_assignment"}, - ValidateFunc: validation.StringIsNotEmpty, + Description: "The duration of the assignment, formatted as an ISO8601 duration string (e.g. P3D for 3 days)", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ExactlyOneOf: []string{"duration", "expiration_date", "permanent_assignment"}, + ValidateFunc: validation.StringIsNotEmpty, }, "permanent_assignment": { - Description: "Is the assignment permanent", - Type: pluginsdk.TypeBool, - Optional: true, - ForceNew: true, - Computed: true, - ConflictsWith: []string{"expiration_date", "duration"}, + Description: "Is the assignment permanent", + Type: pluginsdk.TypeBool, + Optional: true, + ForceNew: true, + Computed: true, + ExactlyOneOf: []string{"duration", "expiration_date", "permanent_assignment"}, }, "justification": { @@ -175,7 +176,7 @@ func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Create() sdk.Res if model.ExpirationDate != "" || model.Duration != "" { if model.Duration != "" { schedule.Expiration.Duration = &model.Duration - schedule.Expiration.Type = msgraph.ExpirationPatternTypeAfterDuration + schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeAfterDuration) } if model.ExpirationDate != "" { @@ -184,12 +185,12 @@ func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Create() sdk.Res return fmt.Errorf("parsing %s: %+v", model.StartDate, err) } schedule.Expiration.EndDateTime = &endDate - schedule.Expiration.Type = msgraph.ExpirationPatternTypeAfterDateTime + schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeAfterDateTime) } } else if model.PermanentAssignment { - schedule.Expiration.Type = msgraph.ExpirationPatternTypeNoExpiration + schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeNoExpiration) } else { - schedule.Expiration.Type = msgraph.ExpirationPatternTypeNotSpecified + schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeNotSpecified) } if model.StartDate != "" { @@ -274,7 +275,7 @@ func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Read() sdk.Resou model.AssignmentType = result.AccessId model.GroupId = *result.GroupId model.Justification = *result.Justification - model.PermanentAssignment = result.ScheduleInfo.Expiration.Type == msgraph.ExpirationPatternTypeNoExpiration + model.PermanentAssignment = *result.ScheduleInfo.Expiration.Type == msgraph.ExpirationPatternTypeNoExpiration model.PrincipalId = *result.PrincipalId model.StartDate = result.ScheduleInfo.StartDateTime.Format(time.RFC3339) model.Status = result.Status @@ -317,23 +318,23 @@ func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Delete() sdk.Res switch model.Status { case msgraph.PrivilegedAccessGroupAssignmentStatusDenied: - return cancelRequest(ctx, metadata, client, id) + return cancelAssignmentRequest(ctx, metadata, client, id) case msgraph.PrivilegedAccessGroupAssignmentStatusFailed: - return cancelRequest(ctx, metadata, client, id) + return cancelAssignmentRequest(ctx, metadata, client, id) case msgraph.PrivilegedAccessGroupAssignmentStatusGranted: - return cancelRequest(ctx, metadata, client, id) + return cancelAssignmentRequest(ctx, metadata, client, id) case msgraph.PrivilegedAccessGroupAssignmentStatusPendingAdminDecision: - return cancelRequest(ctx, metadata, client, id) + return cancelAssignmentRequest(ctx, metadata, client, id) case msgraph.PrivilegedAccessGroupAssignmentStatusPendingApproval: - return cancelRequest(ctx, metadata, client, id) + return cancelAssignmentRequest(ctx, metadata, client, id) case msgraph.PrivilegedAccessGroupAssignmentStatusPendingProvisioning: - return cancelRequest(ctx, metadata, client, id) + return cancelAssignmentRequest(ctx, metadata, client, id) case msgraph.PrivilegedAccessGroupAssignmentStatusPendingScheduledCreation: - return cancelRequest(ctx, metadata, client, id) + return cancelAssignmentRequest(ctx, metadata, client, id) case msgraph.PrivilegedAccessGroupAssignmentStatusProvisioned: - return revokeRequest(ctx, metadata, client, id, &model) + return revokeAssignmentRequest(ctx, metadata, client, id, &model) case msgraph.PrivilegedAccessGroupAssignmentStatusScheduleCreated: - return revokeRequest(ctx, metadata, client, id, &model) + return revokeAssignmentRequest(ctx, metadata, client, id, &model) case msgraph.PrivilegedAccessGroupAssignmentStatusCanceled: return metadata.MarkAsGone(id) case msgraph.PrivilegedAccessGroupAssignmentStatusRevoked: @@ -345,7 +346,7 @@ func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Delete() sdk.Res } } -func cancelRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupAssignmentScheduleRequestsClient, id *parse.PrivilegedAccessGroupAssignmentScheduleRequestId) error { +func cancelAssignmentRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupAssignmentScheduleRequestsClient, id *parse.PrivilegedAccessGroupAssignmentScheduleRequestId) error { status, err := client.Cancel(ctx, id.RequestId) if err != nil { if status == http.StatusNotFound { @@ -356,7 +357,7 @@ func cancelRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *m return metadata.MarkAsGone(id) } -func revokeRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupAssignmentScheduleRequestsClient, id *parse.PrivilegedAccessGroupAssignmentScheduleRequestId, model *PrivilegedAccessGroupAssignmentScheduleRequestModel) error { +func revokeAssignmentRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupAssignmentScheduleRequestsClient, id *parse.PrivilegedAccessGroupAssignmentScheduleRequestId, model *PrivilegedAccessGroupAssignmentScheduleRequestModel) error { result, status, err := client.Create(ctx, msgraph.PrivilegedAccessGroupAssignmentScheduleRequest{ ID: &id.RequestId, AccessId: model.AssignmentType, diff --git a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request_test.go b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request_test.go index 029d9a7e80..1b6427e4a1 100644 --- a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request_test.go +++ b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request_test.go @@ -16,6 +16,7 @@ import ( "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/identitygovernance/helpers" "github.com/manicminer/hamilton/msgraph" ) @@ -35,7 +36,7 @@ func TestPrivilegedAccessGroupAssignmentScheduleRequest_member(t *testing.T) { // There is a minimum life of 5 minutes for a schedule request to exist. // Attempting to delete the request within this time frame will result in // a 400 error on destroy, which we can't trap. - sleepCheck(5*time.Minute+1*time.Second), + helpers.SleepCheck(5*time.Minute+1*time.Second), ), }, }) @@ -53,7 +54,7 @@ func TestPrivilegedAccessGroupAssignmentScheduleRequest_owner(t *testing.T) { // There is a minimum life of 5 minutes for a schedule request to exist. // Attempting to delete the request within this time frame will result in // a 400 error on destroy, which we can't trap. - sleepCheck(5*time.Minute+1*time.Second), + helpers.SleepCheck(5*time.Minute+1*time.Second), ), }, }) @@ -156,10 +157,3 @@ resource "azuread_privileged_access_group_assignment_schedule_request" "owner" { } `, data.RandomString, data.RandomPassword) } - -func sleepCheck(d time.Duration) acceptance.TestCheckFunc { - return func(s *terraform.State) error { - time.Sleep(d) - return nil - } -} diff --git a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go index c928899e25..2fcb8126aa 100644 --- a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go +++ b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go @@ -10,6 +10,7 @@ import ( "slices" "time" + "github.com/hashicorp/go-azure-helpers/lang/pointer" "github.com/hashicorp/terraform-provider-azuread/internal/sdk" "github.com/hashicorp/terraform-provider-azuread/internal/services/identitygovernance/parse" "github.com/hashicorp/terraform-provider-azuread/internal/tf/pluginsdk" @@ -162,7 +163,7 @@ func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Create() sdk.Re return sdk.ResourceFunc{ Timeout: 5 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { - client := metadata.Client.IdentityGovernance.PrivilegedAccessGroupEligibilityScheduleRequestClient + client := metadata.Client.IdentityGovernance.PrivilegedAccessGroupEligibilityScheduleRequestsClient var model PrivilegedAccessGroupEligibilityScheduleRequestModel if err := metadata.Decode(&model); err != nil { @@ -175,7 +176,7 @@ func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Create() sdk.Re if model.ExpirationDate != "" || model.Duration != "" { if model.Duration != "" { schedule.Expiration.Duration = &model.Duration - schedule.Expiration.Type = msgraph.ExpirationPatternTypeAfterDuration + schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeAfterDuration) } if model.ExpirationDate != "" { @@ -184,12 +185,12 @@ func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Create() sdk.Re return fmt.Errorf("parsing %s: %+v", model.StartDate, err) } schedule.Expiration.EndDateTime = &endDate - schedule.Expiration.Type = msgraph.ExpirationPatternTypeAfterDateTime + schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeAfterDateTime) } } else if model.PermanentAssignment { - schedule.Expiration.Type = msgraph.ExpirationPatternTypeNoExpiration + schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeNoExpiration) } else { - schedule.Expiration.Type = msgraph.ExpirationPatternTypeNotSpecified + schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeNotSpecified) } if model.StartDate != "" { @@ -241,7 +242,7 @@ func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Read() sdk.Reso return sdk.ResourceFunc{ Timeout: 5 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { - client := metadata.Client.IdentityGovernance.PrivilegedAccessGroupEligibilityScheduleRequestClient + client := metadata.Client.IdentityGovernance.PrivilegedAccessGroupEligibilityScheduleRequestsClient id, err := parse.ParsePrivilegedAccessGroupEligibilityScheduleRequestID(metadata.ResourceData.Id()) if err != nil { @@ -274,7 +275,7 @@ func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Read() sdk.Reso model.AssignmentType = result.AccessId model.GroupId = *result.GroupId model.Justification = *result.Justification - model.PermanentAssignment = result.ScheduleInfo.Expiration.Type == msgraph.ExpirationPatternTypeNoExpiration + model.PermanentAssignment = *result.ScheduleInfo.Expiration.Type == msgraph.ExpirationPatternTypeNoExpiration model.PrincipalId = *result.PrincipalId model.StartDate = result.ScheduleInfo.StartDateTime.Format(time.RFC3339) model.Status = result.Status @@ -303,7 +304,7 @@ func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Delete() sdk.Re return sdk.ResourceFunc{ Timeout: 5 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { - client := metadata.Client.IdentityGovernance.PrivilegedAccessGroupEligibilityScheduleRequestClient + client := metadata.Client.IdentityGovernance.PrivilegedAccessGroupEligibilityScheduleRequestsClient id, err := parse.ParsePrivilegedAccessGroupEligibilityScheduleRequestID(metadata.ResourceData.Id()) if err != nil { @@ -317,23 +318,23 @@ func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Delete() sdk.Re switch model.Status { case msgraph.PrivilegedAccessGroupEligibilityStatusDenied: - return cancelRequest(ctx, metadata, client, id) + return cancelEligibilityRequest(ctx, metadata, client, id) case msgraph.PrivilegedAccessGroupEligibilityStatusFailed: - return cancelRequest(ctx, metadata, client, id) + return cancelEligibilityRequest(ctx, metadata, client, id) case msgraph.PrivilegedAccessGroupEligibilityStatusGranted: - return cancelRequest(ctx, metadata, client, id) + return cancelEligibilityRequest(ctx, metadata, client, id) case msgraph.PrivilegedAccessGroupEligibilityStatusPendingAdminDecision: - return cancelRequest(ctx, metadata, client, id) + return cancelEligibilityRequest(ctx, metadata, client, id) case msgraph.PrivilegedAccessGroupEligibilityStatusPendingApproval: - return cancelRequest(ctx, metadata, client, id) + return cancelEligibilityRequest(ctx, metadata, client, id) case msgraph.PrivilegedAccessGroupEligibilityStatusPendingProvisioning: - return cancelRequest(ctx, metadata, client, id) + return cancelEligibilityRequest(ctx, metadata, client, id) case msgraph.PrivilegedAccessGroupEligibilityStatusPendingScheduledCreation: - return cancelRequest(ctx, metadata, client, id) + return cancelEligibilityRequest(ctx, metadata, client, id) case msgraph.PrivilegedAccessGroupEligibilityStatusProvisioned: - return revokeRequest(ctx, metadata, client, id, &model) + return revokeEligibilityRequest(ctx, metadata, client, id, &model) case msgraph.PrivilegedAccessGroupEligibilityStatusScheduleCreated: - return revokeRequest(ctx, metadata, client, id, &model) + return revokeEligibilityRequest(ctx, metadata, client, id, &model) case msgraph.PrivilegedAccessGroupEligibilityStatusCanceled: return metadata.MarkAsGone(id) case msgraph.PrivilegedAccessGroupEligibilityStatusRevoked: @@ -345,7 +346,7 @@ func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Delete() sdk.Re } } -func cancelRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupEligibilityScheduleRequestClient, id *parse.PrivilegedAccessGroupEligibilityScheduleRequestId) error { +func cancelEligibilityRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupEligibilityScheduleRequestsClient, id *parse.PrivilegedAccessGroupEligibilityScheduleRequestId) error { status, err := client.Cancel(ctx, id.RequestId) if err != nil { if status == http.StatusNotFound { @@ -356,7 +357,7 @@ func cancelRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *m return metadata.MarkAsGone(id) } -func revokeRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupEligibilityScheduleRequestClient, id *parse.PrivilegedAccessGroupEligibilityScheduleRequestId, model *PrivilegedAccessGroupEligibilityScheduleRequestModel) error { +func revokeEligibilityRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupEligibilityScheduleRequestsClient, id *parse.PrivilegedAccessGroupEligibilityScheduleRequestId, model *PrivilegedAccessGroupEligibilityScheduleRequestModel) error { result, status, err := client.Create(ctx, msgraph.PrivilegedAccessGroupEligibilityScheduleRequest{ ID: &id.RequestId, AccessId: model.AssignmentType, diff --git a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request_test.go b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request_test.go index 6f68bf8ceb..c7b81649c0 100644 --- a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request_test.go +++ b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request_test.go @@ -16,6 +16,7 @@ import ( "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/identitygovernance/helpers" "github.com/manicminer/hamilton/msgraph" ) @@ -35,7 +36,7 @@ func TestPrivilegedAccessGroupEligiblityScheduleRequest_member(t *testing.T) { // There is a minimum life of 5 minutes for a schedule request to exist. // Attempting to delete the request within this time frame will result in // a 400 error on destroy, which we can't trap. - sleepCheck(5*time.Minute+1*time.Second), + helpers.SleepCheck(5*time.Minute+1*time.Second), ), }, }) @@ -53,7 +54,7 @@ func TestPrivilegedAccessGroupEligiblityScheduleRequest_owner(t *testing.T) { // There is a minimum life of 5 minutes for a schedule request to exist. // Attempting to delete the request within this time frame will result in // a 400 error on destroy, which we can't trap. - sleepCheck(5*time.Minute+1*time.Second), + helpers.SleepCheck(5*time.Minute+1*time.Second), ), }, }) @@ -61,7 +62,7 @@ func TestPrivilegedAccessGroupEligiblityScheduleRequest_owner(t *testing.T) { } func (PrivilegedAccessGroupEligiblityScheduleRequestResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { - client := clients.IdentityGovernance.PrivilegedAccessGroupEligibilityScheduleRequestClient + client := clients.IdentityGovernance.PrivilegedAccessGroupEligibilityScheduleRequestsClient client.BaseClient.DisableRetries = true defer func() { client.BaseClient.DisableRetries = false }() @@ -156,10 +157,3 @@ resource "azuread_privileged_access_group_eligibility_schedule_request" "owner" } `, data.RandomString, data.RandomPassword) } - -func sleepCheck(d time.Duration) acceptance.TestCheckFunc { - return func(s *terraform.State) error { - time.Sleep(d) - return nil - } -} From 3f079ccaf9384c1a953b731fe283472d650f0817 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Thu, 7 Mar 2024 11:22:33 +1300 Subject: [PATCH 35/55] Ensure all validation functions are ValidateDiagFuncs --- ...ccess_group_assignment_schedule_request.go | 85 +++++++++---------- ...ccess_group_eligiblity_schedule_request.go | 75 ++++++++-------- 2 files changed, 79 insertions(+), 81 deletions(-) diff --git a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go index 664289bc31..5894939cf8 100644 --- a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go +++ b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go @@ -72,73 +72,72 @@ func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Arguments() map[ Type: pluginsdk.TypeString, Required: true, ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{ + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ msgraph.PrivilegedAccessGroupRelationshipMember, msgraph.PrivilegedAccessGroupRelationshipOwner, msgraph.PrivilegedAccessGroupRelationshipUnknown, - }, false), + }, false)), }, "start_date": { - Description: "The date that this assignment starts, formatted as an RFC3339 date string in UTC (e.g. 2018-01-01T01:02:03Z)", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - Computed: true, - ValidateFunc: validation.IsRFC3339Time, + Description: "The date that this assignment starts, formatted as an RFC3339 date string in UTC (e.g. 2018-01-01T01:02:03Z)", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + ValidateDiagFunc: validation.ValidateDiag(validation.IsRFC3339Time), }, "expiration_date": { - Description: "The date that this assignment expires, formatted as an RFC3339 date string in UTC (e.g. 2018-01-01T01:02:03Z)", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - ExactlyOneOf: []string{"duration", "expiration_date", "permanent_assignment"}, - ValidateFunc: validation.IsRFC3339Time, + Description: "The date that this assignment expires, formatted as an RFC3339 date string in UTC (e.g. 2018-01-01T01:02:03Z)", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"duration"}, + ValidateDiagFunc: validation.ValidateDiag(validation.IsRFC3339Time), }, "duration": { - Description: "The duration of the assignment, formatted as an ISO8601 duration string (e.g. P3D for 3 days)", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - ExactlyOneOf: []string{"duration", "expiration_date", "permanent_assignment"}, - ValidateFunc: validation.StringIsNotEmpty, + Description: "The duration of the assignment, formatted as an ISO8601 duration string (e.g. P3D for 3 days)", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"expiration_date"}, + ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), }, "permanent_assignment": { - Description: "Is the assignment permanent", - Type: pluginsdk.TypeBool, - Optional: true, - ForceNew: true, - Computed: true, - ExactlyOneOf: []string{"duration", "expiration_date", "permanent_assignment"}, + Description: "Is the assignment permanent", + Type: pluginsdk.TypeBool, + Optional: true, + ForceNew: true, + Computed: true, }, "justification": { - Description: "The justification for the assignment", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - ValidateFunc: validation.StringIsNotEmpty, + Description: "The justification for the assignment", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), }, "ticket_number": { - Description: "The ticket number authorising the assignment", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - RequiredWith: []string{"ticket_system"}, - ValidateFunc: validation.StringIsNotEmpty, + Description: "The ticket number authorising the assignment", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + RequiredWith: []string{"ticket_system"}, + ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), }, "ticket_system": { - Description: "The ticket system authorising the assignment", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - RequiredWith: []string{"ticket_number"}, - ValidateFunc: validation.StringIsNotEmpty, + Description: "The ticket system authorising the assignment", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + RequiredWith: []string{"ticket_number"}, + ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), }, } } diff --git a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go index 2fcb8126aa..8aa9ced404 100644 --- a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go +++ b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go @@ -72,47 +72,46 @@ func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Arguments() map Type: pluginsdk.TypeString, Required: true, ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{ + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ msgraph.PrivilegedAccessGroupRelationshipMember, msgraph.PrivilegedAccessGroupRelationshipOwner, msgraph.PrivilegedAccessGroupRelationshipUnknown, - }, false), + }, false)), }, "start_date": { - Description: "The date that this assignment starts, formatted as an RFC3339 date string in UTC (e.g. 2018-01-01T01:02:03Z)", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - Computed: true, - ValidateFunc: validation.IsRFC3339Time, + Description: "The date that this assignment starts, formatted as an RFC3339 date string in UTC (e.g. 2018-01-01T01:02:03Z)", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + ValidateDiagFunc: validation.ValidateDiag(validation.IsRFC3339Time), }, "expiration_date": { - Description: "The date that this assignment expires, formatted as an RFC3339 date string in UTC (e.g. 2018-01-01T01:02:03Z)", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - ConflictsWith: []string{"duration", "permanent_assignment"}, - ValidateFunc: validation.IsRFC3339Time, + Description: "The date that this assignment expires, formatted as an RFC3339 date string in UTC (e.g. 2018-01-01T01:02:03Z)", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"duration"}, + ValidateDiagFunc: validation.ValidateDiag(validation.IsRFC3339Time), }, "duration": { - Description: "The duration of the assignment, formatted as an ISO8601 duration string (e.g. P3D for 3 days)", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - ConflictsWith: []string{"expiration_date", "permanent_assignment"}, - ValidateFunc: validation.StringIsNotEmpty, + Description: "The duration of the assignment, formatted as an ISO8601 duration string (e.g. P3D for 3 days)", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"expiration_date"}, + ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), }, "permanent_assignment": { - Description: "Is the assignment permanent", - Type: pluginsdk.TypeBool, - Optional: true, - ForceNew: true, - Computed: true, - ConflictsWith: []string{"expiration_date", "duration"}, + Description: "Is the assignment permanent", + Type: pluginsdk.TypeBool, + Optional: true, + ForceNew: true, + Computed: true, }, "justification": { @@ -124,21 +123,21 @@ func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Arguments() map }, "ticket_number": { - Description: "The ticket number authorising the assignment", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - RequiredWith: []string{"ticket_system"}, - ValidateFunc: validation.StringIsNotEmpty, + Description: "The ticket number authorising the assignment", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + RequiredWith: []string{"ticket_system"}, + ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), }, "ticket_system": { - Description: "The ticket system authorising the assignment", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - RequiredWith: []string{"ticket_number"}, - ValidateFunc: validation.StringIsNotEmpty, + Description: "The ticket system authorising the assignment", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + RequiredWith: []string{"ticket_number"}, + ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), }, } } From 0c66c13e9db87119c694c694c3ef6e9f5aa6c6e4 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Thu, 7 Mar 2024 11:28:50 +1300 Subject: [PATCH 36/55] Catch error where no valid expiry defined --- .../privileged_access_group_assignment_schedule_request.go | 2 +- .../privileged_access_group_eligiblity_schedule_request.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go index 5894939cf8..3f83aa1061 100644 --- a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go +++ b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go @@ -189,7 +189,7 @@ func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Create() sdk.Res } else if model.PermanentAssignment { schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeNoExpiration) } else { - schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeNotSpecified) + return fmt.Errorf("either expiration_date or duration must be set, or permanent_assignment must be true") } if model.StartDate != "" { diff --git a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go index 8aa9ced404..97aa36d3d1 100644 --- a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go +++ b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go @@ -189,7 +189,7 @@ func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Create() sdk.Re } else if model.PermanentAssignment { schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeNoExpiration) } else { - schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeNotSpecified) + return fmt.Errorf("either expiration_date or duration must be set, or permanent_assignment must be true") } if model.StartDate != "" { From 78ce8d2998451b10ac0994eaabc6772bc48dc9c9 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Fri, 8 Mar 2024 10:45:56 +1300 Subject: [PATCH 37/55] Combine common code for schedule requests --- ...ccess_group_assignment_schedule_request.go | 130 +---------------- ...ccess_group_eligiblity_schedule_request.go | 130 +---------------- ...rivileged_access_group_schedule_request.go | 134 ++++++++++++++++++ 3 files changed, 148 insertions(+), 246 deletions(-) create mode 100644 internal/services/identitygovernance/privileged_access_group_schedule_request.go diff --git a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go index 3f83aa1061..9f1989a241 100644 --- a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go +++ b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go @@ -18,21 +18,6 @@ import ( "github.com/manicminer/hamilton/msgraph" ) -type PrivilegedAccessGroupAssignmentScheduleRequestModel struct { - AssignmentType string `tfschema:"assignment_type"` - Duration string `tfschema:"duration"` - ExpirationDate string `tfschema:"expiration_date"` - GroupId string `tfschema:"group_id"` - Justification string `tfschema:"justification"` - PermanentAssignment bool `tfschema:"permanent_assignment"` - PrincipalId string `tfschema:"principal_id"` - StartDate string `tfschema:"start_date"` - Status string `tfschema:"status"` - TargetScheduleId string `tfschema:"target_schedule_id"` - TicketNumber string `tfschema:"ticket_number"` - TicketSystem string `tfschema:"ticket_system"` -} - type PrivilegedAccessGroupAssignmentScheduleRequestResource struct{} func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { @@ -46,116 +31,15 @@ func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) ResourceType() s } func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) ModelObject() interface{} { - return &PrivilegedAccessGroupAssignmentScheduleRequestModel{} + return &PrivilegedAccessGroupScheduleRequestModel{} } func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Arguments() map[string]*pluginsdk.Schema { - return map[string]*pluginsdk.Schema{ - "group_id": { - Description: "The ID of the Group representing the scope of the assignment", - Type: pluginsdk.TypeString, - Required: true, - ForceNew: true, - ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), - }, - - "principal_id": { - Description: "The ID of the Principal assigned to the schedule", - Type: pluginsdk.TypeString, - Required: true, - ForceNew: true, - ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), - }, - - "assignment_type": { - Description: "The ID of the assignment to the group", - Type: pluginsdk.TypeString, - Required: true, - ForceNew: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ - msgraph.PrivilegedAccessGroupRelationshipMember, - msgraph.PrivilegedAccessGroupRelationshipOwner, - msgraph.PrivilegedAccessGroupRelationshipUnknown, - }, false)), - }, - - "start_date": { - Description: "The date that this assignment starts, formatted as an RFC3339 date string in UTC (e.g. 2018-01-01T01:02:03Z)", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - Computed: true, - ValidateDiagFunc: validation.ValidateDiag(validation.IsRFC3339Time), - }, - - "expiration_date": { - Description: "The date that this assignment expires, formatted as an RFC3339 date string in UTC (e.g. 2018-01-01T01:02:03Z)", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - ConflictsWith: []string{"duration"}, - ValidateDiagFunc: validation.ValidateDiag(validation.IsRFC3339Time), - }, - - "duration": { - Description: "The duration of the assignment, formatted as an ISO8601 duration string (e.g. P3D for 3 days)", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - ConflictsWith: []string{"expiration_date"}, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), - }, - - "permanent_assignment": { - Description: "Is the assignment permanent", - Type: pluginsdk.TypeBool, - Optional: true, - ForceNew: true, - Computed: true, - }, - - "justification": { - Description: "The justification for the assignment", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), - }, - - "ticket_number": { - Description: "The ticket number authorising the assignment", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - RequiredWith: []string{"ticket_system"}, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), - }, - - "ticket_system": { - Description: "The ticket system authorising the assignment", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - RequiredWith: []string{"ticket_number"}, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), - }, - } + return privilegedAccessGroupScheduleRequestArguments() } func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Attributes() map[string]*pluginsdk.Schema { - return map[string]*pluginsdk.Schema{ - "status": { - Description: "The status of the Schedule Request", - Type: pluginsdk.TypeString, - Computed: true, - }, - - "target_schedule_id": { - Description: "The ID of the Schedule targeted by the request", - Type: pluginsdk.TypeString, - Computed: true, - }, - } + return privilegedAccessGroupScheduleRequestAttributes() } func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Create() sdk.ResourceFunc { @@ -164,7 +48,7 @@ func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Create() sdk.Res Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { client := metadata.Client.IdentityGovernance.PrivilegedAccessGroupAssignmentScheduleRequestsClient - var model PrivilegedAccessGroupAssignmentScheduleRequestModel + var model PrivilegedAccessGroupScheduleRequestModel if err := metadata.Decode(&model); err != nil { return fmt.Errorf("decoding: %+v", err) } @@ -248,7 +132,7 @@ func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Read() sdk.Resou return err } - var model PrivilegedAccessGroupAssignmentScheduleRequestModel + var model PrivilegedAccessGroupScheduleRequestModel if err := metadata.Decode(&model); err != nil { return fmt.Errorf("decoding: %+v", err) } @@ -310,7 +194,7 @@ func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Delete() sdk.Res return err } - var model PrivilegedAccessGroupAssignmentScheduleRequestModel + var model PrivilegedAccessGroupScheduleRequestModel if err := metadata.Decode(&model); err != nil { return fmt.Errorf("decoding: %+v", err) } @@ -356,7 +240,7 @@ func cancelAssignmentRequest(ctx context.Context, metadata sdk.ResourceMetaData, return metadata.MarkAsGone(id) } -func revokeAssignmentRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupAssignmentScheduleRequestsClient, id *parse.PrivilegedAccessGroupAssignmentScheduleRequestId, model *PrivilegedAccessGroupAssignmentScheduleRequestModel) error { +func revokeAssignmentRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupAssignmentScheduleRequestsClient, id *parse.PrivilegedAccessGroupAssignmentScheduleRequestId, model *PrivilegedAccessGroupScheduleRequestModel) error { result, status, err := client.Create(ctx, msgraph.PrivilegedAccessGroupAssignmentScheduleRequest{ ID: &id.RequestId, AccessId: model.AssignmentType, diff --git a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go index 97aa36d3d1..cd9f4627db 100644 --- a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go +++ b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go @@ -18,21 +18,6 @@ import ( "github.com/manicminer/hamilton/msgraph" ) -type PrivilegedAccessGroupEligibilityScheduleRequestModel struct { - AssignmentType string `tfschema:"assignment_type"` - Duration string `tfschema:"duration"` - ExpirationDate string `tfschema:"expiration_date"` - GroupId string `tfschema:"group_id"` - Justification string `tfschema:"justification"` - PermanentAssignment bool `tfschema:"permanent_assignment"` - PrincipalId string `tfschema:"principal_id"` - StartDate string `tfschema:"start_date"` - Status string `tfschema:"status"` - TargetScheduleId string `tfschema:"target_schedule_id"` - TicketNumber string `tfschema:"ticket_number"` - TicketSystem string `tfschema:"ticket_system"` -} - type PrivilegedAccessGroupEligibilityScheduleRequestResource struct{} func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { @@ -46,116 +31,15 @@ func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) ResourceType() } func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) ModelObject() interface{} { - return &PrivilegedAccessGroupEligibilityScheduleRequestModel{} + return &PrivilegedAccessGroupScheduleRequestModel{} } func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Arguments() map[string]*pluginsdk.Schema { - return map[string]*pluginsdk.Schema{ - "group_id": { - Description: "The ID of the Group representing the scope of the assignment", - Type: pluginsdk.TypeString, - Required: true, - ForceNew: true, - ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), - }, - - "principal_id": { - Description: "The ID of the Principal assigned to the schedule", - Type: pluginsdk.TypeString, - Required: true, - ForceNew: true, - ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), - }, - - "assignment_type": { - Description: "The ID of the assignment to the group", - Type: pluginsdk.TypeString, - Required: true, - ForceNew: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ - msgraph.PrivilegedAccessGroupRelationshipMember, - msgraph.PrivilegedAccessGroupRelationshipOwner, - msgraph.PrivilegedAccessGroupRelationshipUnknown, - }, false)), - }, - - "start_date": { - Description: "The date that this assignment starts, formatted as an RFC3339 date string in UTC (e.g. 2018-01-01T01:02:03Z)", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - Computed: true, - ValidateDiagFunc: validation.ValidateDiag(validation.IsRFC3339Time), - }, - - "expiration_date": { - Description: "The date that this assignment expires, formatted as an RFC3339 date string in UTC (e.g. 2018-01-01T01:02:03Z)", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - ConflictsWith: []string{"duration"}, - ValidateDiagFunc: validation.ValidateDiag(validation.IsRFC3339Time), - }, - - "duration": { - Description: "The duration of the assignment, formatted as an ISO8601 duration string (e.g. P3D for 3 days)", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - ConflictsWith: []string{"expiration_date"}, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), - }, - - "permanent_assignment": { - Description: "Is the assignment permanent", - Type: pluginsdk.TypeBool, - Optional: true, - ForceNew: true, - Computed: true, - }, - - "justification": { - Description: "The justification for the assignment", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - - "ticket_number": { - Description: "The ticket number authorising the assignment", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - RequiredWith: []string{"ticket_system"}, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), - }, - - "ticket_system": { - Description: "The ticket system authorising the assignment", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - RequiredWith: []string{"ticket_number"}, - ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), - }, - } + return privilegedAccessGroupScheduleRequestArguments() } func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Attributes() map[string]*pluginsdk.Schema { - return map[string]*pluginsdk.Schema{ - "status": { - Description: "The status of the Schedule Request", - Type: pluginsdk.TypeString, - Computed: true, - }, - - "target_schedule_id": { - Description: "The ID of the Schedule targeted by the request", - Type: pluginsdk.TypeString, - Computed: true, - }, - } + return privilegedAccessGroupScheduleRequestAttributes() } func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Create() sdk.ResourceFunc { @@ -164,7 +48,7 @@ func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Create() sdk.Re Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { client := metadata.Client.IdentityGovernance.PrivilegedAccessGroupEligibilityScheduleRequestsClient - var model PrivilegedAccessGroupEligibilityScheduleRequestModel + var model PrivilegedAccessGroupScheduleRequestModel if err := metadata.Decode(&model); err != nil { return fmt.Errorf("decoding: %+v", err) } @@ -248,7 +132,7 @@ func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Read() sdk.Reso return err } - var model PrivilegedAccessGroupEligibilityScheduleRequestModel + var model PrivilegedAccessGroupScheduleRequestModel if err := metadata.Decode(&model); err != nil { return fmt.Errorf("decoding: %+v", err) } @@ -310,7 +194,7 @@ func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Delete() sdk.Re return err } - var model PrivilegedAccessGroupEligibilityScheduleRequestModel + var model PrivilegedAccessGroupScheduleRequestModel if err := metadata.Decode(&model); err != nil { return fmt.Errorf("decoding: %+v", err) } @@ -356,7 +240,7 @@ func cancelEligibilityRequest(ctx context.Context, metadata sdk.ResourceMetaData return metadata.MarkAsGone(id) } -func revokeEligibilityRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupEligibilityScheduleRequestsClient, id *parse.PrivilegedAccessGroupEligibilityScheduleRequestId, model *PrivilegedAccessGroupEligibilityScheduleRequestModel) error { +func revokeEligibilityRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupEligibilityScheduleRequestsClient, id *parse.PrivilegedAccessGroupEligibilityScheduleRequestId, model *PrivilegedAccessGroupScheduleRequestModel) error { result, status, err := client.Create(ctx, msgraph.PrivilegedAccessGroupEligibilityScheduleRequest{ ID: &id.RequestId, AccessId: model.AssignmentType, diff --git a/internal/services/identitygovernance/privileged_access_group_schedule_request.go b/internal/services/identitygovernance/privileged_access_group_schedule_request.go new file mode 100644 index 0000000000..a38f905339 --- /dev/null +++ b/internal/services/identitygovernance/privileged_access_group_schedule_request.go @@ -0,0 +1,134 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package identitygovernance + +import ( + "github.com/hashicorp/terraform-provider-azuread/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azuread/internal/tf/validation" + "github.com/manicminer/hamilton/msgraph" +) + +type PrivilegedAccessGroupScheduleRequestModel struct { + AssignmentType string `tfschema:"assignment_type"` + Duration string `tfschema:"duration"` + ExpirationDate string `tfschema:"expiration_date"` + GroupId string `tfschema:"group_id"` + Justification string `tfschema:"justification"` + PermanentAssignment bool `tfschema:"permanent_assignment"` + PrincipalId string `tfschema:"principal_id"` + StartDate string `tfschema:"start_date"` + Status string `tfschema:"status"` + TargetScheduleId string `tfschema:"target_schedule_id"` + TicketNumber string `tfschema:"ticket_number"` + TicketSystem string `tfschema:"ticket_system"` +} + +func privilegedAccessGroupScheduleRequestArguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "group_id": { + Description: "The ID of the Group representing the scope of the assignment", + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + }, + + "principal_id": { + Description: "The ID of the Principal assigned to the schedule", + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + }, + + "assignment_type": { + Description: "The ID of the assignment to the group", + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ + msgraph.PrivilegedAccessGroupRelationshipMember, + msgraph.PrivilegedAccessGroupRelationshipOwner, + msgraph.PrivilegedAccessGroupRelationshipUnknown, + }, false)), + }, + + "start_date": { + Description: "The date that this assignment starts, formatted as an RFC3339 date string in UTC (e.g. 2018-01-01T01:02:03Z)", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + ValidateDiagFunc: validation.ValidateDiag(validation.IsRFC3339Time), + }, + + "expiration_date": { + Description: "The date that this assignment expires, formatted as an RFC3339 date string in UTC (e.g. 2018-01-01T01:02:03Z)", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"duration"}, + ValidateDiagFunc: validation.ValidateDiag(validation.IsRFC3339Time), + }, + + "duration": { + Description: "The duration of the assignment, formatted as an ISO8601 duration string (e.g. P3D for 3 days)", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"expiration_date"}, + ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + }, + + "permanent_assignment": { + Description: "Is the assignment permanent", + Type: pluginsdk.TypeBool, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "justification": { + Description: "The justification for the assignment", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + }, + + "ticket_number": { + Description: "The ticket number authorising the assignment", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + RequiredWith: []string{"ticket_system"}, + ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + }, + + "ticket_system": { + Description: "The ticket system authorising the assignment", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + RequiredWith: []string{"ticket_number"}, + ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + }, + } +} + +func privilegedAccessGroupScheduleRequestAttributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "status": { + Description: "The status of the Schedule Request", + Type: pluginsdk.TypeString, + Computed: true, + }, + + "target_schedule_id": { + Description: "The ID of the Schedule targeted by the request", + Type: pluginsdk.TypeString, + Computed: true, + }, + } +} From 44aec6d480825e3220d40ef8927c3d589baa238d Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Fri, 8 Mar 2024 10:47:12 +1300 Subject: [PATCH 38/55] Better validation of start and expiry dates --- ...rivileged_access_group_schedule_request.go | 5 +- ...rivileged_access_group_schedule_request.go | 100 ++++++++++++++++++ 2 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 internal/services/identitygovernance/validate/privileged_access_group_schedule_request.go diff --git a/internal/services/identitygovernance/privileged_access_group_schedule_request.go b/internal/services/identitygovernance/privileged_access_group_schedule_request.go index a38f905339..254b95e555 100644 --- a/internal/services/identitygovernance/privileged_access_group_schedule_request.go +++ b/internal/services/identitygovernance/privileged_access_group_schedule_request.go @@ -4,6 +4,7 @@ package identitygovernance import ( + "github.com/hashicorp/terraform-provider-azuread/internal/services/identitygovernance/validate" "github.com/hashicorp/terraform-provider-azuread/internal/tf/pluginsdk" "github.com/hashicorp/terraform-provider-azuread/internal/tf/validation" "github.com/manicminer/hamilton/msgraph" @@ -60,7 +61,7 @@ func privilegedAccessGroupScheduleRequestArguments() map[string]*pluginsdk.Schem Optional: true, ForceNew: true, Computed: true, - ValidateDiagFunc: validation.ValidateDiag(validation.IsRFC3339Time), + ValidateDiagFunc: validate.ScheduleStartDate, }, "expiration_date": { @@ -69,7 +70,7 @@ func privilegedAccessGroupScheduleRequestArguments() map[string]*pluginsdk.Schem Optional: true, ForceNew: true, ConflictsWith: []string{"duration"}, - ValidateDiagFunc: validation.ValidateDiag(validation.IsRFC3339Time), + ValidateDiagFunc: validate.ScheduleExpiryDate, }, "duration": { diff --git a/internal/services/identitygovernance/validate/privileged_access_group_schedule_request.go b/internal/services/identitygovernance/validate/privileged_access_group_schedule_request.go new file mode 100644 index 0000000000..e318fda570 --- /dev/null +++ b/internal/services/identitygovernance/validate/privileged_access_group_schedule_request.go @@ -0,0 +1,100 @@ +package validate + +import ( + "fmt" + "time" + + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-provider-azuread/internal/tf/validation" +) + +func ScheduleStartDate(i interface{}, path cty.Path) (ret diag.Diagnostics) { + v, ok := i.(string) + if !ok { + ret = append(ret, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Expected a string value", + }) + return + } + + warnings, errs := validation.IsRFC3339Time(v, "start_date") + if len(warnings) > 0 || len(errs) > 0 { + for _, warning := range warnings { + ret = append(ret, diag.Diagnostic{ + Severity: diag.Warning, + Summary: warning, + }) + } + for _, err := range errs { + ret = append(ret, diag.Diagnostic{ + Severity: diag.Error, + Summary: err.Error(), + }) + } + return + } + + t, err := time.Parse(time.RFC3339, v) + if err != nil { + ret = append(ret, diag.Diagnostic{ + Severity: diag.Error, + Summary: fmt.Sprintf("Failed to parse start date: %v+", err), + }) + } + + if t.Before(time.Now()) { + ret = append(ret, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Start date must be in the future", + }) + } + + return +} + +func ScheduleExpiryDate(i interface{}, path cty.Path) (ret diag.Diagnostics) { + v, ok := i.(string) + if !ok { + ret = append(ret, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Expected a string value", + }) + return + } + + warnings, errs := validation.IsRFC3339Time(v, "expiration_date") + if len(warnings) > 0 || len(errs) > 0 { + for _, warning := range warnings { + ret = append(ret, diag.Diagnostic{ + Severity: diag.Warning, + Summary: warning, + }) + } + for _, err := range errs { + ret = append(ret, diag.Diagnostic{ + Severity: diag.Error, + Summary: err.Error(), + }) + } + return + } + + t, err := time.Parse(time.RFC3339, v) + if err != nil { + ret = append(ret, diag.Diagnostic{ + Severity: diag.Error, + Summary: fmt.Sprintf("Failed to parse start date: %v+", err), + }) + } + + if t.Before(time.Now().Add(time.Minute * 5)) { + ret = append(ret, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Expiry date must be at least 5 minutes in the future", + }) + } + + return +} From 2edafdaf0680428bd11fd52eb034d65ae2105e17 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Fri, 8 Mar 2024 11:34:39 +1300 Subject: [PATCH 39/55] Centralise schedule resource build --- ...ccess_group_assignment_schedule_request.go | 45 ++------ ...ccess_group_eligiblity_schedule_request.go | 45 ++------ ...rivileged_access_group_schedule_request.go | 58 +++++++++- ...rivileged_access_group_schedule_request.go | 100 ------------------ 4 files changed, 73 insertions(+), 175 deletions(-) delete mode 100644 internal/services/identitygovernance/validate/privileged_access_group_schedule_request.go diff --git a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go index 9f1989a241..31dcc87e8a 100644 --- a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go +++ b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go @@ -10,7 +10,6 @@ import ( "slices" "time" - "github.com/hashicorp/go-azure-helpers/lang/pointer" "github.com/hashicorp/terraform-provider-azuread/internal/sdk" "github.com/hashicorp/terraform-provider-azuread/internal/services/identitygovernance/parse" "github.com/hashicorp/terraform-provider-azuread/internal/tf/pluginsdk" @@ -53,38 +52,9 @@ func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Create() sdk.Res return fmt.Errorf("decoding: %+v", err) } - schedule := msgraph.RequestSchedule{} - schedule.Expiration = &msgraph.ExpirationPattern{} - - if model.ExpirationDate != "" || model.Duration != "" { - if model.Duration != "" { - schedule.Expiration.Duration = &model.Duration - schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeAfterDuration) - } - - if model.ExpirationDate != "" { - endDate, err := time.Parse(time.RFC3339, model.ExpirationDate) - if err != nil { - return fmt.Errorf("parsing %s: %+v", model.StartDate, err) - } - schedule.Expiration.EndDateTime = &endDate - schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeAfterDateTime) - } - } else if model.PermanentAssignment { - schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeNoExpiration) - } else { - return fmt.Errorf("either expiration_date or duration must be set, or permanent_assignment must be true") - } - - if model.StartDate != "" { - startDate, err := time.Parse(time.RFC3339, model.StartDate) - if err != nil { - return fmt.Errorf("parsing %s: %+v", model.StartDate, err) - } - schedule.StartDateTime = &startDate - } else { - now := time.Now() - schedule.StartDateTime = &now + schedule, err := buildRequestSchedule(&model, &metadata) + if err != nil { + return err } properties := msgraph.PrivilegedAccessGroupAssignmentScheduleRequest{ @@ -93,11 +63,14 @@ func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Create() sdk.Res GroupId: &model.GroupId, Action: msgraph.PrivilegedAccessGroupActionAdminAssign, Justification: &model.Justification, - ScheduleInfo: &schedule, - TicketInfo: &msgraph.TicketInfo{ + ScheduleInfo: schedule, + } + + if model.TicketNumber != "" || model.TicketSystem != "" { + properties.TicketInfo = &msgraph.TicketInfo{ TicketNumber: &model.TicketNumber, TicketSystem: &model.TicketSystem, - }, + } } req, _, err := client.Create(ctx, properties) diff --git a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go index cd9f4627db..ab412f03c3 100644 --- a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go +++ b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go @@ -10,7 +10,6 @@ import ( "slices" "time" - "github.com/hashicorp/go-azure-helpers/lang/pointer" "github.com/hashicorp/terraform-provider-azuread/internal/sdk" "github.com/hashicorp/terraform-provider-azuread/internal/services/identitygovernance/parse" "github.com/hashicorp/terraform-provider-azuread/internal/tf/pluginsdk" @@ -53,38 +52,9 @@ func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Create() sdk.Re return fmt.Errorf("decoding: %+v", err) } - schedule := msgraph.RequestSchedule{} - schedule.Expiration = &msgraph.ExpirationPattern{} - - if model.ExpirationDate != "" || model.Duration != "" { - if model.Duration != "" { - schedule.Expiration.Duration = &model.Duration - schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeAfterDuration) - } - - if model.ExpirationDate != "" { - endDate, err := time.Parse(time.RFC3339, model.ExpirationDate) - if err != nil { - return fmt.Errorf("parsing %s: %+v", model.StartDate, err) - } - schedule.Expiration.EndDateTime = &endDate - schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeAfterDateTime) - } - } else if model.PermanentAssignment { - schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeNoExpiration) - } else { - return fmt.Errorf("either expiration_date or duration must be set, or permanent_assignment must be true") - } - - if model.StartDate != "" { - startDate, err := time.Parse(time.RFC3339, model.StartDate) - if err != nil { - return fmt.Errorf("parsing %s: %+v", model.StartDate, err) - } - schedule.StartDateTime = &startDate - } else { - now := time.Now() - schedule.StartDateTime = &now + schedule, err := buildRequestSchedule(&model, &metadata) + if err != nil { + return err } properties := msgraph.PrivilegedAccessGroupEligibilityScheduleRequest{ @@ -93,11 +63,14 @@ func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Create() sdk.Re GroupId: &model.GroupId, Action: msgraph.PrivilegedAccessGroupActionAdminAssign, Justification: &model.Justification, - ScheduleInfo: &schedule, - TicketInfo: &msgraph.TicketInfo{ + ScheduleInfo: schedule, + } + + if model.TicketNumber != "" || model.TicketSystem != "" { + properties.TicketInfo = &msgraph.TicketInfo{ TicketNumber: &model.TicketNumber, TicketSystem: &model.TicketSystem, - }, + } } req, _, err := client.Create(ctx, properties) diff --git a/internal/services/identitygovernance/privileged_access_group_schedule_request.go b/internal/services/identitygovernance/privileged_access_group_schedule_request.go index 254b95e555..5553fb8818 100644 --- a/internal/services/identitygovernance/privileged_access_group_schedule_request.go +++ b/internal/services/identitygovernance/privileged_access_group_schedule_request.go @@ -4,7 +4,11 @@ package identitygovernance import ( - "github.com/hashicorp/terraform-provider-azuread/internal/services/identitygovernance/validate" + "fmt" + "time" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/terraform-provider-azuread/internal/sdk" "github.com/hashicorp/terraform-provider-azuread/internal/tf/pluginsdk" "github.com/hashicorp/terraform-provider-azuread/internal/tf/validation" "github.com/manicminer/hamilton/msgraph" @@ -61,7 +65,7 @@ func privilegedAccessGroupScheduleRequestArguments() map[string]*pluginsdk.Schem Optional: true, ForceNew: true, Computed: true, - ValidateDiagFunc: validate.ScheduleStartDate, + ValidateDiagFunc: validation.ValidateDiag(validation.IsRFC3339Time), }, "expiration_date": { @@ -70,7 +74,7 @@ func privilegedAccessGroupScheduleRequestArguments() map[string]*pluginsdk.Schem Optional: true, ForceNew: true, ConflictsWith: []string{"duration"}, - ValidateDiagFunc: validate.ScheduleExpiryDate, + ValidateDiagFunc: validation.ValidateDiag(validation.IsRFC3339Time), }, "duration": { @@ -133,3 +137,51 @@ func privilegedAccessGroupScheduleRequestAttributes() map[string]*pluginsdk.Sche }, } } + +func buildRequestSchedule(model *PrivilegedAccessGroupScheduleRequestModel, metadata *sdk.ResourceMetaData) (*msgraph.RequestSchedule, error) { + schedule := msgraph.RequestSchedule{} + schedule.Expiration = &msgraph.ExpirationPattern{} + var startDate, expiryDate time.Time + + if model.StartDate != "" { + startDate, err := time.Parse(time.RFC3339, model.StartDate) + if err != nil { + return nil, fmt.Errorf("parsing %s: %+v", model.StartDate, err) + } + if metadata.ResourceData.HasChange("start_date") { + if startDate.Before(time.Now()) { + return nil, fmt.Errorf("start_date must be in the future") + } + } + schedule.StartDateTime = &startDate + } + + if model.ExpirationDate != "" { + expiryDate, err := time.Parse(time.RFC3339, model.ExpirationDate) + if err != nil { + return nil, fmt.Errorf("parsing %s: %+v", model.ExpirationDate, err) + } + if metadata.ResourceData.HasChange("expiry_date") { + if expiryDate.Before(time.Now().Add(5 * time.Minute)) { + return nil, fmt.Errorf("expiry_date must be at least 5 minutes in the future") + } + } + schedule.Expiration.EndDateTime = &expiryDate + schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeAfterDateTime) + } else if model.Duration != "" { + schedule.Expiration.Duration = &model.Duration + schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeAfterDuration) + } else if model.PermanentAssignment { + schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeNoExpiration) + } else { + return nil, fmt.Errorf("either expiration_date or duration must be set, or permanent_assignment must be true") + } + + if model.StartDate != "" && model.ExpirationDate != "" { + if expiryDate.Before(startDate.Add(5 * time.Minute)) { + return nil, fmt.Errorf("expiration_date must be at least 5 minutes after start_date") + } + } + + return &schedule, nil +} diff --git a/internal/services/identitygovernance/validate/privileged_access_group_schedule_request.go b/internal/services/identitygovernance/validate/privileged_access_group_schedule_request.go deleted file mode 100644 index e318fda570..0000000000 --- a/internal/services/identitygovernance/validate/privileged_access_group_schedule_request.go +++ /dev/null @@ -1,100 +0,0 @@ -package validate - -import ( - "fmt" - "time" - - "github.com/hashicorp/go-cty/cty" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-provider-azuread/internal/tf/validation" -) - -func ScheduleStartDate(i interface{}, path cty.Path) (ret diag.Diagnostics) { - v, ok := i.(string) - if !ok { - ret = append(ret, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Expected a string value", - }) - return - } - - warnings, errs := validation.IsRFC3339Time(v, "start_date") - if len(warnings) > 0 || len(errs) > 0 { - for _, warning := range warnings { - ret = append(ret, diag.Diagnostic{ - Severity: diag.Warning, - Summary: warning, - }) - } - for _, err := range errs { - ret = append(ret, diag.Diagnostic{ - Severity: diag.Error, - Summary: err.Error(), - }) - } - return - } - - t, err := time.Parse(time.RFC3339, v) - if err != nil { - ret = append(ret, diag.Diagnostic{ - Severity: diag.Error, - Summary: fmt.Sprintf("Failed to parse start date: %v+", err), - }) - } - - if t.Before(time.Now()) { - ret = append(ret, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Start date must be in the future", - }) - } - - return -} - -func ScheduleExpiryDate(i interface{}, path cty.Path) (ret diag.Diagnostics) { - v, ok := i.(string) - if !ok { - ret = append(ret, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Expected a string value", - }) - return - } - - warnings, errs := validation.IsRFC3339Time(v, "expiration_date") - if len(warnings) > 0 || len(errs) > 0 { - for _, warning := range warnings { - ret = append(ret, diag.Diagnostic{ - Severity: diag.Warning, - Summary: warning, - }) - } - for _, err := range errs { - ret = append(ret, diag.Diagnostic{ - Severity: diag.Error, - Summary: err.Error(), - }) - } - return - } - - t, err := time.Parse(time.RFC3339, v) - if err != nil { - ret = append(ret, diag.Diagnostic{ - Severity: diag.Error, - Summary: fmt.Sprintf("Failed to parse start date: %v+", err), - }) - } - - if t.Before(time.Now().Add(time.Minute * 5)) { - ret = append(ret, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Expiry date must be at least 5 minutes in the future", - }) - } - - return -} From bb2237b701593482a3062fab4f21b6843500a99e Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Fri, 8 Mar 2024 11:49:24 +1300 Subject: [PATCH 40/55] Suppress diff on the start date --- ...rivileged_access_group_schedule_request.go | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/internal/services/identitygovernance/privileged_access_group_schedule_request.go b/internal/services/identitygovernance/privileged_access_group_schedule_request.go index 5553fb8818..ba74b896dd 100644 --- a/internal/services/identitygovernance/privileged_access_group_schedule_request.go +++ b/internal/services/identitygovernance/privileged_access_group_schedule_request.go @@ -60,12 +60,27 @@ func privilegedAccessGroupScheduleRequestArguments() map[string]*pluginsdk.Schem }, "start_date": { - Description: "The date that this assignment starts, formatted as an RFC3339 date string in UTC (e.g. 2018-01-01T01:02:03Z)", - Type: pluginsdk.TypeString, - Optional: true, - ForceNew: true, - Computed: true, - ValidateDiagFunc: validation.ValidateDiag(validation.IsRFC3339Time), + Description: "The date that this assignment starts, formatted as an RFC3339 date string in UTC (e.g. 2018-01-01T01:02:03Z)", + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + ValidateDiagFunc: validation.ValidateDiag(validation.IsRFC3339Time), + DiffSuppressOnRefresh: true, + DiffSuppressFunc: func(k, old, new string, d *pluginsdk.ResourceData) bool { + // Suppress diffs if the start date is in the past + oldTime, err := time.Parse(time.RFC3339, old) + if err == nil { + return oldTime.Before(time.Now()) + } + // Suppress diffs if the new date is within 5 minutes of the old date + // Activation of a future start time is never exactly at the requested time + newTime, err := time.Parse(time.RFC3339, new) + if err == nil { + return newTime.Before(oldTime.Add(5 * time.Minute)) + } + return false + }, }, "expiration_date": { From 1dd653f6bc4e8cff45fe9a7b67989446248f3495 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Sat, 16 Mar 2024 15:29:46 +1300 Subject: [PATCH 41/55] Update read functions to find the lastest request version --- ...ccess_group_assignment_schedule_request.go | 72 +++++++++++------- ...ccess_group_eligiblity_schedule_request.go | 76 +++++++++++-------- 2 files changed, 88 insertions(+), 60 deletions(-) diff --git a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go index 31dcc87e8a..28570fa42e 100644 --- a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go +++ b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go @@ -10,6 +10,7 @@ import ( "slices" "time" + "github.com/hashicorp/go-azure-sdk/sdk/odata" "github.com/hashicorp/terraform-provider-azuread/internal/sdk" "github.com/hashicorp/terraform-provider-azuread/internal/services/identitygovernance/parse" "github.com/hashicorp/terraform-provider-azuread/internal/tf/pluginsdk" @@ -110,45 +111,58 @@ func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Read() sdk.Resou return fmt.Errorf("decoding: %+v", err) } - result, status, err := client.Get(ctx, id.ID()) + // Schedule requests are never deleted. New ones are created when changes are made. + // Therefore on a read, we need to find the latest version of the request. + // This is to cater for changes being made outside of Terraform. + requests, _, err := client.List(ctx, odata.Query{ + Filter: fmt.Sprintf("groupId eq '%s' and principalId eq '%s'", model.GroupId, model.PrincipalId), + OrderBy: odata.OrderBy{ + Field: "createdDateTime", + Direction: odata.Descending, + }, + }) if err != nil { - if status == http.StatusNotFound { - return metadata.MarkAsGone(id) - } - return fmt.Errorf("retrieving %s: %+v", id, err) + return fmt.Errorf("listing requests: %+v", err) } - if result == nil { - return fmt.Errorf("retrieving %s: API error, result was nil", id) + if len(*requests) == 0 { + return metadata.MarkAsGone(id) } + request := (*requests)[0] if slices.Contains([]string{ msgraph.PrivilegedAccessGroupAssignmentStatusCanceled, msgraph.PrivilegedAccessGroupAssignmentStatusRevoked, - }, result.Status) { + }, request.Status) { metadata.MarkAsGone(id) - } + } else { + model.AssignmentType = request.AccessId + model.GroupId = *request.GroupId + model.Justification = *request.Justification + model.PermanentAssignment = *request.ScheduleInfo.Expiration.Type == msgraph.ExpirationPatternTypeNoExpiration + model.PrincipalId = *request.PrincipalId + model.StartDate = request.ScheduleInfo.StartDateTime.Format(time.RFC3339) + model.Status = request.Status + model.TargetScheduleId = *request.TargetScheduleId + + if request.ScheduleInfo.Expiration.EndDateTime != nil { + model.ExpirationDate = request.ScheduleInfo.Expiration.EndDateTime.Format(time.RFC3339) + } + if request.ScheduleInfo.Expiration.Duration != nil { + model.Duration = *request.ScheduleInfo.Expiration.Duration + } - model.AssignmentType = result.AccessId - model.GroupId = *result.GroupId - model.Justification = *result.Justification - model.PermanentAssignment = *result.ScheduleInfo.Expiration.Type == msgraph.ExpirationPatternTypeNoExpiration - model.PrincipalId = *result.PrincipalId - model.StartDate = result.ScheduleInfo.StartDateTime.Format(time.RFC3339) - model.Status = result.Status - model.TargetScheduleId = *result.TargetScheduleId - - if result.ScheduleInfo.Expiration.EndDateTime != nil { - model.ExpirationDate = result.ScheduleInfo.Expiration.EndDateTime.Format(time.RFC3339) - } - if result.ScheduleInfo.Expiration.Duration != nil { - model.Duration = *result.ScheduleInfo.Expiration.Duration - } + if request.TicketInfo.TicketNumber != nil { + model.TicketNumber = *request.TicketInfo.TicketNumber + } + if request.TicketInfo.TicketSystem != nil { + model.TicketSystem = *request.TicketInfo.TicketSystem + } - if result.TicketInfo.TicketNumber != nil { - model.TicketNumber = *result.TicketInfo.TicketNumber - } - if result.TicketInfo.TicketSystem != nil { - model.TicketSystem = *result.TicketInfo.TicketSystem + // Update the ID if it has changed + if *request.ID != id.ID() { + id = parse.NewPrivilegedAccessGroupAssignmentScheduleRequestID(*request.ID) + metadata.SetID(id) + } } return metadata.Encode(&model) diff --git a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go index ab412f03c3..b794c591dc 100644 --- a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go +++ b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go @@ -10,6 +10,7 @@ import ( "slices" "time" + "github.com/hashicorp/go-azure-sdk/sdk/odata" "github.com/hashicorp/terraform-provider-azuread/internal/sdk" "github.com/hashicorp/terraform-provider-azuread/internal/services/identitygovernance/parse" "github.com/hashicorp/terraform-provider-azuread/internal/tf/pluginsdk" @@ -110,45 +111,58 @@ func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Read() sdk.Reso return fmt.Errorf("decoding: %+v", err) } - result, status, err := client.Get(ctx, id.ID()) + // Schedule requests are never deleted. New ones are created when changes are made. + // Therefore on a read, we need to find the latest version of the request. + // This is to cater for changes being made outside of Terraform. + requests, _, err := client.List(ctx, odata.Query{ + Filter: fmt.Sprintf("groupId eq '%s' and principalId eq '%s'", model.GroupId, model.PrincipalId), + OrderBy: odata.OrderBy{ + Field: "createdDateTime", + Direction: odata.Descending, + }, + }) if err != nil { - if status == http.StatusNotFound { - return metadata.MarkAsGone(id) - } - return fmt.Errorf("retrieving %s: %+v", id, err) + return fmt.Errorf("listing requests: %+v", err) } - if result == nil { - return fmt.Errorf("retrieving %s: API error, result was nil", id) + if len(*requests) == 0 { + return metadata.MarkAsGone(id) } + request := (*requests)[0] if slices.Contains([]string{ - msgraph.PrivilegedAccessGroupEligibilityStatusCanceled, - msgraph.PrivilegedAccessGroupEligibilityStatusRevoked, - }, result.Status) { + msgraph.PrivilegedAccessGroupAssignmentStatusCanceled, + msgraph.PrivilegedAccessGroupAssignmentStatusRevoked, + }, request.Status) { metadata.MarkAsGone(id) - } + } else { + model.AssignmentType = request.AccessId + model.GroupId = *request.GroupId + model.Justification = *request.Justification + model.PermanentAssignment = *request.ScheduleInfo.Expiration.Type == msgraph.ExpirationPatternTypeNoExpiration + model.PrincipalId = *request.PrincipalId + model.StartDate = request.ScheduleInfo.StartDateTime.Format(time.RFC3339) + model.Status = request.Status + model.TargetScheduleId = *request.TargetScheduleId + + if request.ScheduleInfo.Expiration.EndDateTime != nil { + model.ExpirationDate = request.ScheduleInfo.Expiration.EndDateTime.Format(time.RFC3339) + } + if request.ScheduleInfo.Expiration.Duration != nil { + model.Duration = *request.ScheduleInfo.Expiration.Duration + } - model.AssignmentType = result.AccessId - model.GroupId = *result.GroupId - model.Justification = *result.Justification - model.PermanentAssignment = *result.ScheduleInfo.Expiration.Type == msgraph.ExpirationPatternTypeNoExpiration - model.PrincipalId = *result.PrincipalId - model.StartDate = result.ScheduleInfo.StartDateTime.Format(time.RFC3339) - model.Status = result.Status - model.TargetScheduleId = *result.TargetScheduleId - - if result.ScheduleInfo.Expiration.EndDateTime != nil { - model.ExpirationDate = result.ScheduleInfo.Expiration.EndDateTime.Format(time.RFC3339) - } - if result.ScheduleInfo.Expiration.Duration != nil { - model.Duration = *result.ScheduleInfo.Expiration.Duration - } + if request.TicketInfo.TicketNumber != nil { + model.TicketNumber = *request.TicketInfo.TicketNumber + } + if request.TicketInfo.TicketSystem != nil { + model.TicketSystem = *request.TicketInfo.TicketSystem + } - if result.TicketInfo.TicketNumber != nil { - model.TicketNumber = *result.TicketInfo.TicketNumber - } - if result.TicketInfo.TicketSystem != nil { - model.TicketSystem = *result.TicketInfo.TicketSystem + // Update the ID if it has changed + if *request.ID != id.ID() { + id = parse.NewPrivilegedAccessGroupEligibilityScheduleRequestID(*request.ID) + metadata.SetID(id) + } } return metadata.Encode(&model) From 832971cdf20faecb01fa7778da3d0fa896e9d097 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Thu, 21 Mar 2024 19:25:18 +1300 Subject: [PATCH 42/55] Correct ordering of arguments in documentation --- .../resources/group_role_management_policy.md | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/resources/group_role_management_policy.md b/docs/resources/group_role_management_policy.md index e96e849fe3..425ae701ad 100644 --- a/docs/resources/group_role_management_policy.md +++ b/docs/resources/group_role_management_policy.md @@ -58,27 +58,15 @@ resource "azuread_group_role_management_policy" "example" { ## Argument Reference -* `group_id` - (Required) The ID of the Azure AD group for which the policy applies. -* `assignment_type` - (Required) The type of assignment this policy coveres. Can be either `member` or `owner`. -* `active_assignment_rules` - (Optional) An `active_assignment_rules` block as defined below. * `activation_rules` - (Optional) An `activation_rules` block as defined below. +* `active_assignment_rules` - (Optional) An `active_assignment_rules` block as defined below. +* `assignment_type` - (Required) The type of assignment this policy coveres. Can be either `member` or `owner`. * `eligible_assignment_rules` - (Optional) An `eligible_assignment_rules` block as defined below. +* `group_id` - (Required) The ID of the Azure AD group for which the policy applies. * `notification_rules` - (Optional) An `notification_rules` block as defined below. --- -An `active_assignment_rules` block supports the following: - -* `expiration_required` - (Optional) Must an assignment have an expiry date. `false` allows permanent assignment. -* `expire_after` - (Optional) The maximum length of time an assignment can be valid, as an ISO8601 duration. Permitted values: `P15D`, `P30D`, `P90D`, `P180D`, or `P365D`. -* `require_multifactor_authentication` - (Optional) Is multi-factor authentication required to create new assignments. -* `require_justification` - (Optional) Is a justification required to create new assignments. -* `require_ticket_info` - (Optional) Is ticket information required to create new assignments. - -One of `expiration_required` or `expire_after` must be provided. - ---- - An `activation_rules` block supports the following: * `maximum_duration` - (Optional) The maximum length of time an activated role can be valid, in an IS)8601 Duration format (e.g. `PT8H`). Valid range is `PT30M` to `PT23H30M`, in 30 minute increments, or `PT1D`. @@ -91,6 +79,18 @@ An `activation_rules` block supports the following: --- +An `active_assignment_rules` block supports the following: + +* `expiration_required` - (Optional) Must an assignment have an expiry date. `false` allows permanent assignment. +* `expire_after` - (Optional) The maximum length of time an assignment can be valid, as an ISO8601 duration. Permitted values: `P15D`, `P30D`, `P90D`, `P180D`, or `P365D`. +* `require_multifactor_authentication` - (Optional) Is multi-factor authentication required to create new assignments. +* `require_justification` - (Optional) Is a justification required to create new assignments. +* `require_ticket_info` - (Optional) Is ticket information required to create new assignments. + +One of `expiration_required` or `expire_after` must be provided. + +--- + An `approval_stage` block supports the following: * One or more `primary_approver` blocks as defined below. From a0f4dffdf2580a40cafaf4996118c1519942ae0f Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Tue, 2 Apr 2024 09:09:25 +1300 Subject: [PATCH 43/55] Bump `hamilton` to v0.67.0 --- go.mod | 2 +- go.sum | 4 +- .../msgraph/accesspackageresourcerole.go | 63 +++++ .../manicminer/hamilton/msgraph/client.go | 4 + .../manicminer/hamilton/msgraph/models.go | 172 ++++++++++++- ...vilegedaccess_groups_assignmentschedule.go | 240 ++++++++++++++++++ ...cess_groups_assignmentschedule_instance.go | 77 ++++++ ...cess_groups_assignmentschedule_requests.go | 128 ++++++++++ ...ilegedaccess_groups_eligibilityschedule.go | 77 ++++++ ...ess_groups_eligibilityschedule_instance.go | 77 ++++++ ...cess_groups_eligibilityschedule_request.go | 127 +++++++++ .../msgraph/role_management_policies.go | 104 ++++++++ .../role_management_policies_assignment.go | 80 ++++++ .../msgraph/role_management_policies_rules.go | 100 ++++++++ .../manicminer/hamilton/msgraph/valuetypes.go | 136 ++++++++++ vendor/modules.txt | 2 +- 16 files changed, 1384 insertions(+), 9 deletions(-) create mode 100644 vendor/github.com/manicminer/hamilton/msgraph/accesspackageresourcerole.go create mode 100644 vendor/github.com/manicminer/hamilton/msgraph/privilegedaccess_groups_assignmentschedule.go create mode 100644 vendor/github.com/manicminer/hamilton/msgraph/privilegedaccess_groups_assignmentschedule_instance.go create mode 100644 vendor/github.com/manicminer/hamilton/msgraph/privilegedaccess_groups_assignmentschedule_requests.go create mode 100644 vendor/github.com/manicminer/hamilton/msgraph/privilegedaccess_groups_eligibilityschedule.go create mode 100644 vendor/github.com/manicminer/hamilton/msgraph/privilegedaccess_groups_eligibilityschedule_instance.go create mode 100644 vendor/github.com/manicminer/hamilton/msgraph/privilegedaccess_groups_eligibilityschedule_request.go create mode 100644 vendor/github.com/manicminer/hamilton/msgraph/role_management_policies.go create mode 100644 vendor/github.com/manicminer/hamilton/msgraph/role_management_policies_assignment.go create mode 100644 vendor/github.com/manicminer/hamilton/msgraph/role_management_policies_rules.go diff --git a/go.mod b/go.mod index 82ca95b464..3fdb270255 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/terraform-plugin-sdk/v2 v2.29.0 github.com/hashicorp/terraform-plugin-testing v1.5.1 - github.com/manicminer/hamilton v0.66.0 + github.com/manicminer/hamilton v0.67.0 golang.org/x/text v0.14.0 ) diff --git a/go.sum b/go.sum index 6633f8cb7c..3247d07454 100644 --- a/go.sum +++ b/go.sum @@ -111,8 +111,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/manicminer/hamilton v0.66.0 h1:pJPlaf32wMZBCArX1U5QC0YqR3vnJoc4crTuigLy0og= -github.com/manicminer/hamilton v0.66.0/go.mod h1:u80g9rPtJpCG7EC0iayttt8UfeAp6jknClixgZGE950= +github.com/manicminer/hamilton v0.67.0 h1:hG3tPunQCGcgP2Nx0+lwW+Swu9MXOs4JGospakK79pY= +github.com/manicminer/hamilton v0.67.0/go.mod h1:u80g9rPtJpCG7EC0iayttt8UfeAp6jknClixgZGE950= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= diff --git a/vendor/github.com/manicminer/hamilton/msgraph/accesspackageresourcerole.go b/vendor/github.com/manicminer/hamilton/msgraph/accesspackageresourcerole.go new file mode 100644 index 0000000000..e6701754c2 --- /dev/null +++ b/vendor/github.com/manicminer/hamilton/msgraph/accesspackageresourcerole.go @@ -0,0 +1,63 @@ +package msgraph + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/hashicorp/go-azure-sdk/sdk/odata" +) + +type AccessPackageResourceRoleClient struct { + BaseClient Client +} + +func NewAccessPackageResourceRoleClient() *AccessPackageResourceRoleClient { + return &AccessPackageResourceRoleClient{ + BaseClient: NewClient(VersionBeta), + } +} + +// List retrieves a list of AccessPackageResourceRoles for a specific accessPackageResource for a particular catalog / originSystem +// This method requires us to use an Odata Filter / Expand to function correctly +func (c *AccessPackageResourceRoleClient) List(ctx context.Context, catalogId string, originSystem AccessPackageResourceOriginSystem, accessPackageResourceId string) (*[]AccessPackageResourceRole, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc, + OData: odata.Query{ + Filter: fmt.Sprintf("originSystem eq '%s' and accessPackageResource/id eq '%s'", originSystem, accessPackageResourceId), + Expand: odata.Expand{ + Relationship: "accessPackageResource", + }, + }, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("/identityGovernance/entitlementManagement/accessPackageCatalogs/%s/accessPackageResourceRoles", catalogId), + }, + }) + if err != nil { + return nil, status, fmt.Errorf("AccessPackageResourceRoleClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var data struct { + AccessPackageResourceRoles []AccessPackageResourceRole `json:"value"` + } + if err := json.Unmarshal(respBody, &data); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + AccessPackageResourceRoles := data.AccessPackageResourceRoles + + if len(AccessPackageResourceRoles) == 0 { + return nil, http.StatusNotFound, fmt.Errorf("no AccessPackageResourceRoles found with catalogId %v, originSystem %v and accessPackageResourceId %v", catalogId, originSystem, accessPackageResourceId) + } + + return &AccessPackageResourceRoles, status, nil +} diff --git a/vendor/github.com/manicminer/hamilton/msgraph/client.go b/vendor/github.com/manicminer/hamilton/msgraph/client.go index 984ab655d5..b9119c21fa 100644 --- a/vendor/github.com/manicminer/hamilton/msgraph/client.go +++ b/vendor/github.com/manicminer/hamilton/msgraph/client.go @@ -58,6 +58,10 @@ type Uri struct { // RetryableErrorHandler ensures that the response is returned after exhausting retries for a request // We can't return an error here, or net/http will not return the response func RetryableErrorHandler(resp *http.Response, err error, numTries int) (*http.Response, error) { + if resp == nil { + return nil, err + } + return resp, nil } diff --git a/vendor/github.com/manicminer/hamilton/msgraph/models.go b/vendor/github.com/manicminer/hamilton/msgraph/models.go index 1cef63f644..630f4b1e49 100644 --- a/vendor/github.com/manicminer/hamilton/msgraph/models.go +++ b/vendor/github.com/manicminer/hamilton/msgraph/models.go @@ -545,6 +545,11 @@ type AppRoleAssignment struct { ResourceId *string `json:"resourceId,omitempty"` } +type Approval struct { + ID *string `json:"id,omitempty"` + Steps *[]ApprovalStep `json:"steps,omitempty"` +} + type ApprovalSettings struct { IsApprovalRequiredForAdd *bool `json:"isApprovalRequiredForAdd,omitempty"` IsApprovalRequiredForUpdate *bool `json:"isApprovalRequiredForUpdate,omitempty"` @@ -564,6 +569,17 @@ type ApprovalStage struct { EscalationApprovers *[]UserSet `json:"escalationApprovers,omitempty"` } +type ApprovalStep struct { + ID *string `json:"id,omitempty"` + AssignedToMe *bool `json:"assignedToMe,omitempty"` + DisplayName *string `json:"displayName,omitempty"` + Justification *string `json:"justification,omitempty"` + ReviewResult *string `json:"reviewResult,omitempty"` + ReviewedBy *[]UserIdentity `json:"reviewedBy,omitempty"` + ReviewedDateTime *time.Time `json:"reviewedDateTime,omitempty"` + Status ApprovalStepStatus `json:"status,omitempty"` +} + type AssignmentReviewSettings struct { IsEnabled *bool `json:"isEnabled,omitempty"` RecurrenceType AccessReviewRecurrenceType `json:"recurrenceType,omitempty"` @@ -615,9 +631,10 @@ type CloudAppSecurityControl struct { } type ConditionalAccessApplications struct { - IncludeApplications *[]string `json:"includeApplications,omitempty"` - ExcludeApplications *[]string `json:"excludeApplications,omitempty"` - IncludeUserActions *[]string `json:"includeUserActions,omitempty"` + ApplicationFilter *ConditionalAccessFilter `json:"applicationFilter,omitempty"` + IncludeApplications *[]string `json:"includeApplications,omitempty"` + ExcludeApplications *[]string `json:"excludeApplications,omitempty"` + IncludeUserActions *[]string `json:"includeUserActions,omitempty"` } type ConditionalAccessClientApplications struct { @@ -1013,7 +1030,7 @@ type ExtensionSchemaProperty struct { } type ExpirationPattern struct { - Duration *time.Duration `json:"duration,omitempty"` + Duration *string `json:"duration,omitempty"` EndDateTime *time.Time `json:"endDateTime,omitempty"` Type *ExpirationPatternType `json:"type,omitempty"` } @@ -1419,6 +1436,93 @@ type PhoneAuthenticationMethod struct { PhoneNumber *string `json:"phoneNumber,omitempty"` PhoneType *AuthenticationPhoneType `json:"phoneType,omitempty"` } + +type PrivilegedAccessGroupAssignmentSchedule struct { + ID *string `json:"id,omitempty"` + AccessId PrivilegedAccessGroupRelationship `json:"accessId,omitempty"` + AssignmentType PrivilegedAccessGroupAssignmentType `json:"assignmentType,omitempty"` + CreatedDateTime *time.Time `json:"createdDateTime,omitempty"` + CreatedUsing *string `json:"createdUsing,omitempty"` + GroupId *string `json:"groupId,omitempty"` + MemberType PrivilegedAccessGroupMemberType `json:"memberType,omitempty"` + ModifiedDateTime *time.Time `json:"modifiedDateTime,omitempty"` + PrincipalId *string `json:"principalId,omitempty"` + ScheduleInfo *RequestSchedule `json:"scheduleInfo,omitempty"` + Status PrivilegedAccessGroupAssignmentStatus `json:"status,omitempty"` +} + +type PrivilegedAccessGroupAssignmentScheduleInstance struct { + ID *string `json:"id,omitempty"` + AccessId PrivilegedAccessGroupRelationship `json:"accessId,omitempty"` + AssignmentScheduleId *string `json:"assignmentScheduleId,omitempty"` + AssignmentType PrivilegedAccessGroupAssignmentType `json:"assignmentType,omitempty"` + EndDateTime *time.Time `json:"createdDateTime,omitempty"` + GroupId *string `json:"groupId,omitempty"` + MemberType PrivilegedAccessGroupMemberType `json:"memberType,omitempty"` + PrincipalId *string `json:"principalId,omitempty"` + StartDateTime *time.Time `json:"startDateTime,omitempty"` +} + +type PrivilegedAccessGroupAssignmentScheduleRequest struct { + ID *string `json:"id,omitempty"` + AccessId PrivilegedAccessGroupRelationship `json:"accessId,omitempty"` + Action PrivilegedAccessGroupAction `json:"action,omitempty"` + ApprovalId *string `json:"approvalId,omitempty"` + CompletedDateTime *time.Time `json:"completedDateTime,omitempty"` + CreatedDateTime *time.Time `json:"createdDateTime,omitempty"` + CustomData *string `json:"customData,omitempty"` + GroupId *string `json:"groupId,omitempty"` + IsValidationOnly *bool `json:"isValidationOnly,omitempty"` + Justification *string `json:"justification,omitempty"` + PrincipalId *string `json:"principalId,omitempty"` + ScheduleInfo *RequestSchedule `json:"scheduleInfo,omitempty"` + Status PrivilegedAccessGroupAssignmentStatus `json:"status,omitempty"` + TargetScheduleId *string `json:"targetScheduleId,omitempty"` + TicketInfo *TicketInfo `json:"ticketInfo,omitempty"` +} + +type PrivilegedAccessGroupEligibilitySchedule struct { + ID *string `json:"id,omitempty"` + AccessId PrivilegedAccessGroupRelationship `json:"accessId,omitempty"` + CreatedDateTime *time.Time `json:"createdDateTime,omitempty"` + CreatedUsing *string `json:"createdUsing,omitempty"` + GroupId *string `json:"groupId,omitempty"` + MemberType PrivilegedAccessGroupMemberType `json:"memberType,omitempty"` + ModifiedDateTime *time.Time `json:"modifiedDateTime,omitempty"` + PrincipalId *string `json:"principalId,omitempty"` + ScheduleInfo *RequestSchedule `json:"scheduleInfo,omitempty"` + Status PrivilegedAccessGroupEligibilityStatus `json:"status,omitempty"` +} + +type PrivilegedAccessGroupEligibilityScheduleInstance struct { + ID *string `json:"id,omitempty"` + AccessId PrivilegedAccessGroupRelationship `json:"accessId,omitempty"` + EligibilityScheduleId *string `json:"eligibilityScheduleId,omitempty"` + EndDateTime *time.Time `json:"createdDateTime,omitempty"` + GroupId *string `json:"groupId,omitempty"` + MemberType PrivilegedAccessGroupMemberType `json:"memberType,omitempty"` + PrincipalId *string `json:"principalId,omitempty"` + StartDateTime *time.Time `json:"startDateTime,omitempty"` +} + +type PrivilegedAccessGroupEligibilityScheduleRequest struct { + ID *string `json:"id,omitempty"` + AccessId PrivilegedAccessGroupRelationship `json:"accessId,omitempty"` + Action PrivilegedAccessGroupAction `json:"action,omitempty"` + ApprovalId *string `json:"approvalId,omitempty"` + CompletedDateTime *time.Time `json:"completedDateTime,omitempty"` + CreatedDateTime *time.Time `json:"createdDateTime,omitempty"` + CustomData *string `json:"customData,omitempty"` + GroupId *string `json:"groupId,omitempty"` + IsValidationOnly *bool `json:"isValidationOnly,omitempty"` + Justification *string `json:"justification,omitempty"` + PrincipalId *string `json:"principalId,omitempty"` + ScheduleInfo *RequestSchedule `json:"scheduleInfo,omitempty"` + Status PrivilegedAccessGroupEligibilityStatus `json:"status,omitempty"` + TargetScheduleId *string `json:"targetScheduleId,omitempty"` + TicketInfo *TicketInfo `json:"ticketInfo,omitempty"` +} + type PublicClient struct { RedirectUris *[]string `json:"redirectUris,omitempty"` } @@ -1802,6 +1906,62 @@ type UnifiedRoleEligibilityScheduleRequest struct { TicketInfo *TicketInfo `json:"ticketInfo,omitempty"` } +type UnifiedRoleManagementPolicy struct { + ID *string `json:"id,omitempty"` + Description *string `json:"description,omitempty"` + DisplayName *string `json:"displayName,omitempty"` + EffectiveRules *[]UnifiedRoleManagementPolicyRule `json:"effectiveRules,omitempty"` + IsOrganizationDefault *bool `json:"isOrganizationDefault,omitempty"` + LastModifiedBy *Identity `json:"lastModifiedBy,omitempty"` + LastModifiedDateTime *time.Time `json:"lastModifiedDateTime,omitempty"` + Rules *[]UnifiedRoleManagementPolicyRule `json:"rules,omitempty"` + ScopeId *string `json:"scopeId,omitempty"` + ScopeType UnifiedRoleManagementPolicyScope `json:"scopeType,omitempty"` +} + +type UnifiedRoleManagementPolicyAssignment struct { + ID *string `json:"id,omitempty"` + PolicyId *string `json:"policyId,omitempty"` + RoleDefinitionId *string `json:"roleDefinitionId,omitempty"` + ScopeId *string `json:"scopeId,omitempty"` + ScopeType UnifiedRoleManagementPolicyScope `json:"scopeType,omitempty"` +} + +type UnifiedRoleManagementPolicyRule struct { + ID *string `json:"id,omitempty"` + ODataType *odata.Type `json:"@odata.type,omitempty"` + Target *UnifiedRoleManagementPolicyRuleTarget `json:"target,omitempty"` + + // unifiedRoleManagementPolicyApprovalRule + Setting *ApprovalSettings `json:"setting,omitempty"` + + // unifiedRoleManagementPolicyAuthenticationContextRule + ClaimValue *string `json:"claimValue,omitempty"` + IsEnabled *bool `json:"isEnabled,omitempty"` + + // unifiedRoleManagementPolicyEnablementRule + EnabledRules *[]string `json:"enabledRules,omitempty"` + + // unifiedRoleManagementPolicyExpirationRule + IsExpirationRequired *bool `json:"isExpirationRequired,omitempty"` + MaximumDuration *string `json:"maximumDuration,omitempty"` + + // + IsDefaultRecipientsEnabled *bool `json:"isDefaultRecipientsEnabled,omitempty"` + NotificationLevel UnifiedRoleManagementPolicyRuleNotificationLevel `json:"notificationLevel,omitempty"` + NotificationRecipients *[]string `json:"notificationRecipients,omitempty"` + NotificationType UnifiedRoleManagementPolicyRuleNotificationType `json:"notificationType,omitempty"` + RecipientType UnifiedRoleManagementPolicyRuleNotificationRecipientType `json:"recipientType,omitempty"` +} + +type UnifiedRoleManagementPolicyRuleTarget struct { + Caller UnifiedRoleManagementPolicyRuleTargetCallerType `json:"caller,omitempty"` + EnforcedSettings *[]string `json:"enforcedSettings,omitempty"` + InheritableSettings *[]string `json:"inheritableSettings,omitempty"` + Level UnifiedRoleManagementPolicyRuleLevel `json:"level,omitempty"` + Operations *[]UnifiedRoleManagementPolicyRuleOperation `json:"operations,omitempty"` +} + type UnifiedRolePermission struct { AllowedResourceActions *[]string `json:"allowedResourceActions,omitempty"` Condition *StringNullWhenEmpty `json:"condition,omitempty"` @@ -1965,7 +2125,9 @@ type UserRegistrationMethodSummary struct { type UserSet struct { ODataType *odata.Type `json:"@odata.type,omitempty"` IsBackup *bool `json:"isBackup,omitempty"` - ID *string `json:"id,omitempty"` // Either user or group ID + ID *string `json:"id,omitempty"` // Either user or group ID + GroupID *string `json:"groupId,omitempty"` // oData groupMembers + UserID *string `json:"userId,omitempty"` // oData singleUser Description *string `json:"description,omitempty"` ManagerLevel *int32 `json:"managerLevel,omitempty"` } diff --git a/vendor/github.com/manicminer/hamilton/msgraph/privilegedaccess_groups_assignmentschedule.go b/vendor/github.com/manicminer/hamilton/msgraph/privilegedaccess_groups_assignmentschedule.go new file mode 100644 index 0000000000..ffc8a3d787 --- /dev/null +++ b/vendor/github.com/manicminer/hamilton/msgraph/privilegedaccess_groups_assignmentschedule.go @@ -0,0 +1,240 @@ +package msgraph + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/hashicorp/go-azure-sdk/sdk/odata" +) + +type PrivilegedAccessGroupAssignmentScheduleClient struct { + BaseClient Client +} + +func NewPrivilegedAccessGroupAssignmentScheduleClient() *PrivilegedAccessGroupAssignmentScheduleClient { + return &PrivilegedAccessGroupAssignmentScheduleClient{ + BaseClient: NewClient(VersionBeta), + } +} + +// List retrieves a list of PrivilegedAccessGroupAssignments +func (c *PrivilegedAccessGroupAssignmentScheduleClient) List(ctx context.Context, query odata.Query) (*[]PrivilegedAccessGroupAssignmentSchedule, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + DisablePaging: query.Top > 0, + OData: query, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: "/identityGovernance/privilegedAccess/group/assignmentSchedules", + }, + }) + if err != nil { + return nil, status, fmt.Errorf("PrivilegedAccessGroupAssignmentScheduleClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var data struct { + Schedules []PrivilegedAccessGroupAssignmentSchedule `json:"value"` + } + if err := json.Unmarshal(respBody, &data); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &data.Schedules, status, nil +} + +// Get retrieves a PrivilegedAccessGroupAssignment +func (c *PrivilegedAccessGroupAssignmentScheduleClient) Get(ctx context.Context, scheduleId string) (*PrivilegedAccessGroupAssignmentSchedule, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("/identityGovernance/privilegedAccess/group/assignmentSchedules/%s", scheduleId), + }, + }) + if err != nil { + return nil, status, fmt.Errorf("PrivilegedAccessGroupAssignmentScheduleClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var schedule PrivilegedAccessGroupAssignmentSchedule + if err := json.Unmarshal(respBody, &schedule); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &schedule, status, nil +} + +// List retrieves a list of PrivilegedAccessGroupAssignmentScheduleInstances +func (c *PrivilegedAccessGroupAssignmentScheduleClient) InstancesList(ctx context.Context, query odata.Query) (*[]PrivilegedAccessGroupAssignmentScheduleInstance, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + DisablePaging: query.Top > 0, + OData: query, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: "/identityGovernance/privilegedAccess/group/assignmentScheduleInstances", + }, + }) + if err != nil { + return nil, status, fmt.Errorf("PrivilegedAccessGroupClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var data struct { + Instances []PrivilegedAccessGroupAssignmentScheduleInstance `json:"value"` + } + if err := json.Unmarshal(respBody, &data); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &data.Instances, status, nil +} + +// Get retrieves a PrivilegedAccessGroupAssignmentScheduleInstance +func (c *PrivilegedAccessGroupAssignmentScheduleClient) InstancesGet(ctx context.Context, instanceId string) (*PrivilegedAccessGroupAssignmentScheduleInstance, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("/identityGovernance/privilegedAccess/group/assignmentScheduleInstances/%s", instanceId), + }, + }) + if err != nil { + return nil, status, fmt.Errorf("PrivilegedAccessGroupClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var instance PrivilegedAccessGroupAssignmentScheduleInstance + if err := json.Unmarshal(respBody, &instance); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &instance, status, nil +} + +// List retrieves a list of PrivilegedAccessGroupAssignmentScheduleRequests +func (c *PrivilegedAccessGroupAssignmentScheduleClient) RequestsList(ctx context.Context, query odata.Query) (*[]PrivilegedAccessGroupAssignmentScheduleRequest, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc, + DisablePaging: query.Top > 0, + OData: query, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: "/identityGovernance/privilegedAccess/group/assignmentScheduleRequests", + }, + }) + if err != nil { + return nil, status, fmt.Errorf("PrivilegedAccessGroupClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var data struct { + Requests []PrivilegedAccessGroupAssignmentScheduleRequest `json:"value"` + } + if err := json.Unmarshal(respBody, &data); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &data.Requests, status, nil +} + +// Create creates a new PrivilegedAccessGroupAssignmentScheduleRequest. +func (c *PrivilegedAccessGroupAssignmentScheduleClient) RequestsCreate(ctx context.Context, request PrivilegedAccessGroupAssignmentScheduleRequest) (*PrivilegedAccessGroupAssignmentScheduleRequest, int, error) { + var status int + + body, err := json.Marshal(request) + if err != nil { + return nil, status, fmt.Errorf("json.Marshal(): %v", err) + } + + resp, status, _, err := c.BaseClient.Post(ctx, PostHttpRequestInput{ + ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc, + Body: body, + ValidStatusCodes: []int{http.StatusCreated}, + Uri: Uri{ + Entity: "/identityGovernance/privilegedAccess/group/assignmentScheduleRequests", + }, + }) + if err != nil && status != http.StatusNotFound { + return nil, status, fmt.Errorf("PrivilegedAccessGroupAssignmentScheduleRequestClient.BaseClient.Post(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var newRequest PrivilegedAccessGroupAssignmentScheduleRequest + if err := json.Unmarshal(respBody, &newRequest); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &newRequest, status, nil +} + +// Get retrieves a PrivilegedAccessGroupAssignmentScheduleRequest +func (c *PrivilegedAccessGroupAssignmentScheduleClient) RequestsGet(ctx context.Context, requestId string) (*PrivilegedAccessGroupAssignmentScheduleRequest, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("/identityGovernance/privilegedAccess/group/assignmentScheduleRequests/%s", requestId), + }, + }) + if err != nil { + return nil, status, fmt.Errorf("PrivilegedAccessGroupClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var request PrivilegedAccessGroupAssignmentScheduleRequest + if err := json.Unmarshal(respBody, &request); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &request, status, nil +} + +// Cancel cancels a PrivilegedAccessGroupAssignmentScheduleRequest +func (c *PrivilegedAccessGroupAssignmentScheduleClient) RequestsCancel(ctx context.Context, requestId string) (int, error) { + _, status, _, err := c.BaseClient.Post(ctx, PostHttpRequestInput{ + ValidStatusCodes: []int{http.StatusNoContent}, + Uri: Uri{ + Entity: fmt.Sprintf("/identityGovernance/privilegedAccess/group/assignmentScheduleRequests/%s/cancel", requestId), + }, + }) + if err != nil { + return status, fmt.Errorf("PrivilegedAccessGroupClient.BaseClient.Post(): %v", err) + } + + return status, nil +} diff --git a/vendor/github.com/manicminer/hamilton/msgraph/privilegedaccess_groups_assignmentschedule_instance.go b/vendor/github.com/manicminer/hamilton/msgraph/privilegedaccess_groups_assignmentschedule_instance.go new file mode 100644 index 0000000000..a914475647 --- /dev/null +++ b/vendor/github.com/manicminer/hamilton/msgraph/privilegedaccess_groups_assignmentschedule_instance.go @@ -0,0 +1,77 @@ +package msgraph + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/hashicorp/go-azure-sdk/sdk/odata" +) + +type PrivilegedAccessGroupAssignmentScheduleInstancesClient struct { + BaseClient Client +} + +func NewPrivilegedAccessGroupAssignmentScheduleInstancesClient() *PrivilegedAccessGroupAssignmentScheduleInstancesClient { + return &PrivilegedAccessGroupAssignmentScheduleInstancesClient{ + BaseClient: NewClient(VersionBeta), + } +} + +// List retrieves a list of PrivilegedAccessGroupAssignmentScheduleInstances +func (c *PrivilegedAccessGroupAssignmentScheduleInstancesClient) List(ctx context.Context, query odata.Query) (*[]PrivilegedAccessGroupAssignmentScheduleInstance, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + DisablePaging: query.Top > 0, + OData: query, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: "/identityGovernance/privilegedAccess/group/assignmentScheduleInstances", + }, + }) + if err != nil { + return nil, status, fmt.Errorf("PrivilegedAccessGroupClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var data struct { + Instances []PrivilegedAccessGroupAssignmentScheduleInstance `json:"value"` + } + if err := json.Unmarshal(respBody, &data); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &data.Instances, status, nil +} + +// Get retrieves a PrivilegedAccessGroupAssignmentScheduleInstance +func (c *PrivilegedAccessGroupAssignmentScheduleInstancesClient) Get(ctx context.Context, instanceId string) (*PrivilegedAccessGroupAssignmentScheduleInstance, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("/identityGovernance/privilegedAccess/group/assignmentScheduleInstances/%s", instanceId), + }, + }) + if err != nil { + return nil, status, fmt.Errorf("PrivilegedAccessGroupClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var instance PrivilegedAccessGroupAssignmentScheduleInstance + if err := json.Unmarshal(respBody, &instance); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &instance, status, nil +} diff --git a/vendor/github.com/manicminer/hamilton/msgraph/privilegedaccess_groups_assignmentschedule_requests.go b/vendor/github.com/manicminer/hamilton/msgraph/privilegedaccess_groups_assignmentschedule_requests.go new file mode 100644 index 0000000000..45c9084e18 --- /dev/null +++ b/vendor/github.com/manicminer/hamilton/msgraph/privilegedaccess_groups_assignmentschedule_requests.go @@ -0,0 +1,128 @@ +package msgraph + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/hashicorp/go-azure-sdk/sdk/odata" +) + +type PrivilegedAccessGroupAssignmentScheduleRequestsClient struct { + BaseClient Client +} + +func NewPrivilegedAccessGroupAssignmentScheduleRequestsClient() *PrivilegedAccessGroupAssignmentScheduleRequestsClient { + return &PrivilegedAccessGroupAssignmentScheduleRequestsClient{ + BaseClient: NewClient(VersionBeta), + } +} + +// List retrieves a list of PrivilegedAccessGroupAssignmentScheduleRequests +func (c *PrivilegedAccessGroupAssignmentScheduleRequestsClient) List(ctx context.Context, query odata.Query) (*[]PrivilegedAccessGroupAssignmentScheduleRequest, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc, + DisablePaging: query.Top > 0, + OData: query, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: "/identityGovernance/privilegedAccess/group/assignmentScheduleRequests", + }, + }) + if err != nil { + return nil, status, fmt.Errorf("PrivilegedAccessGroupAssignmentScheduleRequestsClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var data struct { + Requests []PrivilegedAccessGroupAssignmentScheduleRequest `json:"value"` + } + if err := json.Unmarshal(respBody, &data); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &data.Requests, status, nil +} + +// Create creates a new PrivilegedAccessGroupAssignmentScheduleRequest. +func (c *PrivilegedAccessGroupAssignmentScheduleRequestsClient) Create(ctx context.Context, request PrivilegedAccessGroupAssignmentScheduleRequest) (*PrivilegedAccessGroupAssignmentScheduleRequest, int, error) { + var status int + + body, err := json.Marshal(request) + if err != nil { + return nil, status, fmt.Errorf("json.Marshal(): %v", err) + } + + resp, status, _, err := c.BaseClient.Post(ctx, PostHttpRequestInput{ + ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc, + Body: body, + ValidStatusCodes: []int{http.StatusCreated}, + Uri: Uri{ + Entity: "/identityGovernance/privilegedAccess/group/assignmentScheduleRequests", + }, + }) + if err != nil && status != http.StatusNotFound { + return nil, status, fmt.Errorf("PrivilegedAccessGroupAssignmentScheduleRequestsClient.BaseClient.Post(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var newRequest PrivilegedAccessGroupAssignmentScheduleRequest + if err := json.Unmarshal(respBody, &newRequest); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &newRequest, status, nil +} + +// Get retrieves a PrivilegedAccessGroupAssignmentScheduleRequest +func (c *PrivilegedAccessGroupAssignmentScheduleRequestsClient) Get(ctx context.Context, requestId string) (*PrivilegedAccessGroupAssignmentScheduleRequest, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("/identityGovernance/privilegedAccess/group/assignmentScheduleRequests/%s", requestId), + }, + }) + if err != nil { + return nil, status, fmt.Errorf("PrivilegedAccessGroupAssignmentScheduleRequestsClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var request PrivilegedAccessGroupAssignmentScheduleRequest + if err := json.Unmarshal(respBody, &request); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &request, status, nil +} + +// Cancel cancels a PrivilegedAccessGroupAssignmentScheduleRequest +func (c *PrivilegedAccessGroupAssignmentScheduleRequestsClient) Cancel(ctx context.Context, requestId string) (int, error) { + _, status, _, err := c.BaseClient.Post(ctx, PostHttpRequestInput{ + ValidStatusCodes: []int{http.StatusNoContent}, + Uri: Uri{ + Entity: fmt.Sprintf("/identityGovernance/privilegedAccess/group/assignmentScheduleRequests/%s/cancel", requestId), + }, + }) + if err != nil { + return status, fmt.Errorf("PrivilegedAccessGroupAssignmentScheduleRequestsClient.BaseClient.Post(): %v", err) + } + + return status, nil +} diff --git a/vendor/github.com/manicminer/hamilton/msgraph/privilegedaccess_groups_eligibilityschedule.go b/vendor/github.com/manicminer/hamilton/msgraph/privilegedaccess_groups_eligibilityschedule.go new file mode 100644 index 0000000000..505886e9ec --- /dev/null +++ b/vendor/github.com/manicminer/hamilton/msgraph/privilegedaccess_groups_eligibilityschedule.go @@ -0,0 +1,77 @@ +package msgraph + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/hashicorp/go-azure-sdk/sdk/odata" +) + +type PrivilegedAccessGroupEligibilityScheduleClient struct { + BaseClient Client +} + +func NewPrivilegedAccessGroupEligibilityScheduleClient() *PrivilegedAccessGroupEligibilityScheduleClient { + return &PrivilegedAccessGroupEligibilityScheduleClient{ + BaseClient: NewClient(VersionBeta), + } +} + +// List retrieves a list of PrivilegedAccessGroupEligibilities +func (c *PrivilegedAccessGroupEligibilityScheduleClient) List(ctx context.Context, query odata.Query) (*[]PrivilegedAccessGroupEligibilitySchedule, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + DisablePaging: query.Top > 0, + OData: query, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: "/identityGovernance/privilegedAccess/group/eligibilitySchedules", + }, + }) + if err != nil { + return nil, status, fmt.Errorf("PrivilegedAccessGroupEligibilityScheduleClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var data struct { + Schedules []PrivilegedAccessGroupEligibilitySchedule `json:"value"` + } + if err := json.Unmarshal(respBody, &data); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &data.Schedules, status, nil +} + +// Get retrieves a PrivilegedAccessGroupEligibility +func (c *PrivilegedAccessGroupEligibilityScheduleClient) Get(ctx context.Context, scheduleId string) (*PrivilegedAccessGroupEligibilitySchedule, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("/identityGovernance/privilegedAccess/group/eligibilitySchedules/%s", scheduleId), + }, + }) + if err != nil { + return nil, status, fmt.Errorf("PrivilegedAccessGroupEligibilityScheduleClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var schedule PrivilegedAccessGroupEligibilitySchedule + if err := json.Unmarshal(respBody, &schedule); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &schedule, status, nil +} diff --git a/vendor/github.com/manicminer/hamilton/msgraph/privilegedaccess_groups_eligibilityschedule_instance.go b/vendor/github.com/manicminer/hamilton/msgraph/privilegedaccess_groups_eligibilityschedule_instance.go new file mode 100644 index 0000000000..7cd267fa94 --- /dev/null +++ b/vendor/github.com/manicminer/hamilton/msgraph/privilegedaccess_groups_eligibilityschedule_instance.go @@ -0,0 +1,77 @@ +package msgraph + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/hashicorp/go-azure-sdk/sdk/odata" +) + +type PrivilegedAccessGroupEligibilityScheduleInstancesClient struct { + BaseClient Client +} + +func NewPrivilegedAccessGroupEligibilityScheduleInstancesClient() *PrivilegedAccessGroupEligibilityScheduleInstancesClient { + return &PrivilegedAccessGroupEligibilityScheduleInstancesClient{ + BaseClient: NewClient(VersionBeta), + } +} + +// List retrieves a list of PrivilegedAccessGroupEligibilityScheduleInstances +func (c *PrivilegedAccessGroupEligibilityScheduleInstancesClient) List(ctx context.Context, query odata.Query) (*[]PrivilegedAccessGroupEligibilityScheduleInstance, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + DisablePaging: query.Top > 0, + OData: query, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: "/identityGovernance/privilegedAccess/group/eligibilityScheduleInstances", + }, + }) + if err != nil { + return nil, status, fmt.Errorf("PrivilegedAccessGroupClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var data struct { + Instances []PrivilegedAccessGroupEligibilityScheduleInstance `json:"value"` + } + if err := json.Unmarshal(respBody, &data); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &data.Instances, status, nil +} + +// Get retrieves a PrivilegedAccessGroupEligibilityScheduleInstance +func (c *PrivilegedAccessGroupEligibilityScheduleInstancesClient) Get(ctx context.Context, instanceId string) (*PrivilegedAccessGroupEligibilityScheduleInstance, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("/identityGovernance/privilegedAccess/group/eligibilityScheduleInstances/%s", instanceId), + }, + }) + if err != nil { + return nil, status, fmt.Errorf("PrivilegedAccessGroupClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var instance PrivilegedAccessGroupEligibilityScheduleInstance + if err := json.Unmarshal(respBody, &instance); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &instance, status, nil +} diff --git a/vendor/github.com/manicminer/hamilton/msgraph/privilegedaccess_groups_eligibilityschedule_request.go b/vendor/github.com/manicminer/hamilton/msgraph/privilegedaccess_groups_eligibilityschedule_request.go new file mode 100644 index 0000000000..2baa2f4644 --- /dev/null +++ b/vendor/github.com/manicminer/hamilton/msgraph/privilegedaccess_groups_eligibilityschedule_request.go @@ -0,0 +1,127 @@ +package msgraph + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/hashicorp/go-azure-sdk/sdk/odata" +) + +type PrivilegedAccessGroupEligibilityScheduleRequestsClient struct { + BaseClient Client +} + +func NewPrivilegedAccessGroupEligibilityScheduleRequestsClient() *PrivilegedAccessGroupEligibilityScheduleRequestsClient { + return &PrivilegedAccessGroupEligibilityScheduleRequestsClient{ + BaseClient: NewClient(VersionBeta), + } +} + +// List retrieves a list of PrivilegedAccessGroupEligibilityScheduleRequests +func (c *PrivilegedAccessGroupEligibilityScheduleRequestsClient) List(ctx context.Context, query odata.Query) (*[]PrivilegedAccessGroupEligibilityScheduleRequest, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + DisablePaging: query.Top > 0, + OData: query, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: "/identityGovernance/privilegedAccess/group/eligibilityScheduleRequests", + }, + }) + if err != nil { + return nil, status, fmt.Errorf("PrivilegedAccessGroupEligibilityScheduleRequestsClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var data struct { + Requests []PrivilegedAccessGroupEligibilityScheduleRequest `json:"value"` + } + if err := json.Unmarshal(respBody, &data); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &data.Requests, status, nil +} + +// Create creates a new PrivilegedAccessGroupEligibilityScheduleRequest. +func (c *PrivilegedAccessGroupEligibilityScheduleRequestsClient) Create(ctx context.Context, request PrivilegedAccessGroupEligibilityScheduleRequest) (*PrivilegedAccessGroupEligibilityScheduleRequest, int, error) { + var status int + + body, err := json.Marshal(request) + if err != nil { + return nil, status, fmt.Errorf("json.Marshal(): %v", err) + } + + resp, status, _, err := c.BaseClient.Post(ctx, PostHttpRequestInput{ + ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc, + Body: body, + ValidStatusCodes: []int{http.StatusCreated}, + Uri: Uri{ + Entity: "/identityGovernance/privilegedAccess/group/eligibilityScheduleRequests", + }, + }) + if err != nil && status != http.StatusNotFound { + return nil, status, fmt.Errorf("PrivilegedAccessGroupEligibilityScheduleRequestsClient.BaseClient.Post(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var newRequest PrivilegedAccessGroupEligibilityScheduleRequest + if err := json.Unmarshal(respBody, &newRequest); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &newRequest, status, nil +} + +// Get retrieves a PrivilegedAccessGroupEligibilityScheduleRequest +func (c *PrivilegedAccessGroupEligibilityScheduleRequestsClient) Get(ctx context.Context, requestId string) (*PrivilegedAccessGroupEligibilityScheduleRequest, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("/identityGovernance/privilegedAccess/group/eligibilityScheduleRequests/%s", requestId), + }, + }) + if err != nil { + return nil, status, fmt.Errorf("PrivilegedAccessGroupEligibilityScheduleRequestsClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var request PrivilegedAccessGroupEligibilityScheduleRequest + if err := json.Unmarshal(respBody, &request); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &request, status, nil +} + +// Cancel cancels a PrivilegedAccessGroupEligibilityScheduleRequest +func (c *PrivilegedAccessGroupEligibilityScheduleRequestsClient) Cancel(ctx context.Context, requestId string) (int, error) { + _, status, _, err := c.BaseClient.Post(ctx, PostHttpRequestInput{ + ValidStatusCodes: []int{http.StatusNoContent}, + Uri: Uri{ + Entity: fmt.Sprintf("/identityGovernance/privilegedAccess/group/eligibilityScheduleRequests/%s/cancel", requestId), + }, + }) + if err != nil { + return status, fmt.Errorf("PrivilegedAccessGroupEligibilityScheduleRequestsClient.BaseClient.Post(): %v", err) + } + + return status, nil +} diff --git a/vendor/github.com/manicminer/hamilton/msgraph/role_management_policies.go b/vendor/github.com/manicminer/hamilton/msgraph/role_management_policies.go new file mode 100644 index 0000000000..1d80274c54 --- /dev/null +++ b/vendor/github.com/manicminer/hamilton/msgraph/role_management_policies.go @@ -0,0 +1,104 @@ +package msgraph + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/hashicorp/go-azure-sdk/sdk/odata" +) + +type RoleManagementPolicyClient struct { + BaseClient Client +} + +func NewRoleManagementPolicyClient() *RoleManagementPolicyClient { + return &RoleManagementPolicyClient{ + BaseClient: NewClient(VersionBeta), + } +} + +// List retrieves a list of Role Management Policies +func (c *RoleManagementPolicyClient) List(ctx context.Context, query odata.Query) (*[]UnifiedRoleManagementPolicy, int, error) { + query.Expand = odata.Expand{Relationship: "*"} + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + ConsistencyFailureFunc: RetryOn404ConsistencyFailureFunc, + OData: query, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: "/policies/roleManagementPolicies", + }, + }) + if err != nil { + return nil, status, fmt.Errorf("RoleManagementPolicyClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var data struct { + UnifiedRoleManagementPolicy []UnifiedRoleManagementPolicy `json:"value"` + } + if err := json.Unmarshal(respBody, &data); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &data.UnifiedRoleManagementPolicy, status, nil +} + +// Get retrieves a UnifiedRoleManagementPolicy +func (c *RoleManagementPolicyClient) Get(ctx context.Context, id string) (*UnifiedRoleManagementPolicy, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + OData: odata.Query{ + Expand: odata.Expand{Relationship: "*"}, + }, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("/policies/roleManagementPolicies/%s", id), + }, + }) + if err != nil { + return nil, status, fmt.Errorf("RoleDefinitionsClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var policy UnifiedRoleManagementPolicy + if err := json.Unmarshal(respBody, &policy); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &policy, status, nil +} + +// Update amends an existing UnifiedRoleManagementPolicy. +func (c *RoleManagementPolicyClient) Update(ctx context.Context, policy UnifiedRoleManagementPolicy) (int, error) { + var status int + + body, err := json.Marshal(policy) + if err != nil { + return status, fmt.Errorf("json.Marshal(): %v", err) + } + + _, status, _, err = c.BaseClient.Patch(ctx, PatchHttpRequestInput{ + Body: body, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("/policies/roleManagementPolicies/%s", *policy.ID), + }, + }) + if err != nil { + return status, fmt.Errorf("RoleDefinitionsClient.BaseClient.Patch(): %v", err) + } + + return status, nil +} diff --git a/vendor/github.com/manicminer/hamilton/msgraph/role_management_policies_assignment.go b/vendor/github.com/manicminer/hamilton/msgraph/role_management_policies_assignment.go new file mode 100644 index 0000000000..22f138e1ab --- /dev/null +++ b/vendor/github.com/manicminer/hamilton/msgraph/role_management_policies_assignment.go @@ -0,0 +1,80 @@ +package msgraph + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/hashicorp/go-azure-sdk/sdk/odata" +) + +type RoleManagementPolicyAssignmentClient struct { + BaseClient Client +} + +func NewRoleManagementPolicyAssignmentClient() *RoleManagementPolicyAssignmentClient { + return &RoleManagementPolicyAssignmentClient{ + BaseClient: NewClient(VersionBeta), + } +} + +// List retrieves a list of Role Management Policies +func (c *RoleManagementPolicyAssignmentClient) List(ctx context.Context, query odata.Query) (*[]UnifiedRoleManagementPolicyAssignment, int, error) { + query.Expand = odata.Expand{Relationship: "*"} + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + OData: query, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: "/policies/roleManagementPolicyAssignments", + }, + }) + if err != nil { + return nil, status, fmt.Errorf("RoleManagementPolicyAssignmentClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var data struct { + UnifiedRoleManagementPolicyAssignment []UnifiedRoleManagementPolicyAssignment `json:"value"` + } + if err := json.Unmarshal(respBody, &data); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &data.UnifiedRoleManagementPolicyAssignment, status, nil +} + +// Get retrieves a UnifiedRoleManagementPolicy +func (c *RoleManagementPolicyAssignmentClient) Get(ctx context.Context, id string) (*UnifiedRoleManagementPolicyAssignment, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + OData: odata.Query{ + Expand: odata.Expand{Relationship: "*"}, + }, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("/policies/roleManagementPolicyAssignments/%s", id), + }, + }) + if err != nil { + return nil, status, fmt.Errorf("RoleDefinitionsClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var assign UnifiedRoleManagementPolicyAssignment + if err := json.Unmarshal(respBody, &assign); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &assign, status, nil +} diff --git a/vendor/github.com/manicminer/hamilton/msgraph/role_management_policies_rules.go b/vendor/github.com/manicminer/hamilton/msgraph/role_management_policies_rules.go new file mode 100644 index 0000000000..1efc115e3d --- /dev/null +++ b/vendor/github.com/manicminer/hamilton/msgraph/role_management_policies_rules.go @@ -0,0 +1,100 @@ +package msgraph + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/hashicorp/go-azure-sdk/sdk/odata" +) + +type RoleManagementPolicyRuleClient struct { + BaseClient Client +} + +func NewRoleManagementPolicyRuleClient() *RoleManagementPolicyRuleClient { + return &RoleManagementPolicyRuleClient{ + BaseClient: NewClient(VersionBeta), + } +} + +// List retrieves a list of Rules from a Role Management Policy +func (c *RoleManagementPolicyRuleClient) List(ctx context.Context, policyId string) (*[]UnifiedRoleManagementPolicyRule, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("/policies/roleManagementPolicies/%s/rules", policyId), + }, + }) + if err != nil { + return nil, status, fmt.Errorf("RoleManagementPolicyRuleClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var data struct { + UnifiedRoleManagementPolicyRule []UnifiedRoleManagementPolicyRule `json:"value"` + } + if err := json.Unmarshal(respBody, &data); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + + return &data.UnifiedRoleManagementPolicyRule, status, nil +} + +// Get retrieves a UnifiedRoleManagementPolicyRule +func (c *RoleManagementPolicyRuleClient) Get(ctx context.Context, policyId, ruleId string) (*UnifiedRoleManagementPolicyRule, int, error) { + resp, status, _, err := c.BaseClient.Get(ctx, GetHttpRequestInput{ + OData: odata.Query{ + Expand: odata.Expand{Relationship: "*"}, + }, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("/policies/roleManagementPolicies/%s/rules/%s", policyId, ruleId), + }, + }) + if err != nil { + return nil, status, fmt.Errorf("RoleManagementPolicyRuleClient.BaseClient.Get(): %v", err) + } + + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, status, fmt.Errorf("io.ReadAll(): %v", err) + } + + var rule UnifiedRoleManagementPolicyRule + if err := json.Unmarshal(respBody, &rule); err != nil { + return nil, status, fmt.Errorf("json.Unmarshal(): %v", err) + } + return &rule, status, nil +} + +// Update amends an existing UnifiedRoleManagementPolicyRule. +func (c *RoleManagementPolicyRuleClient) Update(ctx context.Context, policyId string, rule UnifiedRoleManagementPolicyRule) (int, error) { + var status int + + body, err := json.Marshal(rule) + if err != nil { + return status, fmt.Errorf("json.Marshal(): %v", err) + } + + _, status, _, err = c.BaseClient.Patch(ctx, PatchHttpRequestInput{ + Body: body, + ValidStatusCodes: []int{http.StatusOK}, + Uri: Uri{ + Entity: fmt.Sprintf("/policies/roleManagementPolicies/%s/rules/%s", policyId, *rule.ID), + }, + }) + if err != nil { + return status, fmt.Errorf("RoleManagementPolicyRuleClient.BaseClient.Patch(): %v", err) + } + + return status, nil +} diff --git a/vendor/github.com/manicminer/hamilton/msgraph/valuetypes.go b/vendor/github.com/manicminer/hamilton/msgraph/valuetypes.go index e89657c14e..0d7efb8398 100644 --- a/vendor/github.com/manicminer/hamilton/msgraph/valuetypes.go +++ b/vendor/github.com/manicminer/hamilton/msgraph/valuetypes.go @@ -232,6 +232,15 @@ const ( ApprovalModeSingleStage ApprovalMode = "SingleStage" ) +type ApprovalStepStatus = string + +const ( + ApprovalStepStatusInProgress ApprovalStepStatus = "InProgress" + ApprovalStepStatusInitializing ApprovalStepStatus = "Initializing" + ApprovalStepStatusCompleted ApprovalStepStatus = "Completed" + ApprovalStepStatusExpired ApprovalStepStatus = "Expired" +) + type AttestationLevel = string const ( @@ -604,6 +613,7 @@ const ( GroupResourceBehaviorOptionCalendarMemberReadOnly GroupResourceBehaviorOption = "CalendarMemberReadOnly" GroupResourceBehaviorOptionConnectorsDisabled GroupResourceBehaviorOption = "ConnectorsDisabled" GroupResourceBehaviorOptionHideGroupInOutlook GroupResourceBehaviorOption = "HideGroupInOutlook" + GroupResourceBehaviorOptionSkipExchangeInstantOn GroupResourceBehaviorOption = "SkipExchangeInstantOn" GroupResourceBehaviorOptionSubscribeMembersToCalendarEventsDisabled GroupResourceBehaviorOption = "SubscribeMembersToCalendarEventsDisabled" GroupResourceBehaviorOptionSubscribeNewGroupMembers GroupResourceBehaviorOption = "SubscribeNewGroupMembers" GroupResourceBehaviorOptionWelcomeEmailDisabled GroupResourceBehaviorOption = "WelcomeEmailDisabled" @@ -747,6 +757,74 @@ const ( PreferredSingleSignOnModeSaml PreferredSingleSignOnMode = "saml" ) +type PrivilegedAccessGroupAction = string + +const ( + PrivilegedAccessGroupActionAdminAssign PrivilegedAccessGroupAction = "adminAssign" + PrivilegedAccessGroupActionAdminUpdate PrivilegedAccessGroupAction = "adminUpdate" + PrivilegedAccessGroupActionAdminRemove PrivilegedAccessGroupAction = "adminRemove" + PrivilegedAccessGroupActionAdminExtend PrivilegedAccessGroupAction = "adminExtend" + PrivilegedAccessGroupActionAdminRenew PrivilegedAccessGroupAction = "adminRenew" + PrivilegedAccessGroupActionSelfActivate PrivilegedAccessGroupAction = "selfActivate" + PrivilegedAccessGroupActionSelfDeactivate PrivilegedAccessGroupAction = "selfDeactivate" +) + +type PrivilegedAccessGroupAssignmentType = string + +const ( + PrivilegedAccessGroupAssignmentAssigned PrivilegedAccessGroupAssignmentType = "assigned" + PrivilegedAccessGroupAssignmentActivated PrivilegedAccessGroupAssignmentType = "activated" + PrivilegedAccessGroupAssignmentUnknown PrivilegedAccessGroupAssignmentType = "unknownFutureValue" +) + +type PrivilegedAccessGroupAssignmentStatus = string + +const ( + PrivilegedAccessGroupAssignmentStatusCanceled PrivilegedAccessGroupAssignmentStatus = "Canceled" + PrivilegedAccessGroupAssignmentStatusDenied PrivilegedAccessGroupAssignmentStatus = "Denied" + PrivilegedAccessGroupAssignmentStatusFailed PrivilegedAccessGroupAssignmentStatus = "Failed" + PrivilegedAccessGroupAssignmentStatusGranted PrivilegedAccessGroupAssignmentStatus = "Granted" + PrivilegedAccessGroupAssignmentStatusPendingAdminDecision PrivilegedAccessGroupAssignmentStatus = "PendingAdminDecision" + PrivilegedAccessGroupAssignmentStatusPendingApproval PrivilegedAccessGroupAssignmentStatus = "PendingApproval" + PrivilegedAccessGroupAssignmentStatusPendingProvisioning PrivilegedAccessGroupAssignmentStatus = "PendingProvisioning" + PrivilegedAccessGroupAssignmentStatusPendingScheduledCreation PrivilegedAccessGroupAssignmentStatus = "PendingScheduleCreation" + PrivilegedAccessGroupAssignmentStatusProvisioned PrivilegedAccessGroupAssignmentStatus = "Provisioned" + PrivilegedAccessGroupAssignmentStatusRevoked PrivilegedAccessGroupAssignmentStatus = "Revoked" + PrivilegedAccessGroupAssignmentStatusScheduleCreated PrivilegedAccessGroupAssignmentStatus = "ScheduleCreated" +) + +type PrivilegedAccessGroupEligibilityStatus = string + +const ( + PrivilegedAccessGroupEligibilityStatusCanceled PrivilegedAccessGroupEligibilityStatus = "Canceled" + PrivilegedAccessGroupEligibilityStatusDenied PrivilegedAccessGroupEligibilityStatus = "Denied" + PrivilegedAccessGroupEligibilityStatusFailed PrivilegedAccessGroupEligibilityStatus = "Failed" + PrivilegedAccessGroupEligibilityStatusGranted PrivilegedAccessGroupEligibilityStatus = "Granted" + PrivilegedAccessGroupEligibilityStatusPendingAdminDecision PrivilegedAccessGroupEligibilityStatus = "PendingAdminDecision" + PrivilegedAccessGroupEligibilityStatusPendingApproval PrivilegedAccessGroupEligibilityStatus = "PendingApproval" + PrivilegedAccessGroupEligibilityStatusPendingProvisioning PrivilegedAccessGroupEligibilityStatus = "PendingProvisioning" + PrivilegedAccessGroupEligibilityStatusPendingScheduledCreation PrivilegedAccessGroupEligibilityStatus = "PendingScheduleCreation" + PrivilegedAccessGroupEligibilityStatusProvisioned PrivilegedAccessGroupEligibilityStatus = "Provisioned" + PrivilegedAccessGroupEligibilityStatusRevoked PrivilegedAccessGroupEligibilityStatus = "Revoked" + PrivilegedAccessGroupEligibilityStatusScheduleCreated PrivilegedAccessGroupEligibilityStatus = "ScheduleCreated" +) + +type PrivilegedAccessGroupMemberType = string + +const ( + PrivilegedAccessGroupMemberDirect PrivilegedAccessGroupMemberType = "direct" + PrivilegedAccessGroupMemberGroup PrivilegedAccessGroupMemberType = "group" + PrivilegedAccessGroupMemberUnknown PrivilegedAccessGroupMemberType = "unknownFutureValue" +) + +type PrivilegedAccessGroupRelationship = string + +const ( + PrivilegedAccessGroupRelationshipOwner PrivilegedAccessGroupRelationship = "owner" + PrivilegedAccessGroupRelationshipMember PrivilegedAccessGroupRelationship = "member" + PrivilegedAccessGroupRelationshipUnknown PrivilegedAccessGroupRelationship = "unknownFutureValue" +) + type RecurrencePatternType = string const ( @@ -846,6 +924,64 @@ const ( UnifiedRoleScheduleRequestActionUnknownFutureValue UnifiedRoleScheduleRequestAction = "unknownFutureValue" ) +type UnifiedRoleManagementPolicyScope = string + +const ( + UnifiedRoleManagementPolicyScopeDirectory UnifiedRoleManagementPolicyScope = "Directory" + UnifiedRoleManagementPolicyScopeDirectoryRole UnifiedRoleManagementPolicyScope = "DirectoryRole" + UnifiedRoleManagementPolicyScopeGroup UnifiedRoleManagementPolicyScope = "Group" +) + +type UnifiedRoleManagementPolicyRuleTargetCallerType = string + +const ( + UnifiedRoleManagementPolicyRuleTargetCallerTypeNone UnifiedRoleManagementPolicyRuleTargetCallerType = "None" + UnifiedRoleManagementPolicyRuleTargetCallerTypeAdmin UnifiedRoleManagementPolicyRuleTargetCallerType = "Admin" + UnifiedRoleManagementPolicyRuleTargetCallerTypeEndUser UnifiedRoleManagementPolicyRuleTargetCallerType = "EndUser" +) + +type UnifiedRoleManagementPolicyRuleLevel = string + +const ( + UnifiedRoleManagementPolicyRuleLevelEligibility UnifiedRoleManagementPolicyRuleLevel = "Eligibility" + UnifiedRoleManagementPolicyRuleLevelAssignment UnifiedRoleManagementPolicyRuleLevel = "Assignment" +) + +type UnifiedRoleManagementPolicyRuleNotificationLevel = string + +const ( + UnifiedRoleManagementPolicyRuleNotificationLevelNone UnifiedRoleManagementPolicyRuleNotificationLevel = "None" + UnifiedRoleManagementPolicyRuleNotificationLevelCritical UnifiedRoleManagementPolicyRuleNotificationLevel = "Critical" + UnifiedRoleManagementPolicyRuleNotificationLevelAll UnifiedRoleManagementPolicyRuleNotificationLevel = "All" +) + +type UnifiedRoleManagementPolicyRuleNotificationRecipientType = string + +const ( + UnifiedRoleManagementPolicyRuleNotificationRecipientTypeRequestor UnifiedRoleManagementPolicyRuleNotificationRecipientType = "Requestor" + UnifiedRoleManagementPolicyRuleNotificationRecipientTypeApprover UnifiedRoleManagementPolicyRuleNotificationRecipientType = "Approver" + UnifiedRoleManagementPolicyRuleNotificationRecipientTypeAdmin UnifiedRoleManagementPolicyRuleNotificationRecipientType = "Admin" +) + +type UnifiedRoleManagementPolicyRuleNotificationType = string + +const ( + UnifiedRoleManagementPolicyRuleNotificationTypeEmail UnifiedRoleManagementPolicyRuleNotificationType = "Email" +) + +type UnifiedRoleManagementPolicyRuleOperation = string + +const ( + UnifiedRoleManagementPolicyRuleOperationAll UnifiedRoleManagementPolicyRuleOperation = "All" + UnifiedRoleManagementPolicyRuleOperationActivate UnifiedRoleManagementPolicyRuleOperation = "Activate" + UnifiedRoleManagementPolicyRuleOperationDeactivate UnifiedRoleManagementPolicyRuleOperation = "Deactivate" + UnifiedRoleManagementPolicyRuleOperationAssign UnifiedRoleManagementPolicyRuleOperation = "Assign" + UnifiedRoleManagementPolicyRuleOperationUpdate UnifiedRoleManagementPolicyRuleOperation = "Update" + UnifiedRoleManagementPolicyRuleOperationRemove UnifiedRoleManagementPolicyRuleOperation = "Remove" + UnifiedRoleManagementPolicyRuleOperationExtend UnifiedRoleManagementPolicyRuleOperation = "Extend" + UnifiedRoleManagementPolicyRuleOperationRenew UnifiedRoleManagementPolicyRuleOperation = "Renew" +) + type UsageAuthMethod = string const ( diff --git a/vendor/modules.txt b/vendor/modules.txt index f9bf2fe2fa..64a6aee0f6 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -209,7 +209,7 @@ github.com/hashicorp/terraform-svchost # github.com/hashicorp/yamux v0.1.1 ## explicit; go 1.15 github.com/hashicorp/yamux -# github.com/manicminer/hamilton v0.66.0 +# github.com/manicminer/hamilton v0.67.0 ## explicit; go 1.21 github.com/manicminer/hamilton/errors github.com/manicminer/hamilton/internal/utils From c1d6e255001fde560ed769a950717f4b8c4aa2d2 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Fri, 5 Apr 2024 09:42:18 +1300 Subject: [PATCH 44/55] Make Group Role Management documentation clearer --- .../resources/group_role_management_policy.md | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/docs/resources/group_role_management_policy.md b/docs/resources/group_role_management_policy.md index 425ae701ad..1d54787deb 100644 --- a/docs/resources/group_role_management_policy.md +++ b/docs/resources/group_role_management_policy.md @@ -30,17 +30,17 @@ resource "azuread_user" "member" { } resource "azuread_group_role_management_policy" "example" { - object_id = azuread_group.example.id + group_id = azuread_group.example.id assignment_type = "member" - eligible_assignment_rules { - expiration_required = false - } - active_assignment_rules { expire_after = "P365D" } + eligible_assignment_rules { + expiration_required = false + } + notification_rules { approver_notifications { eligible_assignments { @@ -63,19 +63,19 @@ resource "azuread_group_role_management_policy" "example" { * `assignment_type` - (Required) The type of assignment this policy coveres. Can be either `member` or `owner`. * `eligible_assignment_rules` - (Optional) An `eligible_assignment_rules` block as defined below. * `group_id` - (Required) The ID of the Azure AD group for which the policy applies. -* `notification_rules` - (Optional) An `notification_rules` block as defined below. +* `notification_rules` - (Optional) A `notification_rules` block as defined below. --- An `activation_rules` block supports the following: -* `maximum_duration` - (Optional) The maximum length of time an activated role can be valid, in an IS)8601 Duration format (e.g. `PT8H`). Valid range is `PT30M` to `PT23H30M`, in 30 minute increments, or `PT1D`. * `approval_stage` - (Optional) An `approval_stage` block as defined below. +* `maximum_duration` - (Optional) The maximum length of time an activated role can be valid, in an IS)8601 Duration format (e.g. `PT8H`). Valid range is `PT30M` to `PT23H30M`, in 30 minute increments, or `PT1D`. * `require_approval` - (Optional) Is approval required for activation. If `true` an `approval_stage` block must be provided. -* `required_conditional_access_authentication_context` - (Optional) The Entra ID Conditional Access context that must be present for activation. Conflicts with `require_multifactor_authentication`. -* `require_multifactor_authentication` - (Optional) Is multi-factor authentication required to activate the role. Conflicts with `required_conditional_access_authentication_context`. * `require_justification` - (Optional) Is a justification required during activation of the role. +* `require_multifactor_authentication` - (Optional) Is multi-factor authentication required to activate the role. Conflicts with `required_conditional_access_authentication_context`. * `require_ticket_info` - (Optional) Is ticket information requrired during activation of the role. +* `required_conditional_access_authentication_context` - (Optional) The Entra ID Conditional Access context that must be present for activation. Conflicts with `require_multifactor_authentication`. --- @@ -83,8 +83,8 @@ An `active_assignment_rules` block supports the following: * `expiration_required` - (Optional) Must an assignment have an expiry date. `false` allows permanent assignment. * `expire_after` - (Optional) The maximum length of time an assignment can be valid, as an ISO8601 duration. Permitted values: `P15D`, `P30D`, `P90D`, `P180D`, or `P365D`. -* `require_multifactor_authentication` - (Optional) Is multi-factor authentication required to create new assignments. * `require_justification` - (Optional) Is a justification required to create new assignments. +* `require_multifactor_authentication` - (Optional) Is multi-factor authentication required to create new assignments. * `require_ticket_info` - (Optional) Is ticket information required to create new assignments. One of `expiration_required` or `expire_after` must be provided. @@ -108,25 +108,29 @@ One of `expiration_required` or `expire_after` must be provided. A `notification_rules` block supports the following: -* `active_assignments` - An optional `notification_events` block as defined below to configure notfications on active role assignments. -* `eligible_activations` - An optional `notification_events` block as defined below for configuring notifications on activation of eligible role. -* `eligible_assignments` - An optional `notification_events` block as defined below to configure notification on eligible role assignments. +* `active_assignments` - (Optional) A `notification_target` block as defined below to configure notfications on active role assignments. +* `eligible_activations` - (Optional) A `notification_target` block as defined below for configuring notifications on activation of eligible role. +* `eligible_assignments` - (Optional) A `notification_target` block as defined below to configure notification on eligible role assignments. ---- +At least one `notification_target` block must be provided. -An `notification_events` block supports the following: +--- -* `admin_notifications` - (Optional) An `notification_settings` block as defined below. -* `approver_notifications` - (Optional) An `notification_settings` block as defined below. -* `assignee_notifications` - (Optional) An `notification_settings` block as defined below. +A `notification_settings` block supports the following: +* `additional_recipients` - (Optional) A list of additional email addresses that will receive these notifications. +* `default_recipients` - (Required) Should the default recipients receive these notifications. +* `notification_level` - (Required) What level of notifications should be sent. Options are `All` or `Critical`. --- -A `notification_settings` block supports the following: -* `notification_level` - (Required) What level of notifications should be sent. Options are `All` or `Critical`. -* `default_recipients` - (Required) Should the default recipients receive these notifications. -* `additional_recipients` - (Optional) A list of additional email addresses that will receive these notifications. +A `notification_target` block supports the following: + +* `admin_notifications` - (Optional) A `notification_settings` block as defined above. +* `approver_notifications` - (Optional) A `notification_settings` block as defined above. +* `assignee_notifications` - (Optional) A `notification_settings` block as defined above. + +At least one `notification_settings` block must be provided. --- From 16816306c3d69f63f696d6bda1a19d05931195ee Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Fri, 5 Apr 2024 10:13:42 +1300 Subject: [PATCH 45/55] Add full validation function for role management policy IDs --- .../group_role_management_policy_resource.go | 2 +- .../parse/role_management_policy_assignment.go | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index c5e352886c..c31da33364 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -81,7 +81,7 @@ type GroupRoleManagementPolicyNotificationSettings struct { type GroupRoleManagementPolicyResource struct{} func (r GroupRoleManagementPolicyResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { - return validation.IsUUID + return parse.ValidateRoleManagementPolicyAssignmentID } var _ sdk.Resource = GroupRoleManagementPolicyResource{} diff --git a/internal/services/policies/parse/role_management_policy_assignment.go b/internal/services/policies/parse/role_management_policy_assignment.go index f735e1d30d..1baf2b9a67 100644 --- a/internal/services/policies/parse/role_management_policy_assignment.go +++ b/internal/services/policies/parse/role_management_policy_assignment.go @@ -69,3 +69,18 @@ func (id *RoleManagementPolicyAssignmentId) ID() string { func (id *RoleManagementPolicyAssignmentId) String() string { return fmt.Sprintf("Role Management Policy Assignment ID: %s_%s_%s_%s", id.ScopeType, id.ScopeId, id.PolicyId, id.RoleDefinitionId) } + +func ValidateRoleManagementPolicyAssignmentID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + _, err := ParseRoleManagementPolicyAssignmentID(v) + if err != nil { + errors = append(errors, err) + } + + return +} From 0bd0aeff52592e25e1b3c58f77383e984876b46d Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Fri, 5 Apr 2024 10:14:01 +1300 Subject: [PATCH 46/55] Create custom importer for group role management policy --- .../group_role_management_policy_resource.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index c31da33364..04b347c1f5 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -613,6 +613,22 @@ func (r GroupRoleManagementPolicyResource) Delete() sdk.ResourceFunc { } } +func (r GroupRoleManagementPolicyResource) CustomImporter() sdk.ResourceRunFunc { + return func(ctx context.Context, metadata sdk.ResourceMetaData) error { + id, err := parse.ParseRoleManagementPolicyAssignmentID(metadata.ResourceData.Id()) + if err != nil { + return err + } + if err = metadata.ResourceData.Set("assignment_type", id.RoleDefinitionId); err != nil { + return err + } + metadata.SetID(parse.NewRoleManagementPolicyID(id.ScopeType, id.ScopeId, id.PolicyId)) + + return r.Read().Func(ctx, metadata) + } + +} + func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.UnifiedRoleManagementPolicy) (*msgraph.UnifiedRoleManagementPolicy, error) { var model GroupRoleManagementPolicyModel if err := metadata.Decode(&model); err != nil { From f71d75fa77cf768ae479da40955a9f1c823346b1 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Mon, 8 Apr 2024 18:08:47 +1200 Subject: [PATCH 47/55] Move to managing schedules rather than requests --- ...leged_access_group_assignment_schedule.md} | 8 +- ...eged_access_group_eligibility_schedule.md} | 8 +- .../identitygovernance/client/client.go | 60 ++++-- ...ccess_group_assignment_schedule_request.go | 35 ---- ...cess_group_eligibility_schedule_request.go | 35 ---- .../parse/privileged_access_group_schedule.go | 72 +++++++ ...leged_access_group_assignment_schedule.go} | 175 ++++++++++++------ ..._access_group_assignment_schedule_test.go} | 70 ++++--- ...leged_access_group_eligiblity_schedule.go} | 175 ++++++++++++------ ..._access_group_eligiblity_schedule_test.go} | 56 +++--- ...go => privileged_access_group_schedule.go} | 26 +-- .../identitygovernance/registration.go | 4 +- 12 files changed, 421 insertions(+), 303 deletions(-) rename docs/resources/{privileged_access_group_assignment_schedule_request.md => privileged_access_group_assignment_schedule.md} (90%) rename docs/resources/{privileged_access_group_eligibility_schedule_request.md => privileged_access_group_eligibility_schedule.md} (92%) delete mode 100644 internal/services/identitygovernance/parse/privileged_access_group_assignment_schedule_request.go delete mode 100644 internal/services/identitygovernance/parse/privileged_access_group_eligibility_schedule_request.go create mode 100644 internal/services/identitygovernance/parse/privileged_access_group_schedule.go rename internal/services/identitygovernance/{privileged_access_group_assignment_schedule_request.go => privileged_access_group_assignment_schedule.go} (56%) rename internal/services/identitygovernance/{privileged_access_group_assignment_schedule_request_test.go => privileged_access_group_assignment_schedule_test.go} (61%) rename internal/services/identitygovernance/{privileged_access_group_eligiblity_schedule_request.go => privileged_access_group_eligiblity_schedule.go} (56%) rename internal/services/identitygovernance/{privileged_access_group_eligiblity_schedule_request_test.go => privileged_access_group_eligiblity_schedule_test.go} (69%) rename internal/services/identitygovernance/{privileged_access_group_schedule_request.go => privileged_access_group_schedule.go} (87%) diff --git a/docs/resources/privileged_access_group_assignment_schedule_request.md b/docs/resources/privileged_access_group_assignment_schedule.md similarity index 90% rename from docs/resources/privileged_access_group_assignment_schedule_request.md rename to docs/resources/privileged_access_group_assignment_schedule.md index 1fe65bca93..effafce490 100644 --- a/docs/resources/privileged_access_group_assignment_schedule_request.md +++ b/docs/resources/privileged_access_group_assignment_schedule.md @@ -2,7 +2,7 @@ subcategory: "Identity Governance" --- -# Resource: azuread_privileged_access_group_assignment_schedule_request +# Resource: azuread_privileged_access_group_assignment_schedule Manages an active assignment to a privileged access group. @@ -29,7 +29,7 @@ resource "azuread_user" "member" { password = "SecretP@sswd99!" } -resource "azuread_privileged_access_group_assignment_schedule_request" "example" { +resource "azuread_privileged_access_group_assignment_schedule" "example" { group_id = azuread_group.pim.id principal_id = azuread_user.member.id assignment_type = "member" @@ -63,8 +63,8 @@ In addition to all arguments above, the following attributes are exported: ## Import -An assignment schedule can be imported using the ID, e.g. +An assignment schedule can be imported using the schedule ID, e.g. ```shell -terraform import azuread_privileged_access_group_assignment_schedule_request.example 00000000-0000-0000-0000-000000000000 +terraform import azuread_privileged_access_group_assignment_schedule.example 00000000-0000-0000-0000-000000000000_member_00000000-0000-0000-0000-000000000000 ``` diff --git a/docs/resources/privileged_access_group_eligibility_schedule_request.md b/docs/resources/privileged_access_group_eligibility_schedule.md similarity index 92% rename from docs/resources/privileged_access_group_eligibility_schedule_request.md rename to docs/resources/privileged_access_group_eligibility_schedule.md index 336fb08187..c65974dd17 100644 --- a/docs/resources/privileged_access_group_eligibility_schedule_request.md +++ b/docs/resources/privileged_access_group_eligibility_schedule.md @@ -2,7 +2,7 @@ subcategory: "Identity Governance" --- -# Resource: azuread_privileged_access_group_eligibility_schedule_request +# Resource: azuread_privileged_access_group_eligibility_schedule Manages an eligible assignment to a privileged access group. @@ -29,7 +29,7 @@ resource "azuread_user" "member" { password = "SecretP@sswd99!" } -resource "azuread_privileged_access_group_eligibility_schedule_request" "example" { +resource "azuread_privileged_access_group_eligibility_schedule" "example" { group_id = azuread_group.pim.id principal_id = azuread_user.member.id assignment_type = "member" @@ -63,8 +63,8 @@ In addition to all arguments above, the following attributes are exported: ## Import -An assignment schedule can be imported using the ID, e.g. +An assignment schedule can be imported using the schedule ID, e.g. ```shell -terraform import azuread_privileged_access_group_eligibility_schedule_request.example 00000000-0000-0000-0000-000000000000 +terraform import azuread_privileged_access_group_eligibility_schedule.example 00000000-0000-0000-0000-000000000000_member_00000000-0000-0000-0000-000000000000 ``` diff --git a/internal/services/identitygovernance/client/client.go b/internal/services/identitygovernance/client/client.go index 247cd4254b..fdb37c852b 100644 --- a/internal/services/identitygovernance/client/client.go +++ b/internal/services/identitygovernance/client/client.go @@ -9,16 +9,20 @@ import ( ) type Client struct { - AccessPackageAssignmentPolicyClient *msgraph.AccessPackageAssignmentPolicyClient - AccessPackageCatalogClient *msgraph.AccessPackageCatalogClient - AccessPackageCatalogRoleAssignmentsClient *msgraph.EntitlementRoleAssignmentsClient - AccessPackageCatalogRoleClient *msgraph.EntitlementRoleDefinitionsClient - AccessPackageClient *msgraph.AccessPackageClient - AccessPackageResourceClient *msgraph.AccessPackageResourceClient - AccessPackageResourceRequestClient *msgraph.AccessPackageResourceRequestClient - AccessPackageResourceRoleScopeClient *msgraph.AccessPackageResourceRoleScopeClient - PrivilegedAccessGroupAssignmentScheduleRequestsClient *msgraph.PrivilegedAccessGroupAssignmentScheduleRequestsClient - PrivilegedAccessGroupEligibilityScheduleRequestsClient *msgraph.PrivilegedAccessGroupEligibilityScheduleRequestsClient + AccessPackageAssignmentPolicyClient *msgraph.AccessPackageAssignmentPolicyClient + AccessPackageCatalogClient *msgraph.AccessPackageCatalogClient + AccessPackageCatalogRoleAssignmentsClient *msgraph.EntitlementRoleAssignmentsClient + AccessPackageCatalogRoleClient *msgraph.EntitlementRoleDefinitionsClient + AccessPackageClient *msgraph.AccessPackageClient + AccessPackageResourceClient *msgraph.AccessPackageResourceClient + AccessPackageResourceRequestClient *msgraph.AccessPackageResourceRequestClient + AccessPackageResourceRoleScopeClient *msgraph.AccessPackageResourceRoleScopeClient + PrivilegedAccessGroupAssignmentScheduleClient *msgraph.PrivilegedAccessGroupAssignmentScheduleClient + PrivilegedAccessGroupAssignmentScheduleInstancesClient *msgraph.PrivilegedAccessGroupAssignmentScheduleInstancesClient + PrivilegedAccessGroupAssignmentScheduleRequestsClient *msgraph.PrivilegedAccessGroupAssignmentScheduleRequestsClient + PrivilegedAccessGroupEligibilityScheduleClient *msgraph.PrivilegedAccessGroupEligibilityScheduleClient + PrivilegedAccessGroupEligibilityScheduleInstancesClient *msgraph.PrivilegedAccessGroupEligibilityScheduleInstancesClient + PrivilegedAccessGroupEligibilityScheduleRequestsClient *msgraph.PrivilegedAccessGroupEligibilityScheduleRequestsClient } func NewClient(o *common.ClientOptions) *Client { @@ -56,22 +60,38 @@ func NewClient(o *common.ClientOptions) *Client { o.ConfigureClient(&accessPackageResourceRoleScopeClient.BaseClient) accessPackageResourceRoleScopeClient.BaseClient.ApiVersion = msgraph.VersionBeta + privilegedAccessGroupAssignmentScheduleClient := msgraph.NewPrivilegedAccessGroupAssignmentScheduleClient() + o.ConfigureClient(&privilegedAccessGroupAssignmentScheduleClient.BaseClient) + + privilegedAccessGroupAssignmentScheduleInstancesClient := msgraph.NewPrivilegedAccessGroupAssignmentScheduleInstancesClient() + o.ConfigureClient(&privilegedAccessGroupAssignmentScheduleInstancesClient.BaseClient) + privilegedAccessGroupAssignmentScheduleRequestsClient := msgraph.NewPrivilegedAccessGroupAssignmentScheduleRequestsClient() o.ConfigureClient(&privilegedAccessGroupAssignmentScheduleRequestsClient.BaseClient) + privilegedAccessGroupEligibilityScheduleClient := msgraph.NewPrivilegedAccessGroupEligibilityScheduleClient() + o.ConfigureClient(&privilegedAccessGroupEligibilityScheduleClient.BaseClient) + + privilegedAccessGroupEligibilityScheduleInstancesClient := msgraph.NewPrivilegedAccessGroupEligibilityScheduleInstancesClient() + o.ConfigureClient(&privilegedAccessGroupEligibilityScheduleInstancesClient.BaseClient) + privilegedAccessGroupEligibilityScheduleRequestsClient := msgraph.NewPrivilegedAccessGroupEligibilityScheduleRequestsClient() o.ConfigureClient(&privilegedAccessGroupEligibilityScheduleRequestsClient.BaseClient) return &Client{ - AccessPackageAssignmentPolicyClient: accessPackageAssignmentPolicyClient, - AccessPackageCatalogClient: accessPackageCatalogClient, - AccessPackageCatalogRoleAssignmentsClient: accessPackageCatalogRoleAssignmentsClient, - AccessPackageCatalogRoleClient: accessPackageCatalogRoleClient, - AccessPackageClient: accessPackageClient, - AccessPackageResourceClient: accessPackageResourceClient, - AccessPackageResourceRequestClient: accessPackageResourceRequestClient, - AccessPackageResourceRoleScopeClient: accessPackageResourceRoleScopeClient, - PrivilegedAccessGroupAssignmentScheduleRequestsClient: privilegedAccessGroupAssignmentScheduleRequestsClient, - PrivilegedAccessGroupEligibilityScheduleRequestsClient: privilegedAccessGroupEligibilityScheduleRequestsClient, + AccessPackageAssignmentPolicyClient: accessPackageAssignmentPolicyClient, + AccessPackageCatalogClient: accessPackageCatalogClient, + AccessPackageCatalogRoleAssignmentsClient: accessPackageCatalogRoleAssignmentsClient, + AccessPackageCatalogRoleClient: accessPackageCatalogRoleClient, + AccessPackageClient: accessPackageClient, + AccessPackageResourceClient: accessPackageResourceClient, + AccessPackageResourceRequestClient: accessPackageResourceRequestClient, + AccessPackageResourceRoleScopeClient: accessPackageResourceRoleScopeClient, + PrivilegedAccessGroupAssignmentScheduleClient: privilegedAccessGroupAssignmentScheduleClient, + PrivilegedAccessGroupAssignmentScheduleInstancesClient: privilegedAccessGroupAssignmentScheduleInstancesClient, + PrivilegedAccessGroupAssignmentScheduleRequestsClient: privilegedAccessGroupAssignmentScheduleRequestsClient, + PrivilegedAccessGroupEligibilityScheduleClient: privilegedAccessGroupEligibilityScheduleClient, + PrivilegedAccessGroupEligibilityScheduleInstancesClient: privilegedAccessGroupEligibilityScheduleInstancesClient, + PrivilegedAccessGroupEligibilityScheduleRequestsClient: privilegedAccessGroupEligibilityScheduleRequestsClient, } } diff --git a/internal/services/identitygovernance/parse/privileged_access_group_assignment_schedule_request.go b/internal/services/identitygovernance/parse/privileged_access_group_assignment_schedule_request.go deleted file mode 100644 index 584bd5828d..0000000000 --- a/internal/services/identitygovernance/parse/privileged_access_group_assignment_schedule_request.go +++ /dev/null @@ -1,35 +0,0 @@ -package parse - -import ( - "fmt" - - "github.com/hashicorp/terraform-provider-azuread/internal/tf/validation" -) - -type PrivilegedAccessGroupAssignmentScheduleRequestId struct { - RequestId string -} - -func NewPrivilegedAccessGroupAssignmentScheduleRequestID(requestId string) *PrivilegedAccessGroupAssignmentScheduleRequestId { - return &PrivilegedAccessGroupAssignmentScheduleRequestId{ - RequestId: requestId, - } -} - -func ParsePrivilegedAccessGroupAssignmentScheduleRequestID(idString string) (*PrivilegedAccessGroupAssignmentScheduleRequestId, error) { - if _, err := validation.IsUUID(idString, "RequestId"); len(err) > 0 { - return nil, fmt.Errorf("parsing RequestId: %+v", err) - } - - return &PrivilegedAccessGroupAssignmentScheduleRequestId{ - RequestId: idString, - }, nil -} - -func (id *PrivilegedAccessGroupAssignmentScheduleRequestId) ID() string { - return id.RequestId -} - -func (id *PrivilegedAccessGroupAssignmentScheduleRequestId) String() string { - return fmt.Sprintf("Privileged Access Group Assigment Schedule Request ID: %q", id.RequestId) -} diff --git a/internal/services/identitygovernance/parse/privileged_access_group_eligibility_schedule_request.go b/internal/services/identitygovernance/parse/privileged_access_group_eligibility_schedule_request.go deleted file mode 100644 index 65845dacfd..0000000000 --- a/internal/services/identitygovernance/parse/privileged_access_group_eligibility_schedule_request.go +++ /dev/null @@ -1,35 +0,0 @@ -package parse - -import ( - "fmt" - - "github.com/hashicorp/terraform-provider-azuread/internal/tf/validation" -) - -type PrivilegedAccessGroupEligibilityScheduleRequestId struct { - RequestId string -} - -func NewPrivilegedAccessGroupEligibilityScheduleRequestID(requestId string) *PrivilegedAccessGroupEligibilityScheduleRequestId { - return &PrivilegedAccessGroupEligibilityScheduleRequestId{ - RequestId: requestId, - } -} - -func ParsePrivilegedAccessGroupEligibilityScheduleRequestID(idString string) (*PrivilegedAccessGroupEligibilityScheduleRequestId, error) { - if _, err := validation.IsUUID(idString, "RequestId"); len(err) > 0 { - return nil, fmt.Errorf("parsing RequestId: %+v", err) - } - - return &PrivilegedAccessGroupEligibilityScheduleRequestId{ - RequestId: idString, - }, nil -} - -func (id *PrivilegedAccessGroupEligibilityScheduleRequestId) ID() string { - return id.RequestId -} - -func (id *PrivilegedAccessGroupEligibilityScheduleRequestId) String() string { - return fmt.Sprintf("Privileged Access Group Assigment Schedule Request ID: %q", id.RequestId) -} diff --git a/internal/services/identitygovernance/parse/privileged_access_group_schedule.go b/internal/services/identitygovernance/parse/privileged_access_group_schedule.go new file mode 100644 index 0000000000..fce674c9c1 --- /dev/null +++ b/internal/services/identitygovernance/parse/privileged_access_group_schedule.go @@ -0,0 +1,72 @@ +package parse + +import ( + "fmt" + "strings" + + "github.com/hashicorp/terraform-provider-azuread/internal/tf/validation" + "github.com/manicminer/hamilton/msgraph" +) + +type PrivilegedAccessGroupScheduleId struct { + GroupId string + Relationship string + ScheduleId string +} + +func NewPrivilegedAccessGroupScheduleID(groupId, relationship, scheduleId string) *PrivilegedAccessGroupScheduleId { + return &PrivilegedAccessGroupScheduleId{ + GroupId: groupId, + Relationship: relationship, + ScheduleId: scheduleId, + } +} + +func ParsePrivilegedAccessGroupScheduleID(idString string) (*PrivilegedAccessGroupScheduleId, error) { + // Parse the Schedule ID into its parts + parts := strings.Split(idString, "_") + + if len(parts) != 3 { + return nil, fmt.Errorf("parsing GroupScheduleId: expecting 3 parts, got %d", len(parts)) + } + + if _, err := validation.IsUUID(parts[0], "GroupId"); len(err) > 0 { + return nil, fmt.Errorf("parsing GroupScheduleId: %+v", err) + } + + if parts[1] != msgraph.PrivilegedAccessGroupRelationshipOwner && + parts[1] != msgraph.PrivilegedAccessGroupRelationshipMember { + return nil, fmt.Errorf("parsing GroupScheduleId: invalid Relationship") + } + + if _, err := validation.IsUUID(parts[2], "ScheduleId"); len(err) > 0 { + return nil, fmt.Errorf("parsing GroupScheduleId: %+v", err) + } + + id := NewPrivilegedAccessGroupScheduleID(parts[0], parts[1], parts[2]) + + return id, nil +} + +func ValidatePrivilegedAccessGroupScheduleID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + _, err := ParsePrivilegedAccessGroupScheduleID(v) + if err != nil { + errors = append(errors, err) + } + + return +} + +func (id *PrivilegedAccessGroupScheduleId) ID() string { + return strings.Join([]string{id.GroupId, id.Relationship, id.ScheduleId}, "_") +} + +func (id *PrivilegedAccessGroupScheduleId) String() string { + return fmt.Sprintf("Privileged Access Group Assigment Schedule ID: %q", id.ID()) +} diff --git a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go b/internal/services/identitygovernance/privileged_access_group_assignment_schedule.go similarity index 56% rename from internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go rename to internal/services/identitygovernance/privileged_access_group_assignment_schedule.go index 28570fa42e..a567addc12 100644 --- a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request.go +++ b/internal/services/identitygovernance/privileged_access_group_assignment_schedule.go @@ -7,53 +7,52 @@ import ( "context" "fmt" "net/http" - "slices" "time" + "github.com/hashicorp/go-azure-helpers/lang/pointer" "github.com/hashicorp/go-azure-sdk/sdk/odata" "github.com/hashicorp/terraform-provider-azuread/internal/sdk" "github.com/hashicorp/terraform-provider-azuread/internal/services/identitygovernance/parse" "github.com/hashicorp/terraform-provider-azuread/internal/tf/pluginsdk" - "github.com/hashicorp/terraform-provider-azuread/internal/tf/validation" "github.com/manicminer/hamilton/msgraph" ) -type PrivilegedAccessGroupAssignmentScheduleRequestResource struct{} +type PrivilegedAccessGroupAssignmentScheduleResource struct{} -func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { - return validation.IsUUID +func (r PrivilegedAccessGroupAssignmentScheduleResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return parse.ValidatePrivilegedAccessGroupScheduleID } -var _ sdk.Resource = PrivilegedAccessGroupAssignmentScheduleRequestResource{} +var _ sdk.Resource = PrivilegedAccessGroupAssignmentScheduleResource{} -func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) ResourceType() string { - return "azuread_privileged_access_group_assignment_schedule_request" +func (r PrivilegedAccessGroupAssignmentScheduleResource) ResourceType() string { + return "azuread_privileged_access_group_assignment_schedule" } -func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) ModelObject() interface{} { - return &PrivilegedAccessGroupScheduleRequestModel{} +func (r PrivilegedAccessGroupAssignmentScheduleResource) ModelObject() interface{} { + return &PrivilegedAccessGroupScheduleModel{} } -func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Arguments() map[string]*pluginsdk.Schema { - return privilegedAccessGroupScheduleRequestArguments() +func (r PrivilegedAccessGroupAssignmentScheduleResource) Arguments() map[string]*pluginsdk.Schema { + return privilegedAccessGroupScheduleArguments() } -func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Attributes() map[string]*pluginsdk.Schema { - return privilegedAccessGroupScheduleRequestAttributes() +func (r PrivilegedAccessGroupAssignmentScheduleResource) Attributes() map[string]*pluginsdk.Schema { + return privilegedAccessGroupScheduleAttributes() } -func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Create() sdk.ResourceFunc { +func (r PrivilegedAccessGroupAssignmentScheduleResource) Create() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 5 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { client := metadata.Client.IdentityGovernance.PrivilegedAccessGroupAssignmentScheduleRequestsClient - var model PrivilegedAccessGroupScheduleRequestModel + var model PrivilegedAccessGroupScheduleModel if err := metadata.Decode(&model); err != nil { return fmt.Errorf("decoding: %+v", err) } - schedule, err := buildRequestSchedule(&model, &metadata) + schedule, err := buildScheduleRequest(&model, &metadata) if err != nil { return err } @@ -87,7 +86,10 @@ func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Create() sdk.Res return fmt.Errorf("Assignment schedule request is in a failed state") } - id := parse.NewPrivilegedAccessGroupAssignmentScheduleRequestID(*req.ID) + id, err := parse.ParsePrivilegedAccessGroupScheduleID(*req.TargetScheduleId) + if err != nil { + return err + } metadata.SetID(id) return nil @@ -95,27 +97,36 @@ func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Create() sdk.Res } } -func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Read() sdk.ResourceFunc { +func (r PrivilegedAccessGroupAssignmentScheduleResource) Read() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 5 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { - client := metadata.Client.IdentityGovernance.PrivilegedAccessGroupAssignmentScheduleRequestsClient + cSchedule := metadata.Client.IdentityGovernance.PrivilegedAccessGroupAssignmentScheduleClient + cRequests := metadata.Client.IdentityGovernance.PrivilegedAccessGroupAssignmentScheduleRequestsClient + + var request *msgraph.PrivilegedAccessGroupAssignmentScheduleRequest - id, err := parse.ParsePrivilegedAccessGroupAssignmentScheduleRequestID(metadata.ResourceData.Id()) + id, err := parse.ParsePrivilegedAccessGroupScheduleID(metadata.ResourceData.Id()) if err != nil { return err } - var model PrivilegedAccessGroupScheduleRequestModel + var model PrivilegedAccessGroupScheduleModel if err := metadata.Decode(&model); err != nil { return fmt.Errorf("decoding: %+v", err) } + schedule, status, err := cSchedule.Get(ctx, id.ID()) + if err != nil && status != http.StatusNotFound { + return fmt.Errorf("retrieving %s: %+v", id, err) + } + + // Some details are only available on the request which is used for the create/update of the schedule. // Schedule requests are never deleted. New ones are created when changes are made. // Therefore on a read, we need to find the latest version of the request. // This is to cater for changes being made outside of Terraform. - requests, _, err := client.List(ctx, odata.Query{ - Filter: fmt.Sprintf("groupId eq '%s' and principalId eq '%s'", model.GroupId, model.PrincipalId), + requests, _, err := cRequests.List(ctx, odata.Query{ + Filter: fmt.Sprintf("groupId eq '%s' and targetScheduleId eq '%s'", id.GroupId, id.ID()), OrderBy: odata.OrderBy{ Field: "createdDateTime", Direction: odata.Descending, @@ -125,63 +136,111 @@ func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Read() sdk.Resou return fmt.Errorf("listing requests: %+v", err) } if len(*requests) == 0 { - return metadata.MarkAsGone(id) + if status == http.StatusNotFound { + return metadata.MarkAsGone(id) + } + } else { + request = pointer.To((*requests)[0]) + + model.Justification = *request.Justification + if request.TicketInfo.TicketNumber != nil { + model.TicketNumber = *request.TicketInfo.TicketNumber + } + if request.TicketInfo.TicketSystem != nil { + model.TicketSystem = *request.TicketInfo.TicketSystem + } + if request.ScheduleInfo.Expiration.Duration != nil { + model.Duration = *request.ScheduleInfo.Expiration.Duration + } } - request := (*requests)[0] - if slices.Contains([]string{ - msgraph.PrivilegedAccessGroupAssignmentStatusCanceled, - msgraph.PrivilegedAccessGroupAssignmentStatusRevoked, - }, request.Status) { - metadata.MarkAsGone(id) - } else { + // Typically this is because the request has expired + // So we populate the model with the schedule details + if status == http.StatusNotFound { model.AssignmentType = request.AccessId + model.ExpirationDate = request.ScheduleInfo.Expiration.EndDateTime.Format(time.RFC3339) model.GroupId = *request.GroupId - model.Justification = *request.Justification model.PermanentAssignment = *request.ScheduleInfo.Expiration.Type == msgraph.ExpirationPatternTypeNoExpiration model.PrincipalId = *request.PrincipalId model.StartDate = request.ScheduleInfo.StartDateTime.Format(time.RFC3339) model.Status = request.Status - model.TargetScheduleId = *request.TargetScheduleId + } else { + model.AssignmentType = schedule.AccessId + model.ExpirationDate = schedule.ScheduleInfo.Expiration.EndDateTime.Format(time.RFC3339) + model.GroupId = *schedule.GroupId + model.PermanentAssignment = *schedule.ScheduleInfo.Expiration.Type == msgraph.ExpirationPatternTypeNoExpiration + model.PrincipalId = *schedule.PrincipalId + model.StartDate = schedule.ScheduleInfo.StartDateTime.Format(time.RFC3339) + model.Status = schedule.Status + } - if request.ScheduleInfo.Expiration.EndDateTime != nil { - model.ExpirationDate = request.ScheduleInfo.Expiration.EndDateTime.Format(time.RFC3339) - } - if request.ScheduleInfo.Expiration.Duration != nil { - model.Duration = *request.ScheduleInfo.Expiration.Duration - } + return metadata.Encode(&model) + }, + } +} - if request.TicketInfo.TicketNumber != nil { - model.TicketNumber = *request.TicketInfo.TicketNumber - } - if request.TicketInfo.TicketSystem != nil { - model.TicketSystem = *request.TicketInfo.TicketSystem - } +func (r PrivilegedAccessGroupAssignmentScheduleResource) Update() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.IdentityGovernance.PrivilegedAccessGroupAssignmentScheduleRequestsClient + + var model PrivilegedAccessGroupScheduleModel + if err := metadata.Decode(&model); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + schedule, err := buildScheduleRequest(&model, &metadata) + if err != nil { + return err + } + + properties := msgraph.PrivilegedAccessGroupAssignmentScheduleRequest{ + AccessId: model.AssignmentType, + PrincipalId: &model.PrincipalId, + GroupId: &model.GroupId, + Action: msgraph.PrivilegedAccessGroupActionAdminAssign, + Justification: &model.Justification, + ScheduleInfo: schedule, + } - // Update the ID if it has changed - if *request.ID != id.ID() { - id = parse.NewPrivilegedAccessGroupAssignmentScheduleRequestID(*request.ID) - metadata.SetID(id) + if model.TicketNumber != "" || model.TicketSystem != "" { + properties.TicketInfo = &msgraph.TicketInfo{ + TicketNumber: &model.TicketNumber, + TicketSystem: &model.TicketSystem, } } - return metadata.Encode(&model) + req, _, err := client.Create(ctx, properties) + if err != nil { + return fmt.Errorf("Could not create assignment schedule request, %+v", err) + } + + if req.ID == nil || *req.ID == "" { + return fmt.Errorf("ID returned for assignment schedule request is nil/empty") + } + + if req.Status == msgraph.PrivilegedAccessGroupAssignmentStatusFailed { + return fmt.Errorf("Assignment schedule request is in a failed state") + } + + return nil }, } } -func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Delete() sdk.ResourceFunc { +func (r PrivilegedAccessGroupAssignmentScheduleResource) Delete() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 5 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { client := metadata.Client.IdentityGovernance.PrivilegedAccessGroupAssignmentScheduleRequestsClient - id, err := parse.ParsePrivilegedAccessGroupAssignmentScheduleRequestID(metadata.ResourceData.Id()) + id, err := parse.ParsePrivilegedAccessGroupScheduleID(metadata.ResourceData.Id()) if err != nil { return err } - var model PrivilegedAccessGroupScheduleRequestModel + var model PrivilegedAccessGroupScheduleModel if err := metadata.Decode(&model); err != nil { return fmt.Errorf("decoding: %+v", err) } @@ -216,8 +275,8 @@ func (r PrivilegedAccessGroupAssignmentScheduleRequestResource) Delete() sdk.Res } } -func cancelAssignmentRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupAssignmentScheduleRequestsClient, id *parse.PrivilegedAccessGroupAssignmentScheduleRequestId) error { - status, err := client.Cancel(ctx, id.RequestId) +func cancelAssignmentRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupAssignmentScheduleRequestsClient, id *parse.PrivilegedAccessGroupScheduleId) error { + status, err := client.Cancel(ctx, id.ID()) if err != nil { if status == http.StatusNotFound { return metadata.MarkAsGone(id) @@ -227,9 +286,9 @@ func cancelAssignmentRequest(ctx context.Context, metadata sdk.ResourceMetaData, return metadata.MarkAsGone(id) } -func revokeAssignmentRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupAssignmentScheduleRequestsClient, id *parse.PrivilegedAccessGroupAssignmentScheduleRequestId, model *PrivilegedAccessGroupScheduleRequestModel) error { +func revokeAssignmentRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupAssignmentScheduleRequestsClient, id *parse.PrivilegedAccessGroupScheduleId, model *PrivilegedAccessGroupScheduleModel) error { result, status, err := client.Create(ctx, msgraph.PrivilegedAccessGroupAssignmentScheduleRequest{ - ID: &id.RequestId, + ID: pointer.To(id.ID()), AccessId: model.AssignmentType, PrincipalId: &model.PrincipalId, GroupId: &model.GroupId, diff --git a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request_test.go b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_test.go similarity index 61% rename from internal/services/identitygovernance/privileged_access_group_assignment_schedule_request_test.go rename to internal/services/identitygovernance/privileged_access_group_assignment_schedule_test.go index 1b6427e4a1..31a9fc573c 100644 --- a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_request_test.go +++ b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_test.go @@ -7,7 +7,6 @@ import ( "context" "fmt" "net/http" - "slices" "testing" "time" @@ -17,14 +16,14 @@ import ( "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/identitygovernance/helpers" - "github.com/manicminer/hamilton/msgraph" + "github.com/hashicorp/terraform-provider-azuread/internal/services/identitygovernance/parse" ) -type PrivilegedAccessGroupAssignmentScheduleRequestResource struct{} +type PrivilegedAccessGroupAssignmentScheduleResource struct{} -func TestPrivilegedAccessGroupAssignmentScheduleRequest_member(t *testing.T) { - data := acceptance.BuildTestData(t, "azuread_privileged_access_group_assignment_schedule_request", "member") - r := PrivilegedAccessGroupAssignmentScheduleRequestResource{} +func TestPrivilegedAccessGroupAssignmentSchedule_member(t *testing.T) { + data := acceptance.BuildTestData(t, "azuread_privileged_access_group_assignment_schedule", "member") + r := PrivilegedAccessGroupAssignmentScheduleResource{} endTime := time.Now().AddDate(0, 2, 0).UTC() @@ -36,15 +35,16 @@ func TestPrivilegedAccessGroupAssignmentScheduleRequest_member(t *testing.T) { // There is a minimum life of 5 minutes for a schedule request to exist. // Attempting to delete the request within this time frame will result in // a 400 error on destroy, which we can't trap. - helpers.SleepCheck(5*time.Minute+1*time.Second), + helpers.SleepCheck(5*time.Minute+15*time.Second), ), }, + data.ImportStep(), }) } -func TestPrivilegedAccessGroupAssignmentScheduleRequest_owner(t *testing.T) { - data := acceptance.BuildTestData(t, "azuread_privileged_access_group_assignment_schedule_request", "owner") - r := PrivilegedAccessGroupAssignmentScheduleRequestResource{} +func TestPrivilegedAccessGroupAssignmentSchedule_owner(t *testing.T) { + data := acceptance.BuildTestData(t, "azuread_privileged_access_group_assignment_schedule", "owner") + r := PrivilegedAccessGroupAssignmentScheduleResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { @@ -54,43 +54,40 @@ func TestPrivilegedAccessGroupAssignmentScheduleRequest_owner(t *testing.T) { // There is a minimum life of 5 minutes for a schedule request to exist. // Attempting to delete the request within this time frame will result in // a 400 error on destroy, which we can't trap. - helpers.SleepCheck(5*time.Minute+1*time.Second), + helpers.SleepCheck(5*time.Minute+15*time.Second), ), }, + data.ImportStep(), }) } -func (PrivilegedAccessGroupAssignmentScheduleRequestResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { - client := clients.IdentityGovernance.PrivilegedAccessGroupAssignmentScheduleRequestsClient +func (PrivilegedAccessGroupAssignmentScheduleResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { + client := clients.IdentityGovernance.PrivilegedAccessGroupAssignmentScheduleClient client.BaseClient.DisableRetries = true defer func() { client.BaseClient.DisableRetries = false }() - request, status, err := client.Get(ctx, state.ID) + id, err := parse.ParsePrivilegedAccessGroupScheduleID(state.ID) + if err != nil { + return nil, fmt.Errorf("failed to parse privileged group assignment schedule ID %q: %+v", state.ID, err) + } + + _, status, err := client.Get(ctx, id.ID()) if err != nil { if status == http.StatusNotFound { return pointer.To(false), nil } - return nil, fmt.Errorf("failed to retrieve privileged group assignment schedule request with ID %q: %+v", state.ID, err) - } - - // Requests are not deleted, but marked as canceled or revoked. - if slices.Contains([]string{ - msgraph.PrivilegedAccessGroupAssignmentStatusCanceled, - msgraph.PrivilegedAccessGroupAssignmentStatusRevoked, - }, request.Status) { - return pointer.To(false), nil - } else { - return pointer.To(true), nil + return nil, fmt.Errorf("failed to retrieve privileged group assignment schedule with ID %q: %+v", id.ID(), err) } + return pointer.To(true), nil } -func (PrivilegedAccessGroupAssignmentScheduleRequestResource) member(data acceptance.TestData, endTime time.Time) string { +func (PrivilegedAccessGroupAssignmentScheduleResource) member(data acceptance.TestData, endTime time.Time) string { return fmt.Sprintf(` provider "azuread" {} resource "azuread_group" "pam" { - display_name = "Privileged %[1]s" + display_name = "Privileged Assignment %[1]s" mail_enabled = false security_enabled = true } @@ -105,7 +102,7 @@ resource "azuread_user" "member" { password = "%[2]s" } -resource "azuread_privileged_access_group_assignment_schedule_request" "member" { +resource "azuread_privileged_access_group_assignment_schedule" "member" { group_id = azuread_group.pam.id principal_id = azuread_user.member.id assignment_type = "member" @@ -115,7 +112,7 @@ resource "azuread_privileged_access_group_assignment_schedule_request" "member" `, data.RandomString, data.RandomPassword, endTime.Format(time.RFC3339)) } -func (PrivilegedAccessGroupAssignmentScheduleRequestResource) owner(data acceptance.TestData) string { +func (PrivilegedAccessGroupAssignmentScheduleResource) owner(data acceptance.TestData) string { return fmt.Sprintf(` provider "azuread" {} @@ -124,13 +121,13 @@ data "azuread_domains" "test" { } resource "azuread_user" "manual_owner" { - user_principal_name = "pam-owner-manual-%[1]s@${data.azuread_domains.test.domains.0.domain_name}" + user_principal_name = "pam-eligible-owner-manual-%[1]s@${data.azuread_domains.test.domains.0.domain_name}" display_name = "PAM Owner (Manual) %[1]s" password = "%[2]s" } resource "azuread_group" "pam" { - display_name = "Privileged %[1]s" + display_name = "Privileged Assignment %[1]s" mail_enabled = false security_enabled = true @@ -141,16 +138,15 @@ resource "azuread_group" "pam" { } } -resource "azuread_user" "assigned_owner" { - user_principal_name = "pam-owner-assigned-%[1]s@${data.azuread_domains.test.domains.0.domain_name}" - display_name = "PAM Owner (Assigned) %[1]s" +resource "azuread_user" "eligibile_owner" { + user_principal_name = "pam-eligible-owner-eligible-%[1]s@${data.azuread_domains.test.domains.0.domain_name}" + display_name = "PAM Owner (Eligible) %[1]s" password = "%[2]s" } - -resource "azuread_privileged_access_group_assignment_schedule_request" "owner" { +resource "azuread_privileged_access_group_assignment_schedule" "owner" { group_id = azuread_group.pam.id - principal_id = azuread_user.assigned_owner.id + principal_id = azuread_user.eligibile_owner.id assignment_type = "owner" duration = "P30D" justification = "required" diff --git a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule.go similarity index 56% rename from internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go rename to internal/services/identitygovernance/privileged_access_group_eligiblity_schedule.go index b794c591dc..5a8a0ecf4b 100644 --- a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request.go +++ b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule.go @@ -7,53 +7,52 @@ import ( "context" "fmt" "net/http" - "slices" "time" + "github.com/hashicorp/go-azure-helpers/lang/pointer" "github.com/hashicorp/go-azure-sdk/sdk/odata" "github.com/hashicorp/terraform-provider-azuread/internal/sdk" "github.com/hashicorp/terraform-provider-azuread/internal/services/identitygovernance/parse" "github.com/hashicorp/terraform-provider-azuread/internal/tf/pluginsdk" - "github.com/hashicorp/terraform-provider-azuread/internal/tf/validation" "github.com/manicminer/hamilton/msgraph" ) -type PrivilegedAccessGroupEligibilityScheduleRequestResource struct{} +type PrivilegedAccessGroupEligibilityScheduleResource struct{} -func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { - return validation.IsUUID +func (r PrivilegedAccessGroupEligibilityScheduleResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return parse.ValidatePrivilegedAccessGroupScheduleID } -var _ sdk.Resource = PrivilegedAccessGroupEligibilityScheduleRequestResource{} +var _ sdk.Resource = PrivilegedAccessGroupEligibilityScheduleResource{} -func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) ResourceType() string { - return "azuread_privileged_access_group_eligibility_schedule_request" +func (r PrivilegedAccessGroupEligibilityScheduleResource) ResourceType() string { + return "azuread_privileged_access_group_eligibility_schedule" } -func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) ModelObject() interface{} { - return &PrivilegedAccessGroupScheduleRequestModel{} +func (r PrivilegedAccessGroupEligibilityScheduleResource) ModelObject() interface{} { + return &PrivilegedAccessGroupScheduleModel{} } -func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Arguments() map[string]*pluginsdk.Schema { - return privilegedAccessGroupScheduleRequestArguments() +func (r PrivilegedAccessGroupEligibilityScheduleResource) Arguments() map[string]*pluginsdk.Schema { + return privilegedAccessGroupScheduleArguments() } -func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Attributes() map[string]*pluginsdk.Schema { - return privilegedAccessGroupScheduleRequestAttributes() +func (r PrivilegedAccessGroupEligibilityScheduleResource) Attributes() map[string]*pluginsdk.Schema { + return privilegedAccessGroupScheduleAttributes() } -func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Create() sdk.ResourceFunc { +func (r PrivilegedAccessGroupEligibilityScheduleResource) Create() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 5 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { client := metadata.Client.IdentityGovernance.PrivilegedAccessGroupEligibilityScheduleRequestsClient - var model PrivilegedAccessGroupScheduleRequestModel + var model PrivilegedAccessGroupScheduleModel if err := metadata.Decode(&model); err != nil { return fmt.Errorf("decoding: %+v", err) } - schedule, err := buildRequestSchedule(&model, &metadata) + schedule, err := buildScheduleRequest(&model, &metadata) if err != nil { return err } @@ -87,7 +86,10 @@ func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Create() sdk.Re return fmt.Errorf("Assignment schedule request is in a failed state") } - id := parse.NewPrivilegedAccessGroupEligibilityScheduleRequestID(*req.ID) + id, err := parse.ParsePrivilegedAccessGroupScheduleID(*req.TargetScheduleId) + if err != nil { + return err + } metadata.SetID(id) return nil @@ -95,27 +97,36 @@ func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Create() sdk.Re } } -func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Read() sdk.ResourceFunc { +func (r PrivilegedAccessGroupEligibilityScheduleResource) Read() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 5 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { - client := metadata.Client.IdentityGovernance.PrivilegedAccessGroupEligibilityScheduleRequestsClient + cSchedule := metadata.Client.IdentityGovernance.PrivilegedAccessGroupEligibilityScheduleClient + cRequests := metadata.Client.IdentityGovernance.PrivilegedAccessGroupEligibilityScheduleRequestsClient + + var request *msgraph.PrivilegedAccessGroupEligibilityScheduleRequest - id, err := parse.ParsePrivilegedAccessGroupEligibilityScheduleRequestID(metadata.ResourceData.Id()) + id, err := parse.ParsePrivilegedAccessGroupScheduleID(metadata.ResourceData.Id()) if err != nil { return err } - var model PrivilegedAccessGroupScheduleRequestModel + var model PrivilegedAccessGroupScheduleModel if err := metadata.Decode(&model); err != nil { return fmt.Errorf("decoding: %+v", err) } + schedule, status, err := cSchedule.Get(ctx, id.ID()) + if err != nil && status != http.StatusNotFound { + return fmt.Errorf("retrieving %s: %+v", id, err) + } + + // Some details are only available on the request which is used for the create/update of the schedule. // Schedule requests are never deleted. New ones are created when changes are made. // Therefore on a read, we need to find the latest version of the request. // This is to cater for changes being made outside of Terraform. - requests, _, err := client.List(ctx, odata.Query{ - Filter: fmt.Sprintf("groupId eq '%s' and principalId eq '%s'", model.GroupId, model.PrincipalId), + requests, _, err := cRequests.List(ctx, odata.Query{ + Filter: fmt.Sprintf("groupId eq '%s' and targetScheduleId eq '%s'", id.GroupId, id.ID()), OrderBy: odata.OrderBy{ Field: "createdDateTime", Direction: odata.Descending, @@ -125,63 +136,111 @@ func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Read() sdk.Reso return fmt.Errorf("listing requests: %+v", err) } if len(*requests) == 0 { - return metadata.MarkAsGone(id) + if status == http.StatusNotFound { + return metadata.MarkAsGone(id) + } + } else { + request = pointer.To((*requests)[0]) + + model.Justification = *request.Justification + if request.TicketInfo.TicketNumber != nil { + model.TicketNumber = *request.TicketInfo.TicketNumber + } + if request.TicketInfo.TicketSystem != nil { + model.TicketSystem = *request.TicketInfo.TicketSystem + } + if request.ScheduleInfo.Expiration.Duration != nil { + model.Duration = *request.ScheduleInfo.Expiration.Duration + } } - request := (*requests)[0] - if slices.Contains([]string{ - msgraph.PrivilegedAccessGroupAssignmentStatusCanceled, - msgraph.PrivilegedAccessGroupAssignmentStatusRevoked, - }, request.Status) { - metadata.MarkAsGone(id) - } else { + // Typically this is because the request has expired + // So we populate the model with the schedule details + if status == http.StatusNotFound { model.AssignmentType = request.AccessId + model.ExpirationDate = request.ScheduleInfo.Expiration.EndDateTime.Format(time.RFC3339) model.GroupId = *request.GroupId - model.Justification = *request.Justification model.PermanentAssignment = *request.ScheduleInfo.Expiration.Type == msgraph.ExpirationPatternTypeNoExpiration model.PrincipalId = *request.PrincipalId model.StartDate = request.ScheduleInfo.StartDateTime.Format(time.RFC3339) model.Status = request.Status - model.TargetScheduleId = *request.TargetScheduleId + } else { + model.AssignmentType = schedule.AccessId + model.ExpirationDate = schedule.ScheduleInfo.Expiration.EndDateTime.Format(time.RFC3339) + model.GroupId = *schedule.GroupId + model.PermanentAssignment = *schedule.ScheduleInfo.Expiration.Type == msgraph.ExpirationPatternTypeNoExpiration + model.PrincipalId = *schedule.PrincipalId + model.StartDate = schedule.ScheduleInfo.StartDateTime.Format(time.RFC3339) + model.Status = schedule.Status + } - if request.ScheduleInfo.Expiration.EndDateTime != nil { - model.ExpirationDate = request.ScheduleInfo.Expiration.EndDateTime.Format(time.RFC3339) - } - if request.ScheduleInfo.Expiration.Duration != nil { - model.Duration = *request.ScheduleInfo.Expiration.Duration - } + return metadata.Encode(&model) + }, + } +} - if request.TicketInfo.TicketNumber != nil { - model.TicketNumber = *request.TicketInfo.TicketNumber - } - if request.TicketInfo.TicketSystem != nil { - model.TicketSystem = *request.TicketInfo.TicketSystem - } +func (r PrivilegedAccessGroupEligibilityScheduleResource) Update() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.IdentityGovernance.PrivilegedAccessGroupEligibilityScheduleRequestsClient + + var model PrivilegedAccessGroupScheduleModel + if err := metadata.Decode(&model); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + schedule, err := buildScheduleRequest(&model, &metadata) + if err != nil { + return err + } + + properties := msgraph.PrivilegedAccessGroupEligibilityScheduleRequest{ + AccessId: model.AssignmentType, + PrincipalId: &model.PrincipalId, + GroupId: &model.GroupId, + Action: msgraph.PrivilegedAccessGroupActionAdminAssign, + Justification: &model.Justification, + ScheduleInfo: schedule, + } - // Update the ID if it has changed - if *request.ID != id.ID() { - id = parse.NewPrivilegedAccessGroupEligibilityScheduleRequestID(*request.ID) - metadata.SetID(id) + if model.TicketNumber != "" || model.TicketSystem != "" { + properties.TicketInfo = &msgraph.TicketInfo{ + TicketNumber: &model.TicketNumber, + TicketSystem: &model.TicketSystem, } } - return metadata.Encode(&model) + req, _, err := client.Create(ctx, properties) + if err != nil { + return fmt.Errorf("Could not create assignment schedule request, %+v", err) + } + + if req.ID == nil || *req.ID == "" { + return fmt.Errorf("ID returned for assignment schedule request is nil/empty") + } + + if req.Status == msgraph.PrivilegedAccessGroupEligibilityStatusFailed { + return fmt.Errorf("Assignment schedule request is in a failed state") + } + + return nil }, } } -func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Delete() sdk.ResourceFunc { +func (r PrivilegedAccessGroupEligibilityScheduleResource) Delete() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 5 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { client := metadata.Client.IdentityGovernance.PrivilegedAccessGroupEligibilityScheduleRequestsClient - id, err := parse.ParsePrivilegedAccessGroupEligibilityScheduleRequestID(metadata.ResourceData.Id()) + id, err := parse.ParsePrivilegedAccessGroupScheduleID(metadata.ResourceData.Id()) if err != nil { return err } - var model PrivilegedAccessGroupScheduleRequestModel + var model PrivilegedAccessGroupScheduleModel if err := metadata.Decode(&model); err != nil { return fmt.Errorf("decoding: %+v", err) } @@ -216,8 +275,8 @@ func (r PrivilegedAccessGroupEligibilityScheduleRequestResource) Delete() sdk.Re } } -func cancelEligibilityRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupEligibilityScheduleRequestsClient, id *parse.PrivilegedAccessGroupEligibilityScheduleRequestId) error { - status, err := client.Cancel(ctx, id.RequestId) +func cancelEligibilityRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupEligibilityScheduleRequestsClient, id *parse.PrivilegedAccessGroupScheduleId) error { + status, err := client.Cancel(ctx, id.ID()) if err != nil { if status == http.StatusNotFound { return metadata.MarkAsGone(id) @@ -227,9 +286,9 @@ func cancelEligibilityRequest(ctx context.Context, metadata sdk.ResourceMetaData return metadata.MarkAsGone(id) } -func revokeEligibilityRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupEligibilityScheduleRequestsClient, id *parse.PrivilegedAccessGroupEligibilityScheduleRequestId, model *PrivilegedAccessGroupScheduleRequestModel) error { +func revokeEligibilityRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupEligibilityScheduleRequestsClient, id *parse.PrivilegedAccessGroupScheduleId, model *PrivilegedAccessGroupScheduleModel) error { result, status, err := client.Create(ctx, msgraph.PrivilegedAccessGroupEligibilityScheduleRequest{ - ID: &id.RequestId, + ID: pointer.To(id.ID()), AccessId: model.AssignmentType, PrincipalId: &model.PrincipalId, GroupId: &model.GroupId, diff --git a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request_test.go b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_test.go similarity index 69% rename from internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request_test.go rename to internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_test.go index c7b81649c0..6ff94039a3 100644 --- a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_request_test.go +++ b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_test.go @@ -7,7 +7,6 @@ import ( "context" "fmt" "net/http" - "slices" "testing" "time" @@ -17,14 +16,14 @@ import ( "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/identitygovernance/helpers" - "github.com/manicminer/hamilton/msgraph" + "github.com/hashicorp/terraform-provider-azuread/internal/services/identitygovernance/parse" ) -type PrivilegedAccessGroupEligiblityScheduleRequestResource struct{} +type PrivilegedAccessGroupEligibilityScheduleResource struct{} -func TestPrivilegedAccessGroupEligiblityScheduleRequest_member(t *testing.T) { - data := acceptance.BuildTestData(t, "azuread_privileged_access_group_eligibility_schedule_request", "member") - r := PrivilegedAccessGroupEligiblityScheduleRequestResource{} +func TestPrivilegedAccessGroupEligibilitySchedule_member(t *testing.T) { + data := acceptance.BuildTestData(t, "azuread_privileged_access_group_eligibility_schedule", "member") + r := PrivilegedAccessGroupEligibilityScheduleResource{} endTime := time.Now().AddDate(0, 2, 0).UTC() @@ -36,15 +35,16 @@ func TestPrivilegedAccessGroupEligiblityScheduleRequest_member(t *testing.T) { // There is a minimum life of 5 minutes for a schedule request to exist. // Attempting to delete the request within this time frame will result in // a 400 error on destroy, which we can't trap. - helpers.SleepCheck(5*time.Minute+1*time.Second), + helpers.SleepCheck(5*time.Minute+15*time.Second), ), }, + data.ImportStep(), }) } -func TestPrivilegedAccessGroupEligiblityScheduleRequest_owner(t *testing.T) { - data := acceptance.BuildTestData(t, "azuread_privileged_access_group_eligibility_schedule_request", "owner") - r := PrivilegedAccessGroupEligiblityScheduleRequestResource{} +func TestPrivilegedAccessGroupEligibilitySchedule_owner(t *testing.T) { + data := acceptance.BuildTestData(t, "azuread_privileged_access_group_eligibility_schedule", "owner") + r := PrivilegedAccessGroupEligibilityScheduleResource{} data.ResourceTest(t, r, []acceptance.TestStep{ { @@ -54,38 +54,34 @@ func TestPrivilegedAccessGroupEligiblityScheduleRequest_owner(t *testing.T) { // There is a minimum life of 5 minutes for a schedule request to exist. // Attempting to delete the request within this time frame will result in // a 400 error on destroy, which we can't trap. - helpers.SleepCheck(5*time.Minute+1*time.Second), + helpers.SleepCheck(5*time.Minute+15*time.Second), ), }, + data.ImportStep(), }) - } -func (PrivilegedAccessGroupEligiblityScheduleRequestResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { - client := clients.IdentityGovernance.PrivilegedAccessGroupEligibilityScheduleRequestsClient +func (PrivilegedAccessGroupEligibilityScheduleResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { + client := clients.IdentityGovernance.PrivilegedAccessGroupEligibilityScheduleClient client.BaseClient.DisableRetries = true defer func() { client.BaseClient.DisableRetries = false }() - request, status, err := client.Get(ctx, state.ID) + id, err := parse.ParsePrivilegedAccessGroupScheduleID(state.ID) + if err != nil { + return nil, fmt.Errorf("failed to parse privileged group assignment schedule ID %q: %+v", state.ID, err) + } + + _, status, err := client.Get(ctx, id.ID()) if err != nil { if status == http.StatusNotFound { return pointer.To(false), nil } - return nil, fmt.Errorf("failed to retrieve privileged group assignment schedule request with ID %q: %+v", state.ID, err) - } - - // Requests are not deleted, but marked as canceled or revoked. - if slices.Contains([]string{ - msgraph.PrivilegedAccessGroupEligibilityStatusCanceled, - msgraph.PrivilegedAccessGroupEligibilityStatusRevoked, - }, request.Status) { - return pointer.To(false), nil - } else { - return pointer.To(true), nil + return nil, fmt.Errorf("failed to retrieve privileged group assignment schedule request with ID %q: %+v", id.ID(), err) } + return pointer.To(true), nil } -func (PrivilegedAccessGroupEligiblityScheduleRequestResource) member(data acceptance.TestData, endTime time.Time) string { +func (PrivilegedAccessGroupEligibilityScheduleResource) member(data acceptance.TestData, endTime time.Time) string { return fmt.Sprintf(` provider "azuread" {} @@ -105,7 +101,7 @@ resource "azuread_user" "member" { password = "%[2]s" } -resource "azuread_privileged_access_group_eligibility_schedule_request" "member" { +resource "azuread_privileged_access_group_eligibility_schedule" "member" { group_id = azuread_group.pam.id principal_id = azuread_user.member.id assignment_type = "member" @@ -115,7 +111,7 @@ resource "azuread_privileged_access_group_eligibility_schedule_request" "member" `, data.RandomString, data.RandomPassword, endTime.Format(time.RFC3339)) } -func (PrivilegedAccessGroupEligiblityScheduleRequestResource) owner(data acceptance.TestData) string { +func (PrivilegedAccessGroupEligibilityScheduleResource) owner(data acceptance.TestData) string { return fmt.Sprintf(` provider "azuread" {} @@ -148,7 +144,7 @@ resource "azuread_user" "eligibile_owner" { } -resource "azuread_privileged_access_group_eligibility_schedule_request" "owner" { +resource "azuread_privileged_access_group_eligibility_schedule" "owner" { group_id = azuread_group.pam.id principal_id = azuread_user.eligibile_owner.id assignment_type = "owner" diff --git a/internal/services/identitygovernance/privileged_access_group_schedule_request.go b/internal/services/identitygovernance/privileged_access_group_schedule.go similarity index 87% rename from internal/services/identitygovernance/privileged_access_group_schedule_request.go rename to internal/services/identitygovernance/privileged_access_group_schedule.go index ba74b896dd..6b277d94d2 100644 --- a/internal/services/identitygovernance/privileged_access_group_schedule_request.go +++ b/internal/services/identitygovernance/privileged_access_group_schedule.go @@ -14,7 +14,7 @@ import ( "github.com/manicminer/hamilton/msgraph" ) -type PrivilegedAccessGroupScheduleRequestModel struct { +type PrivilegedAccessGroupScheduleModel struct { AssignmentType string `tfschema:"assignment_type"` Duration string `tfschema:"duration"` ExpirationDate string `tfschema:"expiration_date"` @@ -24,12 +24,11 @@ type PrivilegedAccessGroupScheduleRequestModel struct { PrincipalId string `tfschema:"principal_id"` StartDate string `tfschema:"start_date"` Status string `tfschema:"status"` - TargetScheduleId string `tfschema:"target_schedule_id"` TicketNumber string `tfschema:"ticket_number"` TicketSystem string `tfschema:"ticket_system"` } -func privilegedAccessGroupScheduleRequestArguments() map[string]*pluginsdk.Schema { +func privilegedAccessGroupScheduleArguments() map[string]*pluginsdk.Schema { return map[string]*pluginsdk.Schema{ "group_id": { Description: "The ID of the Group representing the scope of the assignment", @@ -55,7 +54,6 @@ func privilegedAccessGroupScheduleRequestArguments() map[string]*pluginsdk.Schem ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ msgraph.PrivilegedAccessGroupRelationshipMember, msgraph.PrivilegedAccessGroupRelationshipOwner, - msgraph.PrivilegedAccessGroupRelationshipUnknown, }, false)), }, @@ -63,7 +61,6 @@ func privilegedAccessGroupScheduleRequestArguments() map[string]*pluginsdk.Schem Description: "The date that this assignment starts, formatted as an RFC3339 date string in UTC (e.g. 2018-01-01T01:02:03Z)", Type: pluginsdk.TypeString, Optional: true, - ForceNew: true, Computed: true, ValidateDiagFunc: validation.ValidateDiag(validation.IsRFC3339Time), DiffSuppressOnRefresh: true, @@ -87,7 +84,7 @@ func privilegedAccessGroupScheduleRequestArguments() map[string]*pluginsdk.Schem Description: "The date that this assignment expires, formatted as an RFC3339 date string in UTC (e.g. 2018-01-01T01:02:03Z)", Type: pluginsdk.TypeString, Optional: true, - ForceNew: true, + Computed: true, ConflictsWith: []string{"duration"}, ValidateDiagFunc: validation.ValidateDiag(validation.IsRFC3339Time), }, @@ -96,7 +93,6 @@ func privilegedAccessGroupScheduleRequestArguments() map[string]*pluginsdk.Schem Description: "The duration of the assignment, formatted as an ISO8601 duration string (e.g. P3D for 3 days)", Type: pluginsdk.TypeString, Optional: true, - ForceNew: true, ConflictsWith: []string{"expiration_date"}, ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), }, @@ -105,7 +101,6 @@ func privilegedAccessGroupScheduleRequestArguments() map[string]*pluginsdk.Schem Description: "Is the assignment permanent", Type: pluginsdk.TypeBool, Optional: true, - ForceNew: true, Computed: true, }, @@ -113,7 +108,6 @@ func privilegedAccessGroupScheduleRequestArguments() map[string]*pluginsdk.Schem Description: "The justification for the assignment", Type: pluginsdk.TypeString, Optional: true, - ForceNew: true, ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), }, @@ -121,7 +115,6 @@ func privilegedAccessGroupScheduleRequestArguments() map[string]*pluginsdk.Schem Description: "The ticket number authorising the assignment", Type: pluginsdk.TypeString, Optional: true, - ForceNew: true, RequiredWith: []string{"ticket_system"}, ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), }, @@ -130,30 +123,23 @@ func privilegedAccessGroupScheduleRequestArguments() map[string]*pluginsdk.Schem Description: "The ticket system authorising the assignment", Type: pluginsdk.TypeString, Optional: true, - ForceNew: true, RequiredWith: []string{"ticket_number"}, ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), }, } } -func privilegedAccessGroupScheduleRequestAttributes() map[string]*pluginsdk.Schema { +func privilegedAccessGroupScheduleAttributes() map[string]*pluginsdk.Schema { return map[string]*pluginsdk.Schema{ "status": { - Description: "The status of the Schedule Request", - Type: pluginsdk.TypeString, - Computed: true, - }, - - "target_schedule_id": { - Description: "The ID of the Schedule targeted by the request", + Description: "The status of the schedule", Type: pluginsdk.TypeString, Computed: true, }, } } -func buildRequestSchedule(model *PrivilegedAccessGroupScheduleRequestModel, metadata *sdk.ResourceMetaData) (*msgraph.RequestSchedule, error) { +func buildScheduleRequest(model *PrivilegedAccessGroupScheduleModel, metadata *sdk.ResourceMetaData) (*msgraph.RequestSchedule, error) { schedule := msgraph.RequestSchedule{} schedule.Expiration = &msgraph.ExpirationPattern{} var startDate, expiryDate time.Time diff --git a/internal/services/identitygovernance/registration.go b/internal/services/identitygovernance/registration.go index 5e63598d66..1ad6f3916b 100644 --- a/internal/services/identitygovernance/registration.go +++ b/internal/services/identitygovernance/registration.go @@ -56,7 +56,7 @@ func (r Registration) DataSources() []sdk.DataSource { // Resources returns the typed Resources supported by this service func (r Registration) Resources() []sdk.Resource { return []sdk.Resource{ - PrivilegedAccessGroupAssignmentScheduleRequestResource{}, - PrivilegedAccessGroupEligibilityScheduleRequestResource{}, + PrivilegedAccessGroupAssignmentScheduleResource{}, + PrivilegedAccessGroupEligibilityScheduleResource{}, } } From a4a0726f93f919f3c7daa95ce0e1e527ade60a94 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Mon, 8 Apr 2024 20:46:12 +1200 Subject: [PATCH 48/55] Handle the change in ID for policies when updated --- .../resources/group_role_management_policy.md | 2 +- .../group_role_management_policy_resource.go | 99 +++++++++++-------- ...up_role_management_policy_resource_test.go | 10 +- .../policies/parse/role_management_policy.go | 80 ++++++++++++++- .../role_management_policy_assignment.go | 19 +--- 5 files changed, 144 insertions(+), 66 deletions(-) diff --git a/docs/resources/group_role_management_policy.md b/docs/resources/group_role_management_policy.md index 1d54787deb..2dd240547e 100644 --- a/docs/resources/group_role_management_policy.md +++ b/docs/resources/group_role_management_policy.md @@ -60,10 +60,10 @@ resource "azuread_group_role_management_policy" "example" { * `activation_rules` - (Optional) An `activation_rules` block as defined below. * `active_assignment_rules` - (Optional) An `active_assignment_rules` block as defined below. -* `assignment_type` - (Required) The type of assignment this policy coveres. Can be either `member` or `owner`. * `eligible_assignment_rules` - (Optional) An `eligible_assignment_rules` block as defined below. * `group_id` - (Required) The ID of the Azure AD group for which the policy applies. * `notification_rules` - (Optional) A `notification_rules` block as defined below. +* `role_id` - (Required) The type of assignment this policy coveres. Can be either `member` or `owner`. --- diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index 04b347c1f5..b99e6d841d 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -21,7 +21,7 @@ type GroupRoleManagementPolicyModel struct { Description string `tfschema:"description"` DisplayName string `tfschema:"display_name"` GroupId string `tfschema:"group_id"` - ScopeType msgraph.UnifiedRoleManagementPolicyScope `tfschema:"assignment_type"` + RoleId msgraph.UnifiedRoleManagementPolicyScope `tfschema:"role_id"` ActiveAssignmentRules []GroupRoleManagementPolicyActiveAssignmentRules `tfschema:"active_assignment_rules"` EligibleAssignmentRules []GroupRoleManagementPolicyEligibleAssignmentRules `tfschema:"eligible_assignment_rules"` ActivationRules []GroupRoleManagementPolicyActivationRules `tfschema:"activation_rules"` @@ -81,7 +81,7 @@ type GroupRoleManagementPolicyNotificationSettings struct { type GroupRoleManagementPolicyResource struct{} func (r GroupRoleManagementPolicyResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { - return parse.ValidateRoleManagementPolicyAssignmentID + return parse.ValidateRoleManagementPolicyID } var _ sdk.Resource = GroupRoleManagementPolicyResource{} @@ -104,8 +104,8 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), }, - "assignment_type": { - Description: "The ID of the assignment to the group", + "role_id": { + Description: "The ID of the role of this policy to the group", Type: pluginsdk.TypeString, Required: true, ForceNew: true, @@ -349,29 +349,16 @@ func (r GroupRoleManagementPolicyResource) Create() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 5 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { - policyClient := metadata.Client.Policies.RoleManagementPolicyClient - assignmentClient := metadata.Client.Policies.RoleManagementPolicyAssignmentClient + client := metadata.Client.Policies.RoleManagementPolicyClient // Fetch the existing policy, as they already exist - policies, _, err := assignmentClient.List(ctx, odata.Query{ - Filter: fmt.Sprintf("scopeId eq '%s' and scopeType eq 'Group' and roleDefinitionId eq '%s'", metadata.ResourceData.Get("group_id").(string), metadata.ResourceData.Get("assignment_type").(string)), - }) - if err != nil { - return fmt.Errorf("Could not list existing policy, %+v", err) - } - if len(*policies) != 1 { - return fmt.Errorf("Got the wrong number of policies, expected 1, got %d", len(*policies)) - } - - assignmentId, err := parse.ParseRoleManagementPolicyAssignmentID(*(*policies)[0].ID) + id, err := getPolicyId(ctx, metadata, metadata.ResourceData.Get("group_id").(string), metadata.ResourceData.Get("role_id").(string)) if err != nil { return fmt.Errorf("Could not parse policy assignment ID, %+v", err) } - - id := parse.NewRoleManagementPolicyID(assignmentId.ScopeType, assignmentId.ScopeId, assignmentId.PolicyId) metadata.SetID(id) - policy, _, err := policyClient.Get(ctx, id.ID()) + policy, _, err := client.Get(ctx, id.ID()) if err != nil { return fmt.Errorf("Could not retrieve existing policy, %+v", err) } @@ -384,11 +371,18 @@ func (r GroupRoleManagementPolicyResource) Create() sdk.ResourceFunc { return fmt.Errorf("Could not build update request, %+v", err) } - _, err = policyClient.Update(ctx, *policyUpdate) + _, err = client.Update(ctx, *policyUpdate) if err != nil { return fmt.Errorf("Could not create assignment schedule request, %+v", err) } + // Update the ID as it changes on modification + id, err = getPolicyId(ctx, metadata, metadata.ResourceData.Get("group_id").(string), metadata.ResourceData.Get("role_id").(string)) + if err != nil { + return fmt.Errorf("Could not parse policy assignment ID, %+v", err) + } + metadata.SetID(id) + return nil }, } @@ -398,7 +392,8 @@ func (r GroupRoleManagementPolicyResource) Read() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 5 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { - client := metadata.Client.Policies.RoleManagementPolicyClient + clientPolicy := metadata.Client.Policies.RoleManagementPolicyClient + clientAssignment := metadata.Client.Policies.RoleManagementPolicyAssignmentClient id, err := parse.ParseRoleManagementPolicyID(metadata.ResourceData.Id()) if err != nil { @@ -410,7 +405,7 @@ func (r GroupRoleManagementPolicyResource) Read() sdk.ResourceFunc { return fmt.Errorf("decoding: %+v", err) } - result, _, err := client.Get(ctx, id.ID()) + result, _, err := clientPolicy.Get(ctx, id.ID()) if err != nil { return fmt.Errorf("retrieving %s: %+v", id, err) } @@ -418,9 +413,20 @@ func (r GroupRoleManagementPolicyResource) Read() sdk.ResourceFunc { return fmt.Errorf("retrieving %s: API error, result was nil", id) } + assignments, _, err := clientAssignment.List(ctx, odata.Query{ + Filter: fmt.Sprintf("scopeType eq 'Group' and scopeId eq '%s' and policyId eq '%s'", id.ScopeId, id.ID()), + }) + if err != nil { + return fmt.Errorf("retrieving %s: %+v", id, err) + } + if len(*assignments) != 1 { + return fmt.Errorf("retrieving %s: expected 1 assignment, got %d", id, len(*assignments)) + } + model.Description = *result.Description model.DisplayName = *result.DisplayName model.GroupId = *result.ScopeId + model.RoleId = *(*assignments)[0].RoleDefinitionId if len(model.EligibleAssignmentRules) == 0 { model.EligibleAssignmentRules = make([]GroupRoleManagementPolicyEligibleAssignmentRules, 1) @@ -573,7 +579,6 @@ func (r GroupRoleManagementPolicyResource) Update() sdk.ResourceFunc { if err != nil { return fmt.Errorf("Could not parse policy ID, %+v", err) } - metadata.SetID(id) policy, _, err := client.Get(ctx, id.ID()) @@ -594,6 +599,13 @@ func (r GroupRoleManagementPolicyResource) Update() sdk.ResourceFunc { return fmt.Errorf("Could not create assignment schedule request, %+v", err) } + // Update the ID as it changes on modification + id, err = getPolicyId(ctx, metadata, metadata.ResourceData.Get("group_id").(string), metadata.ResourceData.Get("role_id").(string)) + if err != nil { + return fmt.Errorf("Could not parse policy assignment ID, %+v", err) + } + metadata.SetID(id) + return nil }, } @@ -613,22 +625,6 @@ func (r GroupRoleManagementPolicyResource) Delete() sdk.ResourceFunc { } } -func (r GroupRoleManagementPolicyResource) CustomImporter() sdk.ResourceRunFunc { - return func(ctx context.Context, metadata sdk.ResourceMetaData) error { - id, err := parse.ParseRoleManagementPolicyAssignmentID(metadata.ResourceData.Id()) - if err != nil { - return err - } - if err = metadata.ResourceData.Set("assignment_type", id.RoleDefinitionId); err != nil { - return err - } - metadata.SetID(parse.NewRoleManagementPolicyID(id.ScopeType, id.ScopeId, id.PolicyId)) - - return r.Read().Func(ctx, metadata) - } - -} - func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.UnifiedRoleManagementPolicy) (*msgraph.UnifiedRoleManagementPolicy, error) { var model GroupRoleManagementPolicyModel if err := metadata.Decode(&model); err != nil { @@ -909,6 +905,29 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie }, nil } +// There isn't a reliable way to get the policy ID from the policy API, as the policy ID changes with each modification +func getPolicyId(ctx context.Context, metadata sdk.ResourceMetaData, scopeId, roleDefinitionId string) (*parse.RoleManagementPolicyId, error) { + client := metadata.Client.Policies.RoleManagementPolicyAssignmentClient + + assignments, _, err := client.List(ctx, odata.Query{ + Filter: fmt.Sprintf("scopeType eq 'Group' and scopeId eq '%s' and roleDefinitionId eq '%s'", scopeId, roleDefinitionId), + }) + if err != nil { + return nil, fmt.Errorf("Could not list existing policy assignments, %+v", err) + } + if len(*assignments) != 1 { + return nil, fmt.Errorf("Got the wrong number of policy assignments, expected 1, got %d", len(*assignments)) + } + + assignmentId, err := parse.ParseRoleManagementPolicyAssignmentID(*(*assignments)[0].ID) + if err != nil { + return nil, fmt.Errorf("Could not parse policy assignment ID, %+v", err) + } + + return parse.NewRoleManagementPolicyID(assignmentId.ScopeType, assignmentId.ScopeId, assignmentId.PolicyId), nil + +} + func expandNotificationSettings(rule msgraph.UnifiedRoleManagementPolicyRule, data GroupRoleManagementPolicyNotificationSettings, recipientChange bool) msgraph.UnifiedRoleManagementPolicyRule { level := rule.NotificationLevel defaultRecipients := rule.IsDefaultRecipientsEnabled diff --git a/internal/services/policies/group_role_management_policy_resource_test.go b/internal/services/policies/group_role_management_policy_resource_test.go index 12a7fb082f..e1d9680db6 100644 --- a/internal/services/policies/group_role_management_policy_resource_test.go +++ b/internal/services/policies/group_role_management_policy_resource_test.go @@ -32,6 +32,7 @@ func TestGroupRoleManagementPolicy_member(t *testing.T) { check.That(data.ResourceName).Key("eligible_assignment_rules.0.expiration_required").Exists(), ), }, + data.ImportStep(), }) } @@ -49,6 +50,7 @@ func TestGroupRoleManagementPolicy_owner(t *testing.T) { check.That(data.ResourceName).Key("eligible_assignment_rules.0.expiration_required").Exists(), ), }, + data.ImportStep(), }) } @@ -80,8 +82,8 @@ resource "azuread_group" "pam" { } resource "azuread_group_role_management_policy" "test" { - group_id = azuread_group.pam.id - assignment_type = "member" + group_id = azuread_group.pam.id + role_id = "member" eligible_assignment_rules { expiration_required = false @@ -132,8 +134,8 @@ resource "azuread_group" "pam" { } resource "azuread_group_role_management_policy" "test" { - group_id = azuread_group.pam.id - assignment_type = "owner" + group_id = azuread_group.pam.id + role_id = "owner" eligible_assignment_rules { expiration_required = false diff --git a/internal/services/policies/parse/role_management_policy.go b/internal/services/policies/parse/role_management_policy.go index 448fd54a8c..0c39de8d1c 100644 --- a/internal/services/policies/parse/role_management_policy.go +++ b/internal/services/policies/parse/role_management_policy.go @@ -35,11 +35,11 @@ func ParseRoleManagementPolicyID(input string) (*RoleManagementPolicyId, error) } if _, err := validation.IsUUID(id.ScopeId, "ScopeId"); len(err) > 0 { - return nil, fmt.Errorf("parsing RoleManagementPolicyId: %+v", err) + return nil, fmt.Errorf("parsing RoleManagementPolicyId ScopeId: %+v", err) } if _, err := validation.IsUUID(id.PolicyId, "PolicyId"); len(err) > 0 { - return nil, fmt.Errorf("parsing RoleManagementPolicyId: %+v", err) + return nil, fmt.Errorf("parsing RoleManagementPolicyId PolicyId: %+v", err) } if id.ScopeType != msgraph.UnifiedRoleManagementPolicyScopeDirectory && @@ -51,10 +51,82 @@ func ParseRoleManagementPolicyID(input string) (*RoleManagementPolicyId, error) return &id, nil } +func ValidateRoleManagementPolicyID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + _, err := ParseRoleManagementPolicyID(v) + if err != nil { + errors = append(errors, err) + } + + return +} + +func ValidateDirectoryRoleManagementPolicyID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + id, err := ParseRoleManagementPolicyID(v) + if err != nil { + errors = append(errors, err) + } + + if id.ScopeType != msgraph.UnifiedRoleManagementPolicyScopeDirectory { + errors = append(errors, fmt.Errorf("expected %q to be a Directory role management policy", key)) + } + + return +} + +func ValidateDirectoryRoleRoleManagementPolicyID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + id, err := ParseRoleManagementPolicyID(v) + if err != nil { + errors = append(errors, err) + } + + if id.ScopeType != msgraph.UnifiedRoleManagementPolicyScopeDirectoryRole { + errors = append(errors, fmt.Errorf("expected %q to be a DirectoryRole role management policy", key)) + } + + return +} + +func ValidateGroupRoleManagementPolicyID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + id, err := ParseRoleManagementPolicyID(v) + if err != nil { + errors = append(errors, err) + } + + if id.ScopeType != msgraph.UnifiedRoleManagementPolicyScopeGroup { + errors = append(errors, fmt.Errorf("expected %q to be a Group role management policy", key)) + } + + return +} + func (id *RoleManagementPolicyId) ID() string { - return fmt.Sprintf("%s_%s_%s", id.ScopeType, id.ScopeId, id.PolicyId) + return strings.Join([]string{id.ScopeType, id.ScopeId, id.PolicyId}, "_") } func (id *RoleManagementPolicyId) String() string { - return fmt.Sprintf("Role Management Policy Assignment ID: %s_%s_%s", id.ScopeType, id.ScopeId, id.PolicyId) + return fmt.Sprintf("Role Management Policy Assignment ID: %s", id.ID()) } diff --git a/internal/services/policies/parse/role_management_policy_assignment.go b/internal/services/policies/parse/role_management_policy_assignment.go index 1baf2b9a67..9c290239d8 100644 --- a/internal/services/policies/parse/role_management_policy_assignment.go +++ b/internal/services/policies/parse/role_management_policy_assignment.go @@ -63,24 +63,9 @@ func ParseRoleManagementPolicyAssignmentID(input string) (*RoleManagementPolicyA } func (id *RoleManagementPolicyAssignmentId) ID() string { - return fmt.Sprintf("%s_%s_%s_%s", id.ScopeType, id.ScopeId, id.PolicyId, id.RoleDefinitionId) + return strings.Join([]string{id.ScopeType, id.ScopeId, id.PolicyId, id.RoleDefinitionId}, "_") } func (id *RoleManagementPolicyAssignmentId) String() string { - return fmt.Sprintf("Role Management Policy Assignment ID: %s_%s_%s_%s", id.ScopeType, id.ScopeId, id.PolicyId, id.RoleDefinitionId) -} - -func ValidateRoleManagementPolicyAssignmentID(input interface{}, key string) (warnings []string, errors []error) { - v, ok := input.(string) - if !ok { - errors = append(errors, fmt.Errorf("expected %q to be a string", key)) - return - } - - _, err := ParseRoleManagementPolicyAssignmentID(v) - if err != nil { - errors = append(errors, err) - } - - return + return fmt.Sprintf("Role Management Policy Assignment ID: %s", id.ID()) } From a71fe540f41174e032b38bed8bb2ca75ce9e29b7 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Wed, 1 May 2024 09:52:42 +1200 Subject: [PATCH 49/55] Fix golint errors --- .../privileged_access_group_schedule.go | 9 +++++---- .../policies/group_role_management_policy_resource.go | 2 +- .../policies/parse/role_management_policy_assignment.go | 8 ++++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/internal/services/identitygovernance/privileged_access_group_schedule.go b/internal/services/identitygovernance/privileged_access_group_schedule.go index 6b277d94d2..17618a309f 100644 --- a/internal/services/identitygovernance/privileged_access_group_schedule.go +++ b/internal/services/identitygovernance/privileged_access_group_schedule.go @@ -157,7 +157,8 @@ func buildScheduleRequest(model *PrivilegedAccessGroupScheduleModel, metadata *s schedule.StartDateTime = &startDate } - if model.ExpirationDate != "" { + switch { + case model.ExpirationDate != "": expiryDate, err := time.Parse(time.RFC3339, model.ExpirationDate) if err != nil { return nil, fmt.Errorf("parsing %s: %+v", model.ExpirationDate, err) @@ -169,12 +170,12 @@ func buildScheduleRequest(model *PrivilegedAccessGroupScheduleModel, metadata *s } schedule.Expiration.EndDateTime = &expiryDate schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeAfterDateTime) - } else if model.Duration != "" { + case model.Duration != "": schedule.Expiration.Duration = &model.Duration schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeAfterDuration) - } else if model.PermanentAssignment { + case model.PermanentAssignment: schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeNoExpiration) - } else { + default: return nil, fmt.Errorf("either expiration_date or duration must be set, or permanent_assignment must be true") } diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index b99e6d841d..c49a561048 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -766,7 +766,7 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie } if metadata.ResourceData.HasChange("activation_rules.0.required_conditional_access_authentication_context") { - isEnabled := policyRules["AuthenticationContext_EndUser_Assignment"].IsEnabled + var isEnabled *bool claimValue := policyRules["AuthenticationContext_EndUser_Assignment"].ClaimValue if _, set := metadata.ResourceData.GetOk("activation_rules.0.required_conditional_access_authentication_context"); set { diff --git a/internal/services/policies/parse/role_management_policy_assignment.go b/internal/services/policies/parse/role_management_policy_assignment.go index 9c290239d8..7422a5a8e0 100644 --- a/internal/services/policies/parse/role_management_policy_assignment.go +++ b/internal/services/policies/parse/role_management_policy_assignment.go @@ -45,17 +45,17 @@ func ParseRoleManagementPolicyAssignmentID(input string) (*RoleManagementPolicyA return nil, fmt.Errorf("parsing RoleManagementPolicyAssignmentId: %+v", err) } - if id.ScopeType == msgraph.UnifiedRoleManagementPolicyScopeDirectory || - id.ScopeType == msgraph.UnifiedRoleManagementPolicyScopeDirectoryRole { + switch id.ScopeType { + case msgraph.UnifiedRoleManagementPolicyScopeDirectory, msgraph.UnifiedRoleManagementPolicyScopeDirectoryRole: if _, err := validation.IsUUID(id.RoleDefinitionId, "RoleDefinitionId"); len(err) > 0 { return nil, fmt.Errorf("parsing RoleManagementPolicyAssignmentId: %+v", err) } - } else if id.ScopeType == msgraph.UnifiedRoleManagementPolicyScopeGroup { + case msgraph.UnifiedRoleManagementPolicyScopeGroup: if id.RoleDefinitionId != msgraph.PrivilegedAccessGroupRelationshipMember && id.RoleDefinitionId != msgraph.PrivilegedAccessGroupRelationshipOwner { return nil, fmt.Errorf("parsing RoleManagementPolicyAssignmentId: invalid RoleDefinitionId") } - } else { + default: return nil, fmt.Errorf("parsing RoleManagementPolicyAssignmentId: invalid ScopeType") } From fcc6044a213a260c8a887579da2c3257ef16826c Mon Sep 17 00:00:00 2001 From: iwarapter Date: Tue, 30 Apr 2024 23:16:48 +0100 Subject: [PATCH 50/55] add basis for a data source to retrieve group role management policy id (#1) Add role management policy data source --- .../group_role_management_policy.md | 40 +++++++++ .../resources/group_role_management_policy.md | 4 +- ...d_access_group_assignment_schedule_test.go | 8 +- ...d_access_group_eligiblity_schedule_test.go | 8 +- ...roup_role_management_policy_data_source.go | 86 +++++++++++++++++++ ...role_management_policy_data_source_test.go | 68 +++++++++++++++ ...up_role_management_policy_resource_test.go | 74 ++++++++-------- internal/services/policies/registration.go | 4 +- 8 files changed, 244 insertions(+), 48 deletions(-) create mode 100644 docs/data-sources/group_role_management_policy.md create mode 100644 internal/services/policies/group_role_management_policy_data_source.go create mode 100644 internal/services/policies/group_role_management_policy_data_source_test.go diff --git a/docs/data-sources/group_role_management_policy.md b/docs/data-sources/group_role_management_policy.md new file mode 100644 index 0000000000..c37cdad170 --- /dev/null +++ b/docs/data-sources/group_role_management_policy.md @@ -0,0 +1,40 @@ +--- +subcategory: "Policies" +--- + +# Data Source: azuread_group_role_management_policy + +Use this data source to retrieve a role policy for an Azure AD group. + +## API Permissions + +The following API permissions are required in order to use this resource. + +When authenticated with a service principal, this resource requires the `RoleManagementPolicy.Read.AzureADGroup` Microsoft Graph API permissions. + +When authenticated with a user principal, this resource requires `Global Administrator` directory role, or the `Privileged Role Administrator` role in Identity Governance. + +## Example Usage + +```terraform +resource "azuread_group" "example" { + display_name = "group-name" + security_enabled = true +} + +data "azuread_group_role_management_policy" "owners_policy" { + group_id = azuread_group.example.id + role_id = "owner" +} +``` + +## Argument Reference + +* `group_id` - (Required) The ID of the Azure AD group for which the policy applies. +* `role_id` - (Required) The type of assignment this policy coveres. Can be either `member` or `owner`. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` (String) The ID of this policy. diff --git a/docs/resources/group_role_management_policy.md b/docs/resources/group_role_management_policy.md index 2dd240547e..07dfbb2eed 100644 --- a/docs/resources/group_role_management_policy.md +++ b/docs/resources/group_role_management_policy.md @@ -44,8 +44,8 @@ resource "azuread_group_role_management_policy" "example" { notification_rules { approver_notifications { eligible_assignments { - notification_level = "Critical" - default_recipients = false + notification_level = "Critical" + default_recipients = false additional_recipients = [ "someone@example.com", "someone.else@example.com", diff --git a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_test.go b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_test.go index 31a9fc573c..372927026d 100644 --- a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_test.go +++ b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_test.go @@ -131,11 +131,11 @@ resource "azuread_group" "pam" { mail_enabled = false security_enabled = true - owners = [azuread_user.manual_owner.object_id] + owners = [azuread_user.manual_owner.object_id] - lifecycle { - ignore_changes = [owners] - } + lifecycle { + ignore_changes = [owners] + } } resource "azuread_user" "eligibile_owner" { diff --git a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_test.go b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_test.go index 6ff94039a3..0bfac03d44 100644 --- a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_test.go +++ b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_test.go @@ -130,11 +130,11 @@ resource "azuread_group" "pam" { mail_enabled = false security_enabled = true - owners = [azuread_user.manual_owner.object_id] + owners = [azuread_user.manual_owner.object_id] - lifecycle { - ignore_changes = [owners] - } + lifecycle { + ignore_changes = [owners] + } } resource "azuread_user" "eligibile_owner" { diff --git a/internal/services/policies/group_role_management_policy_data_source.go b/internal/services/policies/group_role_management_policy_data_source.go new file mode 100644 index 0000000000..31134059b0 --- /dev/null +++ b/internal/services/policies/group_role_management_policy_data_source.go @@ -0,0 +1,86 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package policies + +import ( + "context" + "errors" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-azuread/internal/sdk" + "github.com/hashicorp/terraform-provider-azuread/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azuread/internal/tf/validation" + "github.com/manicminer/hamilton/msgraph" +) + +var _ sdk.DataSource = GroupRoleManagementPolicyDataSource{} + +type GroupRoleManagementPolicyDataSource struct{} + +func (r GroupRoleManagementPolicyDataSource) Arguments() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "group_id": { + Description: "ID of the group to which this policy is assigned", + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + }, + + "role_id": { + Description: "The ID of the role of this policy to the group", + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{ + msgraph.PrivilegedAccessGroupRelationshipMember, + msgraph.PrivilegedAccessGroupRelationshipOwner, + msgraph.PrivilegedAccessGroupRelationshipUnknown, + }, false)), + }, + } +} + +func (r GroupRoleManagementPolicyDataSource) Attributes() map[string]*schema.Schema { + return map[string]*pluginsdk.Schema{ + "display_name": { + Description: "The display name of the policy", + Type: pluginsdk.TypeString, + Computed: true, + }, + + "description": { + Description: "Description of the policy", + Type: pluginsdk.TypeString, + Computed: true, + }, + } +} + +func (r GroupRoleManagementPolicyDataSource) ModelObject() interface{} { + return &GroupRoleManagementPolicyModel{} +} + +func (r GroupRoleManagementPolicyDataSource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Groups.GroupsClient + client.BaseClient.DisableRetries = true + defer func() { client.BaseClient.DisableRetries = false }() + + groupID := metadata.ResourceData.Get("group_id").(string) + roleID := metadata.ResourceData.Get("role_id").(string) + id, err := getPolicyId(ctx, metadata, groupID, roleID) + if err != nil { + return errors.New("Bad API response") + } + metadata.ResourceData.SetId(id.ID()) + return nil + }, + } +} + +func (r GroupRoleManagementPolicyDataSource) ResourceType() string { + return "azuread_group_role_management_policy" +} diff --git a/internal/services/policies/group_role_management_policy_data_source_test.go b/internal/services/policies/group_role_management_policy_data_source_test.go new file mode 100644 index 0000000000..ea89b7e1ac --- /dev/null +++ b/internal/services/policies/group_role_management_policy_data_source_test.go @@ -0,0 +1,68 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package policies_test + +import ( + "context" + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/terraform-plugin-testing/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" +) + +type GroupRoleManagementPolicyDataSource struct{} + +func TestGroupRoleManagementPolicyDataSource_member(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azuread_group_role_management_policy", "test") + r := GroupRoleManagementPolicyDataSource{} + + // Ignore the dangling resource post-test as the policy remains while the group is in a pending deletion state + data.ResourceTestIgnoreDangling(t, r, []acceptance.TestStep{ + { + Config: r.member(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (GroupRoleManagementPolicyDataSource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { + client := clients.Policies.RoleManagementPolicyClient + client.BaseClient.DisableRetries = true + defer func() { client.BaseClient.DisableRetries = false }() + + _, status, err := client.Get(ctx, state.ID) + if err != nil { + if status == http.StatusNotFound { + return pointer.To(false), nil + } + return nil, fmt.Errorf("failed to retrieve role management policy with ID %q: %+v", state.ID, err) + } + + return pointer.To(true), nil +} + +func (GroupRoleManagementPolicyDataSource) member(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azuread" {} + +resource "azuread_group" "pam" { + display_name = "PAM Member Test %[1]s" + mail_enabled = false + security_enabled = true +} + +data "azuread_group_role_management_policy" "test" { + group_id = azuread_group_role_management_policy.test.group_id + role_id = "member" +} +`, data.RandomString) +} diff --git a/internal/services/policies/group_role_management_policy_resource_test.go b/internal/services/policies/group_role_management_policy_resource_test.go index e1d9680db6..99f6ac6803 100644 --- a/internal/services/policies/group_role_management_policy_resource_test.go +++ b/internal/services/policies/group_role_management_policy_resource_test.go @@ -93,22 +93,22 @@ resource "azuread_group_role_management_policy" "test" { expire_after = "P365D" } - notification_rules { - eligible_assignments { - approver_notifications { - notification_level = "Critical" - default_recipients = false - additional_recipients = ["someone@example.com"] - } - } - eligible_activations { - assignee_notifications { - notification_level = "All" - default_recipients = true - additional_recipients = ["someone@example.com"] - } - } - } + notification_rules { + eligible_assignments { + approver_notifications { + notification_level = "Critical" + default_recipients = false + additional_recipients = ["someone@example.com"] + } + } + eligible_activations { + assignee_notifications { + notification_level = "All" + default_recipients = true + additional_recipients = ["someone@example.com"] + } + } + } } `, data.RandomString) } @@ -120,9 +120,9 @@ provider "azuread" {} data "azuread_domains" "test" { only_initial = true } - + resource "azuread_user" "approver" { - user_principal_name = "pam-approver-%[1]s@${data.azuread_domains.test.domains.0.domain_name}" + user_principal_name = "pam-approver-%[1]s@${data.azuread_domains.test.domains.0.domain_name}" display_name = "PAM Approver Test %[1]s" password = "%[2]s" } @@ -145,26 +145,26 @@ resource "azuread_group_role_management_policy" "test" { expire_after = "P90D" } - activation_rules { - maximum_duration = "PT1H" - require_approval = true - approval_stage { - primary_approver { - object_id = azuread_user.approver.object_id - type = "singleUser" - } - } - } + activation_rules { + maximum_duration = "PT1H" + require_approval = true + approval_stage { + primary_approver { + object_id = azuread_user.approver.object_id + type = "singleUser" + } + } + } - notification_rules { - active_assignments { - admin_notifications { - notification_level = "Critical" - default_recipients = false - additional_recipients = ["someone@example.com"] - } - } - } + notification_rules { + active_assignments { + admin_notifications { + notification_level = "Critical" + default_recipients = false + additional_recipients = ["someone@example.com"] + } + } + } } `, data.RandomString, data.RandomPassword) } diff --git a/internal/services/policies/registration.go b/internal/services/policies/registration.go index 78f76372c3..da0786bd47 100644 --- a/internal/services/policies/registration.go +++ b/internal/services/policies/registration.go @@ -42,7 +42,9 @@ func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { // DataSources returns the typed DataSources supported by this service func (r Registration) DataSources() []sdk.DataSource { - return []sdk.DataSource{} + return []sdk.DataSource{ + GroupRoleManagementPolicyDataSource{}, + } } // Resources returns the typed Resources supported by this service From ef8f6132bdb19ffb9adab876be37616d7f0c97d1 Mon Sep 17 00:00:00 2001 From: Jerome Brown Date: Tue, 7 May 2024 15:40:21 +1200 Subject: [PATCH 51/55] Correct notification rule example --- .../resources/group_role_management_policy.md | 74 +++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/docs/resources/group_role_management_policy.md b/docs/resources/group_role_management_policy.md index 07dfbb2eed..eafd808cf7 100644 --- a/docs/resources/group_role_management_policy.md +++ b/docs/resources/group_role_management_policy.md @@ -42,8 +42,8 @@ resource "azuread_group_role_management_policy" "example" { } notification_rules { - approver_notifications { - eligible_assignments { + eligible_assignments { + approver_notifications { notification_level = "Critical" default_recipients = false additional_recipients = [ @@ -58,34 +58,34 @@ resource "azuread_group_role_management_policy" "example" { ## Argument Reference -* `activation_rules` - (Optional) An `activation_rules` block as defined below. -* `active_assignment_rules` - (Optional) An `active_assignment_rules` block as defined below. -* `eligible_assignment_rules` - (Optional) An `eligible_assignment_rules` block as defined below. -* `group_id` - (Required) The ID of the Azure AD group for which the policy applies. -* `notification_rules` - (Optional) A `notification_rules` block as defined below. -* `role_id` - (Required) The type of assignment this policy coveres. Can be either `member` or `owner`. +- `activation_rules` - (Optional) An `activation_rules` block as defined below. +- `active_assignment_rules` - (Optional) An `active_assignment_rules` block as defined below. +- `eligible_assignment_rules` - (Optional) An `eligible_assignment_rules` block as defined below. +- `group_id` - (Required) The ID of the Azure AD group for which the policy applies. +- `notification_rules` - (Optional) A `notification_rules` block as defined below. +- `role_id` - (Required) The type of assignment this policy coveres. Can be either `member` or `owner`. --- An `activation_rules` block supports the following: -* `approval_stage` - (Optional) An `approval_stage` block as defined below. -* `maximum_duration` - (Optional) The maximum length of time an activated role can be valid, in an IS)8601 Duration format (e.g. `PT8H`). Valid range is `PT30M` to `PT23H30M`, in 30 minute increments, or `PT1D`. -* `require_approval` - (Optional) Is approval required for activation. If `true` an `approval_stage` block must be provided. -* `require_justification` - (Optional) Is a justification required during activation of the role. -* `require_multifactor_authentication` - (Optional) Is multi-factor authentication required to activate the role. Conflicts with `required_conditional_access_authentication_context`. -* `require_ticket_info` - (Optional) Is ticket information requrired during activation of the role. -* `required_conditional_access_authentication_context` - (Optional) The Entra ID Conditional Access context that must be present for activation. Conflicts with `require_multifactor_authentication`. +- `approval_stage` - (Optional) An `approval_stage` block as defined below. +- `maximum_duration` - (Optional) The maximum length of time an activated role can be valid, in an IS)8601 Duration format (e.g. `PT8H`). Valid range is `PT30M` to `PT23H30M`, in 30 minute increments, or `PT1D`. +- `require_approval` - (Optional) Is approval required for activation. If `true` an `approval_stage` block must be provided. +- `require_justification` - (Optional) Is a justification required during activation of the role. +- `require_multifactor_authentication` - (Optional) Is multi-factor authentication required to activate the role. Conflicts with `required_conditional_access_authentication_context`. +- `require_ticket_info` - (Optional) Is ticket information requrired during activation of the role. +- `required_conditional_access_authentication_context` - (Optional) The Entra ID Conditional Access context that must be present for activation. Conflicts with `require_multifactor_authentication`. --- An `active_assignment_rules` block supports the following: -* `expiration_required` - (Optional) Must an assignment have an expiry date. `false` allows permanent assignment. -* `expire_after` - (Optional) The maximum length of time an assignment can be valid, as an ISO8601 duration. Permitted values: `P15D`, `P30D`, `P90D`, `P180D`, or `P365D`. -* `require_justification` - (Optional) Is a justification required to create new assignments. -* `require_multifactor_authentication` - (Optional) Is multi-factor authentication required to create new assignments. -* `require_ticket_info` - (Optional) Is ticket information required to create new assignments. +- `expiration_required` - (Optional) Must an assignment have an expiry date. `false` allows permanent assignment. +- `expire_after` - (Optional) The maximum length of time an assignment can be valid, as an ISO8601 duration. Permitted values: `P15D`, `P30D`, `P90D`, `P180D`, or `P365D`. +- `require_justification` - (Optional) Is a justification required to create new assignments. +- `require_multifactor_authentication` - (Optional) Is multi-factor authentication required to create new assignments. +- `require_ticket_info` - (Optional) Is ticket information required to create new assignments. One of `expiration_required` or `expire_after` must be provided. @@ -93,14 +93,14 @@ One of `expiration_required` or `expire_after` must be provided. An `approval_stage` block supports the following: -* One or more `primary_approver` blocks as defined below. +- One or more `primary_approver` blocks as defined below. --- An `eligible_assignment_rules` block supports the following: -* `expiration_required`- Must an assignment have an expiry date. `false` allows permanent assignment. -* `expire_after` - The maximum length of time an assignment can be valid, as an ISO8601 duration. Permitted values: `P15D`, `P30D`, `P90D`, `P180D`, or `P365D`. +- `expiration_required`- Must an assignment have an expiry date. `false` allows permanent assignment. +- `expire_after` - The maximum length of time an assignment can be valid, as an ISO8601 duration. Permitted values: `P15D`, `P30D`, `P90D`, `P180D`, or `P365D`. One of `expiration_required` or `expire_after` must be provided. @@ -108,9 +108,9 @@ One of `expiration_required` or `expire_after` must be provided. A `notification_rules` block supports the following: -* `active_assignments` - (Optional) A `notification_target` block as defined below to configure notfications on active role assignments. -* `eligible_activations` - (Optional) A `notification_target` block as defined below for configuring notifications on activation of eligible role. -* `eligible_assignments` - (Optional) A `notification_target` block as defined below to configure notification on eligible role assignments. +- `active_assignments` - (Optional) A `notification_target` block as defined below to configure notfications on active role assignments. +- `eligible_activations` - (Optional) A `notification_target` block as defined below for configuring notifications on activation of eligible role. +- `eligible_assignments` - (Optional) A `notification_target` block as defined below to configure notification on eligible role assignments. At least one `notification_target` block must be provided. @@ -118,17 +118,17 @@ At least one `notification_target` block must be provided. A `notification_settings` block supports the following: -* `additional_recipients` - (Optional) A list of additional email addresses that will receive these notifications. -* `default_recipients` - (Required) Should the default recipients receive these notifications. -* `notification_level` - (Required) What level of notifications should be sent. Options are `All` or `Critical`. +- `additional_recipients` - (Optional) A list of additional email addresses that will receive these notifications. +- `default_recipients` - (Required) Should the default recipients receive these notifications. +- `notification_level` - (Required) What level of notifications should be sent. Options are `All` or `Critical`. --- A `notification_target` block supports the following: -* `admin_notifications` - (Optional) A `notification_settings` block as defined above. -* `approver_notifications` - (Optional) A `notification_settings` block as defined above. -* `assignee_notifications` - (Optional) A `notification_settings` block as defined above. +- `admin_notifications` - (Optional) A `notification_settings` block as defined above. +- `approver_notifications` - (Optional) A `notification_settings` block as defined above. +- `assignee_notifications` - (Optional) A `notification_settings` block as defined above. At least one `notification_settings` block must be provided. @@ -136,16 +136,16 @@ At least one `notification_settings` block must be provided. A `primary_approver` block supports the following: -* `object_id` - (Required) The ID of the object which will act as an approver. -* `type` - (Required) The type of object acting as an approver. Possible options are `singleUser` and `groupMembers`. +- `object_id` - (Required) The ID of the object which will act as an approver. +- `type` - (Required) The type of object acting as an approver. Possible options are `singleUser` and `groupMembers`. ## Attributes Reference In addition to all arguments above, the following attributes are exported: -* `id` (String) The ID of this policy. -* `display_name` (String) The display name of this policy. -* `description` (String) The description of this policy. +- `id` (String) The ID of this policy. +- `display_name` (String) The display name of this policy. +- `description` (String) The description of this policy. ## Import From 9f53c2d7444ef227d6ee073a10a2f69329854582 Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Wed, 8 May 2024 01:10:57 +0100 Subject: [PATCH 52/55] identitygovernance: don't mark as gone when we've actively destroyed a resource --- ...ess_group_assignment_schedule_resource.go} | 36 ++++++++----------- ...roup_assignment_schedule_resource_test.go} | 0 ...ess_group_eligiblity_schedule_resource.go} | 34 +++++++----------- ...roup_eligiblity_schedule_resource_test.go} | 0 4 files changed, 27 insertions(+), 43 deletions(-) rename internal/services/identitygovernance/{privileged_access_group_assignment_schedule.go => privileged_access_group_assignment_schedule_resource.go} (88%) rename internal/services/identitygovernance/{privileged_access_group_assignment_schedule_test.go => privileged_access_group_assignment_schedule_resource_test.go} (100%) rename internal/services/identitygovernance/{privileged_access_group_eligiblity_schedule.go => privileged_access_group_eligiblity_schedule_resource.go} (89%) rename internal/services/identitygovernance/{privileged_access_group_eligiblity_schedule_test.go => privileged_access_group_eligiblity_schedule_resource_test.go} (100%) diff --git a/internal/services/identitygovernance/privileged_access_group_assignment_schedule.go b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_resource.go similarity index 88% rename from internal/services/identitygovernance/privileged_access_group_assignment_schedule.go rename to internal/services/identitygovernance/privileged_access_group_assignment_schedule_resource.go index a567addc12..383df5a112 100644 --- a/internal/services/identitygovernance/privileged_access_group_assignment_schedule.go +++ b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_resource.go @@ -246,31 +246,23 @@ func (r PrivilegedAccessGroupAssignmentScheduleResource) Delete() sdk.ResourceFu } switch model.Status { - case msgraph.PrivilegedAccessGroupAssignmentStatusDenied: + case msgraph.PrivilegedAccessGroupAssignmentStatusDenied, + msgraph.PrivilegedAccessGroupAssignmentStatusFailed, + msgraph.PrivilegedAccessGroupAssignmentStatusGranted, + msgraph.PrivilegedAccessGroupAssignmentStatusPendingAdminDecision, + msgraph.PrivilegedAccessGroupAssignmentStatusPendingApproval, + msgraph.PrivilegedAccessGroupAssignmentStatusPendingProvisioning, + msgraph.PrivilegedAccessGroupAssignmentStatusPendingScheduledCreation: return cancelAssignmentRequest(ctx, metadata, client, id) - case msgraph.PrivilegedAccessGroupAssignmentStatusFailed: - return cancelAssignmentRequest(ctx, metadata, client, id) - case msgraph.PrivilegedAccessGroupAssignmentStatusGranted: - return cancelAssignmentRequest(ctx, metadata, client, id) - case msgraph.PrivilegedAccessGroupAssignmentStatusPendingAdminDecision: - return cancelAssignmentRequest(ctx, metadata, client, id) - case msgraph.PrivilegedAccessGroupAssignmentStatusPendingApproval: - return cancelAssignmentRequest(ctx, metadata, client, id) - case msgraph.PrivilegedAccessGroupAssignmentStatusPendingProvisioning: - return cancelAssignmentRequest(ctx, metadata, client, id) - case msgraph.PrivilegedAccessGroupAssignmentStatusPendingScheduledCreation: - return cancelAssignmentRequest(ctx, metadata, client, id) - case msgraph.PrivilegedAccessGroupAssignmentStatusProvisioned: + case msgraph.PrivilegedAccessGroupAssignmentStatusProvisioned, + msgraph.PrivilegedAccessGroupAssignmentStatusScheduleCreated: return revokeAssignmentRequest(ctx, metadata, client, id, &model) - case msgraph.PrivilegedAccessGroupAssignmentStatusScheduleCreated: - return revokeAssignmentRequest(ctx, metadata, client, id, &model) - case msgraph.PrivilegedAccessGroupAssignmentStatusCanceled: - return metadata.MarkAsGone(id) - case msgraph.PrivilegedAccessGroupAssignmentStatusRevoked: + case msgraph.PrivilegedAccessGroupAssignmentStatusCanceled, + msgraph.PrivilegedAccessGroupAssignmentStatusRevoked: return metadata.MarkAsGone(id) } - return fmt.Errorf("unknown status: %s", model.Status) + return fmt.Errorf("unable to destroy due to unknown status: %s", model.Status) }, } } @@ -283,7 +275,7 @@ func cancelAssignmentRequest(ctx context.Context, metadata sdk.ResourceMetaData, } return fmt.Errorf("cancelling %s: %+v", id, err) } - return metadata.MarkAsGone(id) + return nil } func revokeAssignmentRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupAssignmentScheduleRequestsClient, id *parse.PrivilegedAccessGroupScheduleId, model *PrivilegedAccessGroupScheduleModel) error { @@ -303,5 +295,5 @@ func revokeAssignmentRequest(ctx context.Context, metadata sdk.ResourceMetaData, if result == nil { return fmt.Errorf("retrieving %s: API error, result was nil", id) } - return metadata.MarkAsGone(id) + return nil } diff --git a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_test.go b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_resource_test.go similarity index 100% rename from internal/services/identitygovernance/privileged_access_group_assignment_schedule_test.go rename to internal/services/identitygovernance/privileged_access_group_assignment_schedule_resource_test.go diff --git a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule.go b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_resource.go similarity index 89% rename from internal/services/identitygovernance/privileged_access_group_eligiblity_schedule.go rename to internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_resource.go index 5a8a0ecf4b..d92a9ad977 100644 --- a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule.go +++ b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_resource.go @@ -246,27 +246,19 @@ func (r PrivilegedAccessGroupEligibilityScheduleResource) Delete() sdk.ResourceF } switch model.Status { - case msgraph.PrivilegedAccessGroupEligibilityStatusDenied: + case msgraph.PrivilegedAccessGroupEligibilityStatusDenied, + msgraph.PrivilegedAccessGroupEligibilityStatusFailed, + msgraph.PrivilegedAccessGroupEligibilityStatusGranted, + msgraph.PrivilegedAccessGroupEligibilityStatusPendingAdminDecision, + msgraph.PrivilegedAccessGroupEligibilityStatusPendingApproval, + msgraph.PrivilegedAccessGroupEligibilityStatusPendingProvisioning, + msgraph.PrivilegedAccessGroupEligibilityStatusPendingScheduledCreation: return cancelEligibilityRequest(ctx, metadata, client, id) - case msgraph.PrivilegedAccessGroupEligibilityStatusFailed: - return cancelEligibilityRequest(ctx, metadata, client, id) - case msgraph.PrivilegedAccessGroupEligibilityStatusGranted: - return cancelEligibilityRequest(ctx, metadata, client, id) - case msgraph.PrivilegedAccessGroupEligibilityStatusPendingAdminDecision: - return cancelEligibilityRequest(ctx, metadata, client, id) - case msgraph.PrivilegedAccessGroupEligibilityStatusPendingApproval: - return cancelEligibilityRequest(ctx, metadata, client, id) - case msgraph.PrivilegedAccessGroupEligibilityStatusPendingProvisioning: - return cancelEligibilityRequest(ctx, metadata, client, id) - case msgraph.PrivilegedAccessGroupEligibilityStatusPendingScheduledCreation: - return cancelEligibilityRequest(ctx, metadata, client, id) - case msgraph.PrivilegedAccessGroupEligibilityStatusProvisioned: + case msgraph.PrivilegedAccessGroupEligibilityStatusProvisioned, + msgraph.PrivilegedAccessGroupEligibilityStatusScheduleCreated: return revokeEligibilityRequest(ctx, metadata, client, id, &model) - case msgraph.PrivilegedAccessGroupEligibilityStatusScheduleCreated: - return revokeEligibilityRequest(ctx, metadata, client, id, &model) - case msgraph.PrivilegedAccessGroupEligibilityStatusCanceled: - return metadata.MarkAsGone(id) - case msgraph.PrivilegedAccessGroupEligibilityStatusRevoked: + case msgraph.PrivilegedAccessGroupEligibilityStatusCanceled, + msgraph.PrivilegedAccessGroupEligibilityStatusRevoked: return metadata.MarkAsGone(id) } @@ -283,7 +275,7 @@ func cancelEligibilityRequest(ctx context.Context, metadata sdk.ResourceMetaData } return fmt.Errorf("cancelling %s: %+v", id, err) } - return metadata.MarkAsGone(id) + return nil } func revokeEligibilityRequest(ctx context.Context, metadata sdk.ResourceMetaData, client *msgraph.PrivilegedAccessGroupEligibilityScheduleRequestsClient, id *parse.PrivilegedAccessGroupScheduleId, model *PrivilegedAccessGroupScheduleModel) error { @@ -303,5 +295,5 @@ func revokeEligibilityRequest(ctx context.Context, metadata sdk.ResourceMetaData if result == nil { return fmt.Errorf("retrieving %s: API error, result was nil", id) } - return metadata.MarkAsGone(id) + return nil } diff --git a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_test.go b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_resource_test.go similarity index 100% rename from internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_test.go rename to internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_resource_test.go From 2af400cdf4ef3499a96be8175ee595f1a2dcbbcd Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Wed, 8 May 2024 01:11:37 +0100 Subject: [PATCH 53/55] data.azuread_group_role_management_policy: fix up acceptance test --- ...roup_role_management_policy_data_source.go | 52 +++++++++++++++++-- ...role_management_policy_data_source_test.go | 40 ++------------ 2 files changed, 52 insertions(+), 40 deletions(-) diff --git a/internal/services/policies/group_role_management_policy_data_source.go b/internal/services/policies/group_role_management_policy_data_source.go index 31134059b0..aeb1a65a4b 100644 --- a/internal/services/policies/group_role_management_policy_data_source.go +++ b/internal/services/policies/group_role_management_policy_data_source.go @@ -6,7 +6,9 @@ package policies import ( "context" "errors" + "fmt" + "github.com/hashicorp/go-azure-sdk/sdk/odata" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-azuread/internal/sdk" "github.com/hashicorp/terraform-provider-azuread/internal/tf/pluginsdk" @@ -16,6 +18,13 @@ import ( var _ sdk.DataSource = GroupRoleManagementPolicyDataSource{} +type GroupRoleManagementPolicyDataSourceModel struct { + Description string `tfschema:"description"` + DisplayName string `tfschema:"display_name"` + GroupId string `tfschema:"group_id"` + RoleId msgraph.UnifiedRoleManagementPolicyScope `tfschema:"role_id"` +} + type GroupRoleManagementPolicyDataSource struct{} func (r GroupRoleManagementPolicyDataSource) Arguments() map[string]*schema.Schema { @@ -59,15 +68,22 @@ func (r GroupRoleManagementPolicyDataSource) Attributes() map[string]*schema.Sch } func (r GroupRoleManagementPolicyDataSource) ModelObject() interface{} { - return &GroupRoleManagementPolicyModel{} + return &GroupRoleManagementPolicyDataSourceModel{} } func (r GroupRoleManagementPolicyDataSource) Read() sdk.ResourceFunc { return sdk.ResourceFunc{ Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { - client := metadata.Client.Groups.GroupsClient - client.BaseClient.DisableRetries = true - defer func() { client.BaseClient.DisableRetries = false }() + clientPolicy := metadata.Client.Policies.RoleManagementPolicyClient + clientAssignment := metadata.Client.Policies.RoleManagementPolicyAssignmentClient + + clientPolicy.BaseClient.DisableRetries = true + clientAssignment.BaseClient.DisableRetries = true + + defer func() { + clientPolicy.BaseClient.DisableRetries = false + clientAssignment.BaseClient.DisableRetries = false + }() groupID := metadata.ResourceData.Get("group_id").(string) roleID := metadata.ResourceData.Get("role_id").(string) @@ -75,8 +91,34 @@ func (r GroupRoleManagementPolicyDataSource) Read() sdk.ResourceFunc { if err != nil { return errors.New("Bad API response") } + + result, _, err := clientPolicy.Get(ctx, id.ID()) + if err != nil { + return fmt.Errorf("retrieving %s: %+v", id, err) + } + if result == nil { + return fmt.Errorf("retrieving %s: API error, result was nil", id) + } + + assignments, _, err := clientAssignment.List(ctx, odata.Query{ + Filter: fmt.Sprintf("scopeType eq 'Group' and scopeId eq '%s' and policyId eq '%s'", id.ScopeId, id.ID()), + }) + if err != nil { + return fmt.Errorf("retrieving %s: %+v", id, err) + } + if len(*assignments) != 1 { + return fmt.Errorf("retrieving %s: expected 1 assignment, got %d", id, len(*assignments)) + } + + state := GroupRoleManagementPolicyDataSourceModel{ + Description: *result.Description, + DisplayName: *result.DisplayName, + GroupId: *result.ScopeId, + RoleId: *(*assignments)[0].RoleDefinitionId, + } + metadata.ResourceData.SetId(id.ID()) - return nil + return metadata.Encode(&state) }, } } diff --git a/internal/services/policies/group_role_management_policy_data_source_test.go b/internal/services/policies/group_role_management_policy_data_source_test.go index ea89b7e1ac..e5fb2b0617 100644 --- a/internal/services/policies/group_role_management_policy_data_source_test.go +++ b/internal/services/policies/group_role_management_policy_data_source_test.go @@ -4,65 +4,35 @@ package policies_test import ( - "context" "fmt" - "net/http" "testing" - "github.com/hashicorp/go-azure-helpers/lang/pointer" - "github.com/hashicorp/terraform-plugin-testing/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" ) type GroupRoleManagementPolicyDataSource struct{} func TestGroupRoleManagementPolicyDataSource_member(t *testing.T) { data := acceptance.BuildTestData(t, "data.azuread_group_role_management_policy", "test") - r := GroupRoleManagementPolicyDataSource{} - // Ignore the dangling resource post-test as the policy remains while the group is in a pending deletion state - data.ResourceTestIgnoreDangling(t, r, []acceptance.TestStep{ + data.DataSourceTest(t, []acceptance.TestStep{ { - Config: r.member(data), + Config: GroupRoleManagementPolicyDataSource{}.member(data), Check: acceptance.ComposeTestCheckFunc( - check.That(data.ResourceName).ExistsInAzure(r), + check.That(data.ResourceName).Key("display_name").Exists(), ), }, - data.ImportStep(), }) } -func (GroupRoleManagementPolicyDataSource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) { - client := clients.Policies.RoleManagementPolicyClient - client.BaseClient.DisableRetries = true - defer func() { client.BaseClient.DisableRetries = false }() - - _, status, err := client.Get(ctx, state.ID) - if err != nil { - if status == http.StatusNotFound { - return pointer.To(false), nil - } - return nil, fmt.Errorf("failed to retrieve role management policy with ID %q: %+v", state.ID, err) - } - - return pointer.To(true), nil -} - func (GroupRoleManagementPolicyDataSource) member(data acceptance.TestData) string { return fmt.Sprintf(` -provider "azuread" {} - -resource "azuread_group" "pam" { - display_name = "PAM Member Test %[1]s" - mail_enabled = false - security_enabled = true -} +%[1]s data "azuread_group_role_management_policy" "test" { group_id = azuread_group_role_management_policy.test.group_id role_id = "member" } -`, data.RandomString) +`, GroupRoleManagementPolicyResource{}.member(data)) } From 8c2cdfe1e27902fc2eaaf90381c2c236dba990dc Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Wed, 8 May 2024 01:21:13 +0100 Subject: [PATCH 54/55] doc fixups --- docs/data-sources/group_role_management_policy.md | 4 +++- docs/resources/group_role_management_policy.md | 6 +++--- .../privileged_access_group_assignment_schedule.md | 6 +++--- .../privileged_access_group_eligibility_schedule.md | 6 +++--- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/data-sources/group_role_management_policy.md b/docs/data-sources/group_role_management_policy.md index c37cdad170..c92da0d70b 100644 --- a/docs/data-sources/group_role_management_policy.md +++ b/docs/data-sources/group_role_management_policy.md @@ -37,4 +37,6 @@ data "azuread_group_role_management_policy" "owners_policy" { In addition to all arguments above, the following attributes are exported: -* `id` (String) The ID of this policy. +* `description` - (String) The description of this policy. +* `display_name` - (String) The display name of this policy. +* `id` - (String) The ID of this policy. diff --git a/docs/resources/group_role_management_policy.md b/docs/resources/group_role_management_policy.md index eafd808cf7..ede936e93e 100644 --- a/docs/resources/group_role_management_policy.md +++ b/docs/resources/group_role_management_policy.md @@ -143,9 +143,9 @@ A `primary_approver` block supports the following: In addition to all arguments above, the following attributes are exported: -- `id` (String) The ID of this policy. -- `display_name` (String) The display name of this policy. -- `description` (String) The description of this policy. +- `description` - (String) The description of this policy. +- `display_name` - (String) The display name of this policy. +- `id` - (String) The ID of this policy. ## Import diff --git a/docs/resources/privileged_access_group_assignment_schedule.md b/docs/resources/privileged_access_group_assignment_schedule.md index effafce490..42edec7362 100644 --- a/docs/resources/privileged_access_group_assignment_schedule.md +++ b/docs/resources/privileged_access_group_assignment_schedule.md @@ -57,9 +57,9 @@ At least one of `expiration_date`, `duration`, or `permanent_assignment` must be In addition to all arguments above, the following attributes are exported: -- `id` (String) The ID of this request. -- `status` (String) The provisioning status of this request. -- `target_schedule_id` (String) The ID of this schedule created by this request. +- `id` - (String) The ID of this request. +- `status` - (String) The provisioning status of this request. +- `target_schedule_id` - (String) The ID of this schedule created by this request. ## Import diff --git a/docs/resources/privileged_access_group_eligibility_schedule.md b/docs/resources/privileged_access_group_eligibility_schedule.md index c65974dd17..2eb5ab27bc 100644 --- a/docs/resources/privileged_access_group_eligibility_schedule.md +++ b/docs/resources/privileged_access_group_eligibility_schedule.md @@ -57,9 +57,9 @@ At least one of `expiration_date`, `duration`, or `permanent_assignment` must be In addition to all arguments above, the following attributes are exported: -- `id` (String) The ID of this request. -- `status` (String) The provisioning status of this request. -- `target_schedule_id` (String) The ID of this schedule created by this request. +- `id` - (String) The ID of this request. +- `status` - (String) The provisioning status of this request. +- `target_schedule_id` - (String) The ID of this schedule created by this request. ## Import From 4e071f8e6d907fb7a95e4fd5260195373b54e10e Mon Sep 17 00:00:00 2001 From: Tom Bamford Date: Wed, 8 May 2024 01:37:50 +0100 Subject: [PATCH 55/55] make generate --- .github/labeler-issue-triage.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/labeler-issue-triage.yaml b/.github/labeler-issue-triage.yaml index 55b0f27153..a0a95dfed3 100644 --- a/.github/labeler-issue-triage.yaml +++ b/.github/labeler-issue-triage.yaml @@ -25,16 +25,16 @@ feature/domains: - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azuread_domains((.|\n)*)###' feature/groups: - - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azuread_group((.|\n)*)###' + - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azuread_(group\W+|group_member\W+|groups\W+)((.|\n)*)###' feature/identity-governance: - - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azuread_access_package((.|\n)*)###' + - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azuread_(access_package|privileged_access_group_)((.|\n)*)###' feature/invitations: - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azuread_invitation((.|\n)*)###' feature/policies: - - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azuread_(authentication_strength_policy|claims_mapping_policy)((.|\n)*)###' + - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azuread_(authentication_strength_policy|claims_mapping_policy|group_role_management_policy)((.|\n)*)###' feature/service-principals: - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azuread_(client_config|service_principal|synchronization_)((.|\n)*)###'