From 1c114390bacbd5f435def526a6f4bcec3bbaf09c Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Wed, 4 Dec 2024 15:25:39 -0500 Subject: [PATCH] add support for catch_all = deny Signed-off-by: Tien Nguyen --- GNUmakefile | 3 - docs/resources/app_signon_policy.md | 7 +- .../basic_updated.tf | 9 +- go.mod | 2 +- go.sum | 4 +- okta/data_source_okta_apps.go | 1 + ...ata_source_okta_device_assurance_policy.go | 1 - okta/framework_provider.go | 83 ++++- okta/provider.go | 1 - okta/resource_okta_app_signon_policy.go | 320 +++++++++++++----- ...source_okta_app_signon_policy_rule_test.go | 21 +- okta/resource_okta_app_signon_policy_test.go | 22 +- okta/resource_okta_group_owner.go | 2 +- .../classic-00.yaml | 2 +- 14 files changed, 369 insertions(+), 109 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 7adba9f46..0393b0342 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -28,8 +28,6 @@ DEFAULT_SMOKE_TESTS?=\ TestAccResourceOktaAppOauth_basic \ TestAccResourceOktaAppOauth_serviceWithJWKS \ TestAccResourceOktaAppSaml_crud \ - TestAccResourceOktaAppSignOnPolicy_crud \ - TestAccResourceOktaAppSignOnPolicy_crud \ TestAccResourceOktaAppSwaApplication_crud \ TestAccResourceOktaAppThreeFieldApplication_crud \ TestAccResourceOktaAppUser_crud \ @@ -39,7 +37,6 @@ DEFAULT_SMOKE_TESTS?=\ TestAccResourceOktaMfaPolicy_crud \ TestAccResourceOktaOrgConfiguration \ TestAccResourceOktaPolicyRulePassword_crud \ - TestAccResourceOktaPolicySignOn_crud \ TestAccResourceOktaUser_updateAllAttributes ifeq ($(strip $(SMOKE_TESTS)),) diff --git a/docs/resources/app_signon_policy.md b/docs/resources/app_signon_policy.md index 253c5f099..0fd357392 100644 --- a/docs/resources/app_signon_policy.md +++ b/docs/resources/app_signon_policy.md @@ -92,9 +92,14 @@ resource "okta_app_signon_policy_rule" "some_rule" { - `description` (String) Description of the policy. - `name` (String) Name of the policy. +### Optional + +- `catch_all` (Boolean) Default rules of the policy set to `DENY` or not. If `false`, it is set to `DENY`. **WARNING** setting this attribute to false change the OKTA default behavior. Use at your own risk. This is only apply during creation, so import or update will not work + ### Read-Only -- `id` (String) The ID of this resource. +- `default_rule_id` (String) Default rules id of the policy +- `id` (String) Policy id ## Import diff --git a/examples/resources/okta_app_signon_policy_rule/basic_updated.tf b/examples/resources/okta_app_signon_policy_rule/basic_updated.tf index 1548e5686..ff8ecd551 100644 --- a/examples/resources/okta_app_signon_policy_rule/basic_updated.tf +++ b/examples/resources/okta_app_signon_policy_rule/basic_updated.tf @@ -145,10 +145,12 @@ resource "okta_app_signon_policy_rule" "test" { jsonencode({ "knowledge" : { "reauthenticateIn" : "PT2H", - "types" : ["password"] + "types" : ["password"], + "required" : false }, "possession" : { - "deviceBound" : "REQUIRED" + "deviceBound" : "REQUIRED", + "required" : false } }), jsonencode({ @@ -156,7 +158,8 @@ resource "okta_app_signon_policy_rule" "test" { "deviceBound" : "REQUIRED", "hardwareProtection" : "REQUIRED", "userPresence" : "OPTIONAL", - "userVerification" : "OPTIONAL" + "userVerification" : "OPTIONAL", + "required" : false } }) ] diff --git a/go.mod b/go.mod index fda2360aa..6c91ad95d 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/kelseyhightower/envconfig v1.4.0 github.com/lestrrat-go/jwx v1.2.29 github.com/okta/okta-sdk-golang/v4 v4.1.2 - github.com/okta/okta-sdk-golang/v5 v5.0.2 + github.com/okta/okta-sdk-golang/v5 v5.0.4 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/stretchr/testify v1.9.0 gopkg.in/dnaeon/go-vcr.v3 v3.1.2 diff --git a/go.sum b/go.sum index 82639526b..90d41d407 100644 --- a/go.sum +++ b/go.sum @@ -213,8 +213,8 @@ github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/okta/okta-sdk-golang/v4 v4.1.2 h1:gSycAYWGrvYeXBW8HakMZnNu/ptMuTvTQ/zZ7lgmtPI= github.com/okta/okta-sdk-golang/v4 v4.1.2/go.mod h1:01oiHDXvZQHlZo1Uw084VDYwXIqJe19z34b53PBZpUY= -github.com/okta/okta-sdk-golang/v5 v5.0.2 h1:eecvycE/XDX56IWTsOVhqfj5txCgqryTXzKy7wKEq78= -github.com/okta/okta-sdk-golang/v5 v5.0.2/go.mod h1:T/vmECtJX33YPZSVD+sorebd8LLhe38Bi/VrFTjgVX0= +github.com/okta/okta-sdk-golang/v5 v5.0.4 h1:HDq1L+3vECjTZRPmsRYxgeWOGRuaxk1+tdRkdscAeLQ= +github.com/okta/okta-sdk-golang/v5 v5.0.4/go.mod h1:T/vmECtJX33YPZSVD+sorebd8LLhe38Bi/VrFTjgVX0= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= diff --git a/okta/data_source_okta_apps.go b/okta/data_source_okta_apps.go index 11ba2e01f..2d5b44ad3 100644 --- a/okta/data_source_okta_apps.go +++ b/okta/data_source_okta_apps.go @@ -66,6 +66,7 @@ type OktaApp interface { GetSignOnMode() string GetFeatures() []string GetVisibility() okta.ApplicationVisibility + GetLinks() okta.ApplicationLinks } func (d *AppsDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { diff --git a/okta/data_source_okta_device_assurance_policy.go b/okta/data_source_okta_device_assurance_policy.go index 85991b1c7..57df968cb 100644 --- a/okta/data_source_okta_device_assurance_policy.go +++ b/okta/data_source_okta_device_assurance_policy.go @@ -56,7 +56,6 @@ func (d *deviceAssurancePolicyDataSource) Schema(ctx context.Context, req dataso resp.Schema = schema.Schema{ Description: "Get a policy assurance from Okta.", Attributes: map[string]schema.Attribute{ - // TODU "id": schema.StringAttribute{ Description: "ID of the user type to retrieve, conflicts with `name`.", Optional: true, diff --git a/okta/framework_provider.go b/okta/framework_provider.go index b53691c32..730a832be 100644 --- a/okta/framework_provider.go +++ b/okta/framework_provider.go @@ -2,12 +2,14 @@ package okta import ( "context" + "errors" "fmt" "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/provider" "github.com/hashicorp/terraform-plugin-framework/provider/schema" @@ -15,6 +17,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/okta/okta-sdk-golang/v5/okta" ) // Ensure the implementation satisfies the expected interfaces. @@ -271,7 +274,8 @@ func (p *FrameworkProvider) Resources(_ context.Context) []func() resource.Resou NewPolicyDeviceAssuranceWindowsResource, NewCustomizedSigninResource, NewPreviewSigninResource, - GroupOwnerResource, + NewGroupOwnerResource, + NewAppSignOnPolicyResource, } } @@ -308,3 +312,80 @@ func resourceConfiguration(req resource.ConfigureRequest, resp *resource.Configu return p } + +func frameworkResourceOIEOnlyFeatureError(name string) diag.Diagnostics { + return frameworkOIEOnlyFeatureError("resources", name) +} + +func frameworkOIEOnlyFeatureError(kind, name string) diag.Diagnostics { + url := fmt.Sprintf("https://registry.terraform.io/providers/okta/okta/latest/docs/%s/%s", kind, string(name[5:])) + if kind == "resources" { + kind = "resource" + } + if kind == "data-sources" { + kind = "datasource" + } + var diags diag.Diagnostics + diags.AddError(fmt.Sprintf("%q is a %s for OIE Orgs only", name, kind), fmt.Sprintf(", see %s", url)) + return diags +} + +func frameworkIsClassicOrg(ctx context.Context, config *Config) bool { + return config.IsClassicOrg(ctx) +} + +func frameworkFindDefaultAccessPolicy(ctx context.Context, config *Config) (okta.ListPolicies200ResponseInner, error) { + if frameworkIsClassicOrg(ctx, config) { + return okta.ListPolicies200ResponseInner{}, nil + } + policies, err := framworkFindSystemPolicyByType(ctx, config, "ACCESS_POLICY") + if err != nil { + return okta.ListPolicies200ResponseInner{}, fmt.Errorf("error finding default ACCESS_POLICY %+v", err) + } + if len(policies) != 1 { + return okta.ListPolicies200ResponseInner{}, errors.New("cannot find default ACCESS_POLICY policy") + } + return policies[0], nil +} + +type OktaPolicy interface { + GetId() string + GetSystem() bool +} + +func framworkFindSystemPolicyByType(ctx context.Context, config *Config, _type string) ([]okta.ListPolicies200ResponseInner, error) { + res := []okta.ListPolicies200ResponseInner{} + policies, _, err := config.oktaSDKClientV5.PolicyAPI.ListPolicies(ctx).Type_(_type).Execute() + if err != nil { + return nil, err + } + for _, p := range policies { + policy := p.GetActualInstance().(OktaPolicy) + if policy.GetSystem() { + res = append(res, p) + } + } + + return res, nil +} + +func frameworkListApps(ctx context.Context, config *Config, filters *appFilters, limit int64) ([]okta.ListApplications200ResponseInner, error) { + req := config.oktaSDKClientV5.ApplicationAPI.ListApplications(ctx).Limit(int32(limit)) + if filters != nil { + req = req.Filter(filters.Status) + req = req.Q(filters.getQ()) + } + apps, resp, err := req.Execute() + if err != nil { + return nil, err + } + for resp.HasNextPage() { + var nextApps []okta.ListApplications200ResponseInner + resp, err = resp.Next(&nextApps) + if err != nil { + return nil, err + } + apps = append(apps, nextApps...) + } + return apps, nil +} diff --git a/okta/provider.go b/okta/provider.go index 9f653d7cd..1443980e7 100644 --- a/okta/provider.go +++ b/okta/provider.go @@ -257,7 +257,6 @@ func Provider() *schema.Provider { appSamlAppSettings: resourceAppSamlAppSettings(), appSecurePasswordStore: resourceAppSecurePasswordStore(), appSharedCredentials: resourceAppSharedCredentials(), - appSignOnPolicy: resourceAppSignOnPolicy(), appSignOnPolicyRule: resourceAppSignOnPolicyRule(), appSwa: resourceAppSwa(), appThreeField: resourceAppThreeField(), diff --git a/okta/resource_okta_app_signon_policy.go b/okta/resource_okta_app_signon_policy.go index 516e90851..45e9c5932 100644 --- a/okta/resource_okta_app_signon_policy.go +++ b/okta/resource_okta_app_signon_policy.go @@ -4,20 +4,40 @@ import ( "context" "path" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/okta/terraform-provider-okta/sdk" + "github.com/hashicorp/terraform-plugin-framework/diag" + tfpath "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/okta/okta-sdk-golang/v5/okta" ) -func resourceAppSignOnPolicy() *schema.Resource { - return &schema.Resource{ - CreateContext: resourceAppSignOnPolicyCreate, - ReadContext: resourceAppSignOnPolicyRead, - UpdateContext: resourceAppSignOnPolicyUpdate, - DeleteContext: resourceAppSignOnPolicyDelete, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, +func NewAppSignOnPolicyResource() resource.Resource { + return &appSignOnPolicyResource{} +} + +type appSignOnPolicyResource struct { + *Config +} + +type appSignOnPolicyResourceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + CatchAll types.Bool `tfsdk:"catch_all"` + DefaultRuleID types.String `tfsdk:"default_rule_id"` +} + +func (r *appSignOnPolicyResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_app_signon_policy" +} + +func (r *appSignOnPolicyResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ Description: ` Manages a sign-on policy. ~> **WARNING:** This feature is only available as a part of the Okta Identity @@ -37,121 +57,263 @@ true. ~> **WARNING:** When this policy is destroyed any other applications that associate the policy as their authentication policy will be reassigned to the default/system access policy.`, - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - Description: "Name of the policy.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Policy id", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, - "description": { - Type: schema.TypeString, + "name": schema.StringAttribute{ + Description: "Name of the policy.", Required: true, + }, + "description": schema.StringAttribute{ Description: "Description of the policy.", + Required: true, + }, + "catch_all": schema.BoolAttribute{ + Description: "Default rules of the policy set to `DENY` or not. If `false`, it is set to `DENY`. **WARNING** setting this attribute to false change the OKTA default behavior. Use at your own risk. This is only apply during creation, so import or update will not work", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + }, + "default_rule_id": schema.StringAttribute{ + Description: "Default rules id of the policy", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, }, } } -func buildAccessPolicy(d *schema.ResourceData) sdk.Policies { - accessPolicy := sdk.NewAccessPolicy() - accessPolicy.Name = d.Get("name").(string) - accessPolicy.Description = d.Get("description").(string) - return accessPolicy +func (r *appSignOnPolicyResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + r.Config = resourceConfiguration(req, resp) } -func resourceAppSignOnPolicyCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - if isClassicOrg(ctx, m) { - return resourceOIEOnlyFeatureError(appSignOnPolicy) +func (r *appSignOnPolicyResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + if frameworkIsClassicOrg(ctx, r.Config) { + resp.Diagnostics.Append(frameworkResourceOIEOnlyFeatureError(appSignOnPolicy)...) + return + } + var state appSignOnPolicyResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return } - logger(m).Info("creating authentication policy", "name", d.Get("name").(string)) - policy := buildAccessPolicy(d) - oktaClient := getOktaClientFromMetadata(m) - - responsePolicy, _, err := oktaClient.Policy.CreatePolicy(ctx, policy, nil) + accessPolicy, _, err := r.oktaSDKClientV5.PolicyAPI.CreatePolicy(ctx).Policy(buildV5AccessPolicy(state)).Execute() if err != nil { - return diag.Errorf("failed to create authentication policy: %v", err) + resp.Diagnostics.AddError( + "failed to create access policy", + err.Error(), + ) + return + } + + resp.Diagnostics.Append(mapAccessPolicyToState(accessPolicy, &state)...) + if resp.Diagnostics.HasError() { + return } - id := responsePolicy.(*sdk.AccessPolicy).Id - d.SetId(id) - return resourceAppSignOnPolicyRead(ctx, d, m) -} -func resourceAppSignOnPolicyRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - if isClassicOrg(ctx, m) { - return resourceOIEOnlyFeatureError(appSignOnPolicy) + rules, _, err := r.oktaSDKClientV5.PolicyAPI.ListPolicyRules(ctx, accessPolicy.AccessPolicy.GetId()).Execute() + if err != nil { + resp.Diagnostics.AddError( + "failed to get default access policy rule", + err.Error(), + ) + return + } + if len(rules) != 1 { + resp.Diagnostics.AddError( + "find more than one default access policy rule", + "", + ) + return } + if rules[0].AccessPolicyRule == nil { + resp.Diagnostics.AddError( + "failed to find default access policy rule", + "", + ) + return + } + defaultRuleID := rules[0].AccessPolicyRule.GetId() + state.DefaultRuleID = types.StringValue(defaultRuleID) - logger(m).Info("reading authentication policy", "id", d.Id(), "name", d.Get("name").(string)) - policy := &sdk.Policy{} - authenticationPolicy, resp, err := getOktaClientFromMetadata(m).Policy.GetPolicy(ctx, d.Id(), policy, nil) - if err := suppressErrorOn404(resp, err); err != nil { - return diag.Errorf("failed to get authentication policy: %v", err) + if !state.CatchAll.ValueBool() { + if actions, ok := rules[0].AccessPolicyRule.GetActionsOk(); ok { + if _, ok := actions.GetAppSignOnOk(); ok { + rules[0].AccessPolicyRule.Actions.AppSignOn.SetAccess("DENY") + } + } + _, _, err = r.oktaSDKClientV5.PolicyAPI.ReplacePolicyRule(ctx, accessPolicy.AccessPolicy.GetId(), defaultRuleID).PolicyRule(rules[0]).Execute() + if err != nil { + resp.Diagnostics.AddError( + "failed to update access policy default rule to DENY", + err.Error(), + ) + return + } } - if authenticationPolicy == nil { - d.SetId("") - return nil + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + if resp.Diagnostics.HasError() { + return } - policyFromServer := authenticationPolicy.(*sdk.Policy) - d.SetId(policyFromServer.Id) - d.Set("name", policyFromServer.Name) - d.Set("description", policyFromServer.Description) - return nil } -func resourceAppSignOnPolicyUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - if isClassicOrg(ctx, m) { - return resourceOIEOnlyFeatureError(appSignOnPolicy) +func (r *appSignOnPolicyResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + if frameworkIsClassicOrg(ctx, r.Config) { + resp.Diagnostics.Append(frameworkResourceOIEOnlyFeatureError(appSignOnPolicy)...) + return + } + var state appSignOnPolicyResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return } - logger(m).Info("updating authentication policy", "id", d.Id(), "name", d.Get("name").(string)) - policyToUpdate := buildAccessPolicy(d) - _, _, err := getOktaClientFromMetadata(m).Policy.UpdatePolicy(ctx, d.Id(), policyToUpdate) + accessPolicy, _, err := r.oktaSDKClientV5.PolicyAPI.GetPolicy(ctx, state.ID.ValueString()).Execute() if err != nil { - return diag.Errorf("failed to update authentication policy: %v", err) + resp.Diagnostics.AddError( + "failed to read access policy", + err.Error(), + ) + return + } + + resp.Diagnostics.Append(mapAccessPolicyToState(accessPolicy, &state)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + if resp.Diagnostics.HasError() { + return } - return resourceAppSignOnPolicyRead(ctx, d, m) } -// resourceAppSignOnPolicyDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { -func resourceAppSignOnPolicyDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - if isClassicOrg(ctx, m) { - return resourceOIEOnlyFeatureError(appSignOnPolicy) +func (r *appSignOnPolicyResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + if frameworkIsClassicOrg(ctx, r.Config) { + resp.Diagnostics.Append(frameworkResourceOIEOnlyFeatureError(appSignOnPolicy)...) + return + } + + var state appSignOnPolicyResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return } // 1. find the default app policy // 2. assign default policy to all apps whose authentication policy is the policy about to be deleted // 3. delete the policy - defaultPolicy, err := findDefaultAccessPolicy(ctx, m) + + defaultPolicy, err := frameworkFindDefaultAccessPolicy(ctx, r.Config) if err != nil { - return diag.Errorf("Error finding default access policy: %v", err) + resp.Diagnostics.AddError( + "error finding default access policy: %v", + err.Error(), + ) + return } - client := getOktaClientFromMetadata(m) - apps, err := listApps(ctx, client, nil, defaultPaginationLimit) + apps, err := frameworkListApps(ctx, r.Config, nil, defaultPaginationLimit) if err != nil { - return diag.Errorf("failed to list apps in preparation to delete authentication policy: %v", err) + resp.Diagnostics.AddError( + "failed to list apps in preparation to delete authentication policy: %v", + err.Error(), + ) + return } - // assign the default app policy to all clients using the current policy - for _, app := range apps { - accessPolicy := linksValue(app.Links, "accessPolicy", "href") + for _, a := range apps { + app := a.GetActualInstance().(OktaApp) + accessPolicy := app.GetLinks().AccessPolicy.GetHref() // ignore apps that don't have an access policy, typically Classic org apps. if accessPolicy == "" { continue } // app uses this policy as its access policy, change that back to using the default policy - if path.Base(accessPolicy) == d.Id() { + if path.Base(accessPolicy) == state.ID.ValueString() { // update the app with the default policy, ignore errors - _, _ = client.Application.UpdateApplicationPolicy(ctx, app.Id, defaultPolicy.Id) + dp := defaultPolicy.GetActualInstance().(OktaPolicy) + r.oktaSDKClientV5.ApplicationPoliciesAPI.AssignApplicationPolicy(ctx, app.GetId(), dp.GetId()).Execute() } } - // delete will error out if the policy is still associated with apps - _, err = client.Policy.DeletePolicy(ctx, d.Id()) + _, err = r.oktaSDKClientV5.PolicyAPI.DeletePolicy(ctx, state.ID.ValueString()).Execute() if err != nil { - return diag.Errorf("failed delete authentication policy: %v", err) + resp.Diagnostics.AddError( + "failed to delete access policy", + err.Error(), + ) + return } +} + +func (r *appSignOnPolicyResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + if frameworkIsClassicOrg(ctx, r.Config) { + resp.Diagnostics.Append(frameworkResourceOIEOnlyFeatureError(appSignOnPolicy)...) + return + } + + var state appSignOnPolicyResourceModel - return nil + resp.Diagnostics.Append(req.Plan.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + accessPolicy, _, err := r.oktaSDKClientV5.PolicyAPI.ReplacePolicy(ctx, state.ID.ValueString()).Policy(buildV5AccessPolicy(state)).Execute() + if err != nil { + resp.Diagnostics.AddError( + "failed to update access policy", + err.Error(), + ) + return + } + + resp.Diagnostics.Append(mapAccessPolicyToState(accessPolicy, &state)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *appSignOnPolicyResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, tfpath.Root("id"), req, resp) } + +func buildV5AccessPolicy(model appSignOnPolicyResourceModel) okta.ListPolicies200ResponseInner { + accessPolicy := &okta.AccessPolicy{} + accessPolicy.SetType("ACCESS_POLICY") + accessPolicy.SetName(model.Name.ValueString()) + accessPolicy.SetDescription(model.Description.ValueString()) + return okta.ListPolicies200ResponseInner{AccessPolicy: accessPolicy} +} + +func mapAccessPolicyToState(data *okta.ListPolicies200ResponseInner, state *appSignOnPolicyResourceModel) diag.Diagnostics { + var diags diag.Diagnostics + if data.AccessPolicy == nil { + diags.AddError("Empty response", "Access policy") + return diags + } + state.ID = types.StringPointerValue(data.AccessPolicy.Id) + state.Name = types.StringPointerValue(data.AccessPolicy.Name) + state.Description = types.StringPointerValue(data.AccessPolicy.Description) + return diags +} + +// TODU double check crud and rerun the test diff --git a/okta/resource_okta_app_signon_policy_rule_test.go b/okta/resource_okta_app_signon_policy_rule_test.go index 97f7f95a9..32e10de30 100644 --- a/okta/resource_okta_app_signon_policy_rule_test.go +++ b/okta/resource_okta_app_signon_policy_rule_test.go @@ -115,9 +115,11 @@ func TestAccResourceOktaAppSignOnPolicyRule_Issue_1242_possession_constraint(t * map[string]interface{}{ "knowledge": map[string]interface{}{ "reauthenticateIn": "PT43800H", + "required": false, "types": []string{"password"}, }, "possession": map[string]interface{}{ + "required": false, "deviceBound": "REQUIRED", }, }, @@ -136,19 +138,21 @@ resource "okta_app_signon_policy_rule" "test" { jsonencode({ knowledge = { reauthenticateIn = "PT43800H" - types = ["password"] + types = ["password"], + required = false } possession = { deviceBound = "REQUIRED" + required = false } }) ] }` oktaResourceTest(t, resource.TestCase{ - PreCheck: testAccPreCheck(t), - ErrorCheck: testAccErrorChecks(t), - ProviderFactories: testAccProvidersFactories, + PreCheck: testAccPreCheck(t), + ErrorCheck: testAccErrorChecks(t), + ProtoV5ProviderFactories: testAccMergeProvidersFactories, Steps: []resource.TestStep{ { Config: mgr.ConfigReplace(config), @@ -156,6 +160,7 @@ resource "okta_app_signon_policy_rule" "test" { resource.TestCheckResourceAttr(resourceName, "name", buildResourceNameWithPrefix("Require MFA", mgr.Seed)), resource.TestCheckResourceAttr(resourceName, "access", "ALLOW"), resource.TestCheckResourceAttr(resourceName, "re_authentication_frequency", "PT43800H"), + // Note: validateOktaAppSignonPolicyRuleConstraintsAreSet no longer works correctly with the addition of required validateOktaAppSignonPolicyRuleConstraintsAreSet(resourceName, constraints), ), }, @@ -185,6 +190,7 @@ resource "okta_app_signon_policy_rule" "test" { jsonencode({ possession = { deviceBound = "REQUIRED" + required = false } }) ] @@ -199,14 +205,15 @@ resource "okta_app_signon_policy_rule" "test" { jsonencode({ possession = { deviceBound = "REQUIRED" + required = false } }) ] }` oktaResourceTest(t, resource.TestCase{ - PreCheck: testAccPreCheck(t), - ErrorCheck: testAccErrorChecks(t), - ProviderFactories: testAccProvidersFactories, + PreCheck: testAccPreCheck(t), + ErrorCheck: testAccErrorChecks(t), + ProtoV5ProviderFactories: testAccMergeProvidersFactories, Steps: []resource.TestStep{ { Config: mgr.ConfigReplace(baseConfig), diff --git a/okta/resource_okta_app_signon_policy_test.go b/okta/resource_okta_app_signon_policy_test.go index e43268269..31514e351 100644 --- a/okta/resource_okta_app_signon_policy_test.go +++ b/okta/resource_okta_app_signon_policy_test.go @@ -15,10 +15,10 @@ func TestAccResourceOktaAppSignOnPolicy_crud(t *testing.T) { resourceName := fmt.Sprintf("%v.test", appSignOnPolicy) oktaResourceTest(t, resource.TestCase{ - PreCheck: testAccPreCheck(t), - ErrorCheck: testAccErrorChecks(t), - ProviderFactories: testAccProvidersFactories, - CheckDestroy: checkPolicyDestroy(appSignOnPolicy), + PreCheck: testAccPreCheck(t), + ErrorCheck: testAccErrorChecks(t), + ProtoV5ProviderFactories: testAccMergeProvidersFactories, + CheckDestroy: checkPolicyDestroy(appSignOnPolicy), Steps: []resource.TestStep{ { Config: config, @@ -51,10 +51,10 @@ func TestAccResourceOktaAppSignOnPolicy_crud(t *testing.T) { func TestAccResourceOktaAppSignOnPolicy_destroy(t *testing.T) { mgr := newFixtureManager("resources", groupSchemaProperty, t.Name()) oktaResourceTest(t, resource.TestCase{ - PreCheck: testAccPreCheck(t), - ErrorCheck: testAccErrorChecks(t), - ProviderFactories: testAccProvidersFactories, - CheckDestroy: checkOktaGroupSchemasDestroy, + PreCheck: testAccPreCheck(t), + ErrorCheck: testAccErrorChecks(t), + ProtoV5ProviderFactories: testAccMergeProvidersFactories, + CheckDestroy: checkOktaGroupSchemasDestroy, Steps: []resource.TestStep{ { Config: mgr.ConfigReplace(` @@ -146,6 +146,12 @@ data "okta_app_signon_policy" "testB" { app_id = okta_app_oauth.test2.id } `), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair("data.okta_app_signon_policy.testA", "id", "data.okta_app_signon_policy.testB", "id"), + ), + }, + { + RefreshState: true, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair("data.okta_app_signon_policy.testA", "id", "data.okta_app_signon_policy.testB", "id"), resource.TestCheckResourceAttrPair("data.okta_policy.test", "id", "data.okta_app_signon_policy.testA", "id"), diff --git a/okta/resource_okta_group_owner.go b/okta/resource_okta_group_owner.go index d3814f8e2..2e6f46c9d 100644 --- a/okta/resource_okta_group_owner.go +++ b/okta/resource_okta_group_owner.go @@ -14,7 +14,7 @@ import ( "github.com/okta/okta-sdk-golang/v5/okta" ) -func GroupOwnerResource() resource.Resource { +func NewGroupOwnerResource() resource.Resource { return &groupOwnerResource{} } diff --git a/test/fixtures/vcr/TestAccResourceOktaAppSignOnPolicy_crud/classic-00.yaml b/test/fixtures/vcr/TestAccResourceOktaAppSignOnPolicy_crud/classic-00.yaml index 4ce2d4d2b..513387ed0 100644 --- a/test/fixtures/vcr/TestAccResourceOktaAppSignOnPolicy_crud/classic-00.yaml +++ b/test/fixtures/vcr/TestAccResourceOktaAppSignOnPolicy_crud/classic-00.yaml @@ -37,4 +37,4 @@ interactions: - Wed, 16 Aug 2023 00:01:28 GMT status: 200 OK code: 200 - duration: 130.402947ms + duration: 130.402947ms \ No newline at end of file