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)*)###' 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..c92da0d70b --- /dev/null +++ b/docs/data-sources/group_role_management_policy.md @@ -0,0 +1,42 @@ +--- +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: + +* `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 new file mode 100644 index 0000000000..ede936e93e --- /dev/null +++ b/docs/resources/group_role_management_policy.md @@ -0,0 +1,152 @@ +--- +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" { + group_id = azuread_group.example.id + assignment_type = "member" + + active_assignment_rules { + expire_after = "P365D" + } + + eligible_assignment_rules { + expiration_required = false + } + + notification_rules { + eligible_assignments { + approver_notifications { + notification_level = "Critical" + default_recipients = false + additional_recipients = [ + "someone@example.com", + "someone.else@example.com", + ] + } + } + } +} +``` + +## 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`. + +--- + +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`. + +--- + +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. + +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. + +--- + +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: + +- `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. + +--- + +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_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. + +--- + +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`. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +- `description` - (String) The description of this policy. +- `display_name` - (String) The display name of this policy. +- `id` - (String) The ID of this policy. + +## Import + +Because these policies are created automatically by Entra ID, they will auto-import on first use. diff --git a/docs/resources/privileged_access_group_assignment_schedule.md b/docs/resources/privileged_access_group_assignment_schedule.md new file mode 100644 index 0000000000..42edec7362 --- /dev/null +++ b/docs/resources/privileged_access_group_assignment_schedule.md @@ -0,0 +1,70 @@ +--- +subcategory: "Identity Governance" +--- + +# Resource: azuread_privileged_access_group_assignment_schedule + +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" "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 schedule ID, e.g. + +```shell +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.md b/docs/resources/privileged_access_group_eligibility_schedule.md new file mode 100644 index 0000000000..2eb5ab27bc --- /dev/null +++ b/docs/resources/privileged_access_group_eligibility_schedule.md @@ -0,0 +1,70 @@ +--- +subcategory: "Identity Governance" +--- + +# Resource: azuread_privileged_access_group_eligibility_schedule + +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" "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 schedule ID, e.g. + +```shell +terraform import azuread_privileged_access_group_eligibility_schedule.example 00000000-0000-0000-0000-000000000000_member_00000000-0000-0000-0000-000000000000 +``` 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/internal/provider/services.go b/internal/provider/services.go index 20c524da44..23176cbb48 100644 --- a/internal/provider/services.go +++ b/internal/provider/services.go @@ -29,6 +29,8 @@ func SupportedTypedServices() []sdk.TypedServiceRegistration { applications.Registration{}, directoryroles.Registration{}, domains.Registration{}, + policies.Registration{}, + identitygovernance.Registration{}, serviceprincipals.Registration{}, } } diff --git a/internal/services/identitygovernance/client/client.go b/internal/services/identitygovernance/client/client.go index 201d2cecf5..fdb37c852b 100644 --- a/internal/services/identitygovernance/client/client.go +++ b/internal/services/identitygovernance/client/client.go @@ -9,14 +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 + 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 { @@ -54,14 +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, + 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/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/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_resource.go b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_resource.go new file mode 100644 index 0000000000..383df5a112 --- /dev/null +++ b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_resource.go @@ -0,0 +1,299 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package identitygovernance + +import ( + "context" + "fmt" + "net/http" + "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/manicminer/hamilton/msgraph" +) + +type PrivilegedAccessGroupAssignmentScheduleResource struct{} + +func (r PrivilegedAccessGroupAssignmentScheduleResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return parse.ValidatePrivilegedAccessGroupScheduleID +} + +var _ sdk.Resource = PrivilegedAccessGroupAssignmentScheduleResource{} + +func (r PrivilegedAccessGroupAssignmentScheduleResource) ResourceType() string { + return "azuread_privileged_access_group_assignment_schedule" +} + +func (r PrivilegedAccessGroupAssignmentScheduleResource) ModelObject() interface{} { + return &PrivilegedAccessGroupScheduleModel{} +} + +func (r PrivilegedAccessGroupAssignmentScheduleResource) Arguments() map[string]*pluginsdk.Schema { + return privilegedAccessGroupScheduleArguments() +} + +func (r PrivilegedAccessGroupAssignmentScheduleResource) Attributes() map[string]*pluginsdk.Schema { + return privilegedAccessGroupScheduleAttributes() +} + +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 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, + } + + if model.TicketNumber != "" || model.TicketSystem != "" { + properties.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.PrivilegedAccessGroupAssignmentStatusFailed { + return fmt.Errorf("Assignment schedule request is in a failed state") + } + + id, err := parse.ParsePrivilegedAccessGroupScheduleID(*req.TargetScheduleId) + if err != nil { + return err + } + metadata.SetID(id) + + return nil + }, + } +} + +func (r PrivilegedAccessGroupAssignmentScheduleResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + cSchedule := metadata.Client.IdentityGovernance.PrivilegedAccessGroupAssignmentScheduleClient + cRequests := metadata.Client.IdentityGovernance.PrivilegedAccessGroupAssignmentScheduleRequestsClient + + var request *msgraph.PrivilegedAccessGroupAssignmentScheduleRequest + + id, err := parse.ParsePrivilegedAccessGroupScheduleID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + 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 := 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, + }, + }) + if err != nil { + return fmt.Errorf("listing requests: %+v", err) + } + if len(*requests) == 0 { + 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 + } + } + + // 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.PermanentAssignment = *request.ScheduleInfo.Expiration.Type == msgraph.ExpirationPatternTypeNoExpiration + model.PrincipalId = *request.PrincipalId + model.StartDate = request.ScheduleInfo.StartDateTime.Format(time.RFC3339) + model.Status = request.Status + } 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 + } + + return metadata.Encode(&model) + }, + } +} + +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, + } + + if model.TicketNumber != "" || model.TicketSystem != "" { + properties.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.PrivilegedAccessGroupAssignmentStatusFailed { + return fmt.Errorf("Assignment schedule request is in a failed state") + } + + return nil + }, + } +} + +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.ParsePrivilegedAccessGroupScheduleID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + var model PrivilegedAccessGroupScheduleModel + if err := metadata.Decode(&model); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + switch model.Status { + case msgraph.PrivilegedAccessGroupAssignmentStatusDenied, + msgraph.PrivilegedAccessGroupAssignmentStatusFailed, + msgraph.PrivilegedAccessGroupAssignmentStatusGranted, + msgraph.PrivilegedAccessGroupAssignmentStatusPendingAdminDecision, + msgraph.PrivilegedAccessGroupAssignmentStatusPendingApproval, + msgraph.PrivilegedAccessGroupAssignmentStatusPendingProvisioning, + msgraph.PrivilegedAccessGroupAssignmentStatusPendingScheduledCreation: + return cancelAssignmentRequest(ctx, metadata, client, id) + case msgraph.PrivilegedAccessGroupAssignmentStatusProvisioned, + msgraph.PrivilegedAccessGroupAssignmentStatusScheduleCreated: + return revokeAssignmentRequest(ctx, metadata, client, id, &model) + case msgraph.PrivilegedAccessGroupAssignmentStatusCanceled, + msgraph.PrivilegedAccessGroupAssignmentStatusRevoked: + return metadata.MarkAsGone(id) + } + + return fmt.Errorf("unable to destroy due to unknown status: %s", model.Status) + }, + } +} + +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) + } + return fmt.Errorf("cancelling %s: %+v", id, err) + } + return nil +} + +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: pointer.To(id.ID()), + 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 +} diff --git a/internal/services/identitygovernance/privileged_access_group_assignment_schedule_resource_test.go b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_resource_test.go new file mode 100644 index 0000000000..372927026d --- /dev/null +++ b/internal/services/identitygovernance/privileged_access_group_assignment_schedule_resource_test.go @@ -0,0 +1,155 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package identitygovernance_test + +import ( + "context" + "fmt" + "net/http" + "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/hashicorp/terraform-provider-azuread/internal/services/identitygovernance/helpers" + "github.com/hashicorp/terraform-provider-azuread/internal/services/identitygovernance/parse" +) + +type PrivilegedAccessGroupAssignmentScheduleResource struct{} + +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() + + 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. + helpers.SleepCheck(5*time.Minute+15*time.Second), + ), + }, + data.ImportStep(), + }) +} + +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{ + { + 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. + helpers.SleepCheck(5*time.Minute+15*time.Second), + ), + }, + data.ImportStep(), + }) + +} + +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 }() + + 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 with ID %q: %+v", id.ID(), err) + } + return pointer.To(true), nil +} + +func (PrivilegedAccessGroupAssignmentScheduleResource) member(data acceptance.TestData, endTime time.Time) string { + return fmt.Sprintf(` +provider "azuread" {} + +resource "azuread_group" "pam" { + display_name = "Privileged Assignment %[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" "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 (PrivilegedAccessGroupAssignmentScheduleResource) 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 Assignment %[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-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" "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) +} diff --git a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_resource.go b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_resource.go new file mode 100644 index 0000000000..d92a9ad977 --- /dev/null +++ b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_resource.go @@ -0,0 +1,299 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package identitygovernance + +import ( + "context" + "fmt" + "net/http" + "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/manicminer/hamilton/msgraph" +) + +type PrivilegedAccessGroupEligibilityScheduleResource struct{} + +func (r PrivilegedAccessGroupEligibilityScheduleResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return parse.ValidatePrivilegedAccessGroupScheduleID +} + +var _ sdk.Resource = PrivilegedAccessGroupEligibilityScheduleResource{} + +func (r PrivilegedAccessGroupEligibilityScheduleResource) ResourceType() string { + return "azuread_privileged_access_group_eligibility_schedule" +} + +func (r PrivilegedAccessGroupEligibilityScheduleResource) ModelObject() interface{} { + return &PrivilegedAccessGroupScheduleModel{} +} + +func (r PrivilegedAccessGroupEligibilityScheduleResource) Arguments() map[string]*pluginsdk.Schema { + return privilegedAccessGroupScheduleArguments() +} + +func (r PrivilegedAccessGroupEligibilityScheduleResource) Attributes() map[string]*pluginsdk.Schema { + return privilegedAccessGroupScheduleAttributes() +} + +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 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, + } + + if model.TicketNumber != "" || model.TicketSystem != "" { + properties.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, err := parse.ParsePrivilegedAccessGroupScheduleID(*req.TargetScheduleId) + if err != nil { + return err + } + metadata.SetID(id) + + return nil + }, + } +} + +func (r PrivilegedAccessGroupEligibilityScheduleResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + cSchedule := metadata.Client.IdentityGovernance.PrivilegedAccessGroupEligibilityScheduleClient + cRequests := metadata.Client.IdentityGovernance.PrivilegedAccessGroupEligibilityScheduleRequestsClient + + var request *msgraph.PrivilegedAccessGroupEligibilityScheduleRequest + + id, err := parse.ParsePrivilegedAccessGroupScheduleID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + 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 := 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, + }, + }) + if err != nil { + return fmt.Errorf("listing requests: %+v", err) + } + if len(*requests) == 0 { + 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 + } + } + + // 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.PermanentAssignment = *request.ScheduleInfo.Expiration.Type == msgraph.ExpirationPatternTypeNoExpiration + model.PrincipalId = *request.PrincipalId + model.StartDate = request.ScheduleInfo.StartDateTime.Format(time.RFC3339) + model.Status = request.Status + } 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 + } + + return metadata.Encode(&model) + }, + } +} + +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, + } + + if model.TicketNumber != "" || model.TicketSystem != "" { + properties.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") + } + + return nil + }, + } +} + +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.ParsePrivilegedAccessGroupScheduleID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + var model PrivilegedAccessGroupScheduleModel + if err := metadata.Decode(&model); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + switch model.Status { + case msgraph.PrivilegedAccessGroupEligibilityStatusDenied, + msgraph.PrivilegedAccessGroupEligibilityStatusFailed, + msgraph.PrivilegedAccessGroupEligibilityStatusGranted, + msgraph.PrivilegedAccessGroupEligibilityStatusPendingAdminDecision, + msgraph.PrivilegedAccessGroupEligibilityStatusPendingApproval, + msgraph.PrivilegedAccessGroupEligibilityStatusPendingProvisioning, + msgraph.PrivilegedAccessGroupEligibilityStatusPendingScheduledCreation: + return cancelEligibilityRequest(ctx, metadata, client, id) + case msgraph.PrivilegedAccessGroupEligibilityStatusProvisioned, + msgraph.PrivilegedAccessGroupEligibilityStatusScheduleCreated: + return revokeEligibilityRequest(ctx, metadata, client, id, &model) + case msgraph.PrivilegedAccessGroupEligibilityStatusCanceled, + msgraph.PrivilegedAccessGroupEligibilityStatusRevoked: + return metadata.MarkAsGone(id) + } + + return fmt.Errorf("unknown status: %s", model.Status) + }, + } +} + +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) + } + return fmt.Errorf("cancelling %s: %+v", id, err) + } + return nil +} + +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: pointer.To(id.ID()), + 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 +} diff --git a/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_resource_test.go b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_resource_test.go new file mode 100644 index 0000000000..0bfac03d44 --- /dev/null +++ b/internal/services/identitygovernance/privileged_access_group_eligiblity_schedule_resource_test.go @@ -0,0 +1,155 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package identitygovernance_test + +import ( + "context" + "fmt" + "net/http" + "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/hashicorp/terraform-provider-azuread/internal/services/identitygovernance/helpers" + "github.com/hashicorp/terraform-provider-azuread/internal/services/identitygovernance/parse" +) + +type PrivilegedAccessGroupEligibilityScheduleResource struct{} + +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() + + 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. + helpers.SleepCheck(5*time.Minute+15*time.Second), + ), + }, + data.ImportStep(), + }) +} + +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{ + { + 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. + helpers.SleepCheck(5*time.Minute+15*time.Second), + ), + }, + data.ImportStep(), + }) +} + +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 }() + + 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", id.ID(), err) + } + return pointer.To(true), nil +} + +func (PrivilegedAccessGroupEligibilityScheduleResource) 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" "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 (PrivilegedAccessGroupEligibilityScheduleResource) 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" "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) +} diff --git a/internal/services/identitygovernance/privileged_access_group_schedule.go b/internal/services/identitygovernance/privileged_access_group_schedule.go new file mode 100644 index 0000000000..17618a309f --- /dev/null +++ b/internal/services/identitygovernance/privileged_access_group_schedule.go @@ -0,0 +1,189 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package identitygovernance + +import ( + "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" +) + +type PrivilegedAccessGroupScheduleModel 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"` + TicketNumber string `tfschema:"ticket_number"` + TicketSystem string `tfschema:"ticket_system"` +} + +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", + 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, + }, 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, + 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": { + 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, + Computed: 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, + ConflictsWith: []string{"expiration_date"}, + ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + }, + + "permanent_assignment": { + Description: "Is the assignment permanent", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + + "justification": { + Description: "The justification for the assignment", + Type: pluginsdk.TypeString, + Optional: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + }, + + "ticket_number": { + Description: "The ticket number authorising the assignment", + Type: pluginsdk.TypeString, + Optional: true, + RequiredWith: []string{"ticket_system"}, + ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + }, + + "ticket_system": { + Description: "The ticket system authorising the assignment", + Type: pluginsdk.TypeString, + Optional: true, + RequiredWith: []string{"ticket_number"}, + ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), + }, + } +} + +func privilegedAccessGroupScheduleAttributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "status": { + Description: "The status of the schedule", + Type: pluginsdk.TypeString, + Computed: true, + }, + } +} + +func buildScheduleRequest(model *PrivilegedAccessGroupScheduleModel, 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 + } + + 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) + } + 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) + case model.Duration != "": + schedule.Expiration.Duration = &model.Duration + schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeAfterDuration) + case model.PermanentAssignment: + schedule.Expiration.Type = pointer.To(msgraph.ExpirationPatternTypeNoExpiration) + default: + 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/registration.go b/internal/services/identitygovernance/registration.go index 81a3e1b8ac..1ad6f3916b 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,16 @@ 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{ + PrivilegedAccessGroupAssignmentScheduleResource{}, + PrivilegedAccessGroupEligibilityScheduleResource{}, + } +} 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/group_role_management_policy_data_source.go b/internal/services/policies/group_role_management_policy_data_source.go new file mode 100644 index 0000000000..aeb1a65a4b --- /dev/null +++ b/internal/services/policies/group_role_management_policy_data_source.go @@ -0,0 +1,128 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +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" + "github.com/hashicorp/terraform-provider-azuread/internal/tf/validation" + "github.com/manicminer/hamilton/msgraph" +) + +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 { + 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 &GroupRoleManagementPolicyDataSourceModel{} +} + +func (r GroupRoleManagementPolicyDataSource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + 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) + id, err := getPolicyId(ctx, metadata, groupID, roleID) + 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 metadata.Encode(&state) + }, + } +} + +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..e5fb2b0617 --- /dev/null +++ b/internal/services/policies/group_role_management_policy_data_source_test.go @@ -0,0 +1,38 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package policies_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-provider-azuread/internal/acceptance" + "github.com/hashicorp/terraform-provider-azuread/internal/acceptance/check" +) + +type GroupRoleManagementPolicyDataSource struct{} + +func TestGroupRoleManagementPolicyDataSource_member(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azuread_group_role_management_policy", "test") + + data.DataSourceTest(t, []acceptance.TestStep{ + { + Config: GroupRoleManagementPolicyDataSource{}.member(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).Key("display_name").Exists(), + ), + }, + }) +} + +func (GroupRoleManagementPolicyDataSource) member(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s + +data "azuread_group_role_management_policy" "test" { + group_id = azuread_group_role_management_policy.test.group_id + role_id = "member" +} +`, GroupRoleManagementPolicyResource{}.member(data)) +} 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..c49a561048 --- /dev/null +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -0,0 +1,1024 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package policies + +import ( + "context" + "fmt" + "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" + "github.com/hashicorp/terraform-provider-azuread/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azuread/internal/tf/validation" + "github.com/manicminer/hamilton/msgraph" +) + +type GroupRoleManagementPolicyModel struct { + Description string `tfschema:"description"` + DisplayName string `tfschema:"display_name"` + GroupId string `tfschema:"group_id"` + RoleId msgraph.UnifiedRoleManagementPolicyScope `tfschema:"role_id"` + ActiveAssignmentRules []GroupRoleManagementPolicyActiveAssignmentRules `tfschema:"active_assignment_rules"` + EligibleAssignmentRules []GroupRoleManagementPolicyEligibleAssignmentRules `tfschema:"eligible_assignment_rules"` + ActivationRules []GroupRoleManagementPolicyActivationRules `tfschema:"activation_rules"` + NotificationRules []GroupRoleManagementPolicyNotificationEvents `tfschema:"notification_rules"` +} + +type GroupRoleManagementPolicyActiveAssignmentRules struct { + ExpirationRequired bool `tfschema:"expiration_required"` + 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 { + ExpirationRequired bool `tfschema:"expiration_required"` + ExpireAfter string `tfschema:"expire_after"` +} + +type GroupRoleManagementPolicyActivationRules struct { + MaximumDuration string `tfschema:"maximum_duration"` + RequireApproval bool `tfschema:"require_approval"` + ApprovalStages []GroupRoleManagementPolicyApprovalStage `tfschema:"approval_stage"` + 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"` +} + +type GroupRoleManagementPolicyApprovalStage struct { + PrimaryApprovers []GroupRoleManagementPolicyApprover `tfschema:"primary_approver"` +} + +type GroupRoleManagementPolicyApprover struct { + ID string `tfschema:"object_id"` + Type string `tfschema:"type"` +} + +type GroupRoleManagementPolicyNotificationEvents struct { + ActiveAssignments []GroupRoleManagementPolicyNotificationRule `tfschema:"active_assignments"` + EligibleActivations []GroupRoleManagementPolicyNotificationRule `tfschema:"eligible_activations"` + EligibleAssignments []GroupRoleManagementPolicyNotificationRule `tfschema:"eligible_assignments"` +} + +type GroupRoleManagementPolicyNotificationRule struct { + AdminNotifications []GroupRoleManagementPolicyNotificationSettings `tfschema:"admin_notifications"` + ApproverNotifications []GroupRoleManagementPolicyNotificationSettings `tfschema:"approver_notifications"` + AssigneeNotifications []GroupRoleManagementPolicyNotificationSettings `tfschema:"assignee_notifications"` +} + +type GroupRoleManagementPolicyNotificationSettings struct { + NotificationLevel msgraph.UnifiedRoleManagementPolicyRuleNotificationLevel `tfschema:"notification_level"` + DefaultRecipients bool `tfschema:"default_recipients"` + AdditionalRecipients []string `tfschema:"additional_recipients"` +} + +type GroupRoleManagementPolicyResource struct{} + +func (r GroupRoleManagementPolicyResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return parse.ValidateRoleManagementPolicyID +} + +var _ sdk.Resource = GroupRoleManagementPolicyResource{} + +func (r GroupRoleManagementPolicyResource) ResourceType() string { + return "azuread_group_role_management_policy" +} + +func (r GroupRoleManagementPolicyResource) ModelObject() interface{} { + return &GroupRoleManagementPolicyModel{} +} + +func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.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)), + }, + + "eligible_assignment_rules": { + Description: "The rules for eligible assignment of the policy", + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "expiration_required": { + 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)), + }, + }, + }, + }, + + "active_assignment_rules": { + Description: "The rules for active assignment of the policy", + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "expiration_required": { + 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)), + }, + + "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_ticket_info": { + Description: "Whether ticket information is required to make an assignment", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, + }, + }, + }, + + "activation_rules": { + Description: "The activation rules of the policy", + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "maximum_duration": { + Description: "The time after which the 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_stage": { + Description: "The approval stages for the activation", + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "primary_approver": { + Description: "The IDs of the users or groups who can approve the activation", + Type: pluginsdk.TypeSet, + Required: true, + MinItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "object_id": { + Description: "The ID of the object to act as an approver", + Type: pluginsdk.TypeString, + Required: true, + ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), + }, + + "type": { + Description: "The type of object acting as an approver", + Type: pluginsdk.TypeString, + Optional: true, + ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"singleUser", "groupMembers"}, false)), + }, + }, + }, + }, + }, + }, + }, + + "required_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_rules.0.required_conditional_access_authentication_context"}, + }, + + "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, + }, + }, + }, + }, + + "notification_rules": { + Description: "The notification rules of the policy", + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "active_assignments": { + Description: "Notifications about active assignments", + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: notificationRuleSchema(), + }, + }, + "eligible_activations": { + Description: "Notifications about activations of eligible assignments", + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: notificationRuleSchema(), + }, + }, + "eligible_assignments": { + Description: "Notifications about eligible assignments", + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: notificationRuleSchema(), + }, + }, + }, + }, + }, + } +} + +func (r GroupRoleManagementPolicyResource) Attributes() map[string]*pluginsdk.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 GroupRoleManagementPolicyResource) Create() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Policies.RoleManagementPolicyClient + + // Fetch the existing policy, as they already exist + 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) + + 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) + } + + // 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 + }, + } +} + +func (r GroupRoleManagementPolicyResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + clientPolicy := metadata.Client.Policies.RoleManagementPolicyClient + clientAssignment := metadata.Client.Policies.RoleManagementPolicyAssignmentClient + + id, err := parse.ParseRoleManagementPolicyID(metadata.ResourceData.Id()) + if err != nil { + return fmt.Errorf("Could not parse policy ID, %+v", err) + } + + var model GroupRoleManagementPolicyModel + if err := metadata.Decode(&model); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + 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)) + } + + 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) + } + if len(model.ActiveAssignmentRules) == 0 { + model.ActiveAssignmentRules = make([]GroupRoleManagementPolicyActiveAssignmentRules, 1) + } + if len(model.ActivationRules) == 0 { + model.ActivationRules = make([]GroupRoleManagementPolicyActivationRules, 1) + } + if len(model.NotificationRules) == 0 { + model.NotificationRules = make([]GroupRoleManagementPolicyNotificationEvents, 1) + } + if len(model.NotificationRules[0].EligibleActivations) == 0 { + model.NotificationRules[0].EligibleActivations = make([]GroupRoleManagementPolicyNotificationRule, 1) + } + if len(model.NotificationRules[0].ActiveAssignments) == 0 { + model.NotificationRules[0].ActiveAssignments = 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 "Approval_EndUser_Assignment": + model.ActivationRules[0].RequireApproval = *rule.Setting.IsApprovalRequired + + 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 != "" { + 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 + model.ActivationRules[0].RequireTicketInfo = false + for _, enabledRule := range *rule.EnabledRules { + switch enabledRule { + case "MultiFactorAuthentication": + model.ActivationRules[0].RequireMultiFactorAuth = true + case "Justification": + model.ActivationRules[0].RequireJustification = true + case "Ticketing": + model.ActivationRules[0].RequireTicketInfo = true + } + } + + 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_Eligibility": + model.NotificationRules[0].EligibleAssignments[0].AdminNotifications = []GroupRoleManagementPolicyNotificationSettings{ + *flattenNotificationSettings(pointer.To(rule)), + } + + case "Notification_Admin_EndUser_Assignment": + model.NotificationRules[0].EligibleActivations[0].AdminNotifications = []GroupRoleManagementPolicyNotificationSettings{ + *flattenNotificationSettings(pointer.To(rule)), + } + + case "Notification_Approver_Admin_Assignment": + model.NotificationRules[0].ActiveAssignments[0].ApproverNotifications = []GroupRoleManagementPolicyNotificationSettings{ + *flattenNotificationSettings(pointer.To(rule)), + } + + case "Notification_Approver_Admin_Eligibility": + model.NotificationRules[0].EligibleAssignments[0].ApproverNotifications = []GroupRoleManagementPolicyNotificationSettings{ + *flattenNotificationSettings(pointer.To(rule)), + } + + case "Notification_Approver_EndUser_Assignment": + model.NotificationRules[0].EligibleActivations[0].ApproverNotifications = []GroupRoleManagementPolicyNotificationSettings{ + *flattenNotificationSettings(pointer.To(rule)), + } + + case "Notification_Requestor_Admin_Assignment": + model.NotificationRules[0].ActiveAssignments[0].AssigneeNotifications = []GroupRoleManagementPolicyNotificationSettings{ + *flattenNotificationSettings(pointer.To(rule)), + } + + case "Notification_Requestor_Admin_Eligibility": + model.NotificationRules[0].EligibleAssignments[0].AssigneeNotifications = []GroupRoleManagementPolicyNotificationSettings{ + *flattenNotificationSettings(pointer.To(rule)), + } + + case "Notification_Requestor_EndUser_Assignment": + model.NotificationRules[0].EligibleActivations[0].AssigneeNotifications = []GroupRoleManagementPolicyNotificationSettings{ + *flattenNotificationSettings(pointer.To(rule)), + } + } + } + + return metadata.Encode(&model) + }, + } +} + +func (r GroupRoleManagementPolicyResource) 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) + } + + // 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 + }, + } +} + +func (r GroupRoleManagementPolicyResource) 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 GroupRoleManagementPolicyModel + 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 + } + 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 *expirationRequired != model.EligibleAssignmentRules[0].ExpirationRequired { + expirationRequired = pointer.To(model.EligibleAssignmentRules[0].ExpirationRequired) + } + if *maximumDuration != model.EligibleAssignmentRules[0].ExpireAfter && + model.EligibleAssignmentRules[0].ExpireAfter != "" { + maximumDuration = pointer.To(model.EligibleAssignmentRules[0].ExpireAfter) + } + + rule := msgraph.UnifiedRoleManagementPolicyRule{ + 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) + } + + 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.ActiveAssignmentRules[0].RequireMultiFactorAuth { + enabledRules = append(enabledRules, "MultiFactorAuthentication") + } + 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, + 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.expiration_required") || + metadata.ResourceData.HasChange("active_assignment_rules.0.expire_after") { + expirationRequired := policyRules["Expiration_Admin_Assignment"].IsExpirationRequired + maximumDuration := policyRules["Expiration_Admin_Assignment"].MaximumDuration + + if *expirationRequired != model.ActiveAssignmentRules[0].ExpirationRequired { + expirationRequired = pointer.To(model.ActiveAssignmentRules[0].ExpirationRequired) + } + if *maximumDuration != model.ActiveAssignmentRules[0].ExpireAfter && + model.ActiveAssignmentRules[0].ExpireAfter != "" { + maximumDuration = pointer.To(model.ActiveAssignmentRules[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 := msgraph.UnifiedRoleManagementPolicyRule{ + ID: policyRules["Expiration_EndUser_Assignment"].ID, + ODataType: policyRules["Expiration_EndUser_Assignment"].ODataType, + Target: policyRules["Expiration_EndUser_Assignment"].Target, + 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_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 + var approvalStages []msgraph.ApprovalStage + if *isApprovalRequired != model.ActivationRules[0].RequireApproval { + isApprovalRequired = pointer.To(model.ActivationRules[0].RequireApproval) + } + 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 { + switch approver.Type { + case "singleUser": + primaryApprovers = append(primaryApprovers, msgraph.UserSet{ + ODataType: pointer.To("#microsoft.graph.singleUser"), + UserID: pointer.To(approver.ID), + }) + case "groupMembers": + primaryApprovers = append(primaryApprovers, msgraph.UserSet{ + ODataType: pointer.To("#microsoft.graph.groupMembers"), + GroupID: pointer.To(approver.ID), + }) + } + } + + approvalStages = append(approvalStages, msgraph.ApprovalStage{ + PrimaryApprovers: &primaryApprovers, + }) + } + } else { + approvalStages = *policyRules["Approval_EndUser_Assignment"].Setting.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.required_conditional_access_authentication_context") { + var isEnabled *bool + claimValue := policyRules["AuthenticationContext_EndUser_Assignment"].ClaimValue + + 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 { + 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) + } + + 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[0].RequireMultiFactorAuth { + enabledRules = append(enabledRules, "MultiFactorAuthentication") + } + if model.ActivationRules[0].RequireJustification { + enabledRules = append(enabledRules, "Justification") + } + if model.ActivationRules[0].RequireTicketInfo { + enabledRules = append(enabledRules, "Ticketing") + } + + 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.eligible_assignments.0.admin_notifications") { + updatedRules = append(updatedRules, + expandNotificationSettings( + policyRules["Notification_Admin_Admin_Eligibility"], + 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.active_assignments.0.admin_notifications") { + updatedRules = append(updatedRules, + expandNotificationSettings( + policyRules["Notification_Admin_Admin_Assignment"], + 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.eligible_activations.0.admin_notifications") { + updatedRules = append(updatedRules, + expandNotificationSettings( + policyRules["Notification_Admin_EndUser_Assignment"], + 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.eligible_assignments.0.approver_notifications") { + updatedRules = append(updatedRules, + expandNotificationSettings( + policyRules["Notification_Approver_Admin_Eligibility"], + 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.active_assignments.0.approver_notifications") { + updatedRules = append(updatedRules, + expandNotificationSettings( + policyRules["Notification_Approver_Admin_Assignment"], + 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.eligible_activations.0.approver_notifications") { + updatedRules = append(updatedRules, + expandNotificationSettings( + policyRules["Notification_Approver_EndUser_Assignment"], + 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.eligible_assignments.0.assignee_notifications") { + updatedRules = append(updatedRules, + expandNotificationSettings( + policyRules["Notification_Requestor_Admin_Eligibility"], + 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.active_assignments.0.assignee_notifications") { + updatedRules = append(updatedRules, + expandNotificationSettings( + policyRules["Notification_Requestor_Admin_Assignment"], + 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.eligible_activations.0.assignee_notifications") { + updatedRules = append(updatedRules, + expandNotificationSettings( + policyRules["Notification_Requestor_EndUser_Assignment"], + model.NotificationRules[0].EligibleActivations[0].AssigneeNotifications[0], + metadata.ResourceData.HasChange("notification_rules.0.eligible_activations.0.assignee_notifications.0.additional_recipients"), + ), + ) + } + + return &msgraph.UnifiedRoleManagementPolicy{ + ID: policy.ID, + Rules: pointer.To(updatedRules), + }, 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 + 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{ + "admin_notifications": { + Description: "Admin notification settings", + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: notificationSettingsSchema(), + }, + }, + "approver_notifications": { + Description: "Approver notification settings", + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: notificationSettingsSchema(), + }, + }, + "assignee_notifications": { + Description: "Assignee notification settings", + 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, + }, + }, + } +} 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..99f6ac6803 --- /dev/null +++ b/internal/services/policies/group_role_management_policy_resource_test.go @@ -0,0 +1,170 @@ +// 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(), + ), + }, + data.ImportStep(), + }) +} + +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(), + ), + }, + data.ImportStep(), + }) + +} + +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" { + group_id = azuread_group.pam.id + role_id = "member" + + eligible_assignment_rules { + expiration_required = false + } + + active_assignment_rules { + 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"] + } + } + } +} +`, 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" { + group_id = azuread_group.pam.id + role_id = "owner" + + eligible_assignment_rules { + expiration_required = false + } + + active_assignment_rules { + expire_after = "P90D" + } + + 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"] + } + } + } +} +`, data.RandomString, data.RandomPassword) +} 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..0c39de8d1c --- /dev/null +++ b/internal/services/policies/parse/role_management_policy.go @@ -0,0 +1,132 @@ +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 ScopeId: %+v", err) + } + + if _, err := validation.IsUUID(id.PolicyId, "PolicyId"); len(err) > 0 { + return nil, fmt.Errorf("parsing RoleManagementPolicyId PolicyId: %+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 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 strings.Join([]string{id.ScopeType, id.ScopeId, id.PolicyId}, "_") +} + +func (id *RoleManagementPolicyId) String() string { + 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 new file mode 100644 index 0000000000..7422a5a8e0 --- /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) + } + + 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) + } + case msgraph.UnifiedRoleManagementPolicyScopeGroup: + if id.RoleDefinitionId != msgraph.PrivilegedAccessGroupRelationshipMember && + id.RoleDefinitionId != msgraph.PrivilegedAccessGroupRelationshipOwner { + return nil, fmt.Errorf("parsing RoleManagementPolicyAssignmentId: invalid RoleDefinitionId") + } + default: + return nil, fmt.Errorf("parsing RoleManagementPolicyAssignmentId: invalid ScopeType") + } + + return &id, nil +} + +func (id *RoleManagementPolicyAssignmentId) ID() string { + 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", id.ID()) +} 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) +} diff --git a/internal/services/policies/registration.go b/internal/services/policies/registration.go index 5d95f81304..da0786bd47 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,17 @@ 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{ + GroupRoleManagementPolicyDataSource{}, + } +} + +// Resources returns the typed Resources supported by this service +func (r Registration) Resources() []sdk.Resource { + return []sdk.Resource{ + GroupRoleManagementPolicyResource{}, + } +} 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