From fe91ce57f0a802837fa590dfb7ae7a3557503f78 Mon Sep 17 00:00:00 2001 From: Pengyu Wang Date: Tue, 27 Jul 2021 01:34:36 -0700 Subject: [PATCH 1/5] Create okta_user_factor_question resource and test (passed) --- .../okta_user_factor_question.tf | 18 ++++ okta/provider.go | 2 + okta/resource_okta_user_factor_question.go | 90 +++++++++++++++++++ ...resource_okta_user_factor_question_test.go | 66 ++++++++++++++ 4 files changed, 176 insertions(+) create mode 100644 examples/okta_user_factor_question/okta_user_factor_question.tf create mode 100644 okta/resource_okta_user_factor_question.go create mode 100644 okta/resource_okta_user_factor_question_test.go diff --git a/examples/okta_user_factor_question/okta_user_factor_question.tf b/examples/okta_user_factor_question/okta_user_factor_question.tf new file mode 100644 index 000000000..45e2a5bed --- /dev/null +++ b/examples/okta_user_factor_question/okta_user_factor_question.tf @@ -0,0 +1,18 @@ +resource "okta_user" "test" { + first_name = "TestAcc" + last_name = "Jones" + login = "john_replace_with_uuid@ledzeppelin.com" + email = "john_replace_with_uuid@ledzeppelin.com" +} + +resource "okta_factor" "test_factor" { + provider_id = "okta_question" + active = true +} + +resource "okta_user_factor_question" "test" { + user_id = okta_user.test.id + security_question_key = "disliked_food" + security_answer = "okta" + depends_on = [okta_factor.test_factor] +} \ No newline at end of file diff --git a/okta/provider.go b/okta/provider.go index 9b235e21b..9a55bb59c 100644 --- a/okta/provider.go +++ b/okta/provider.go @@ -77,6 +77,7 @@ const ( userType = "okta_user_type" userGroupMemberships = "okta_user_group_memberships" userAdminRoles = "okta_user_admin_roles" + userFactorQuestion = "okta_user_factor_question" ) // Provider establishes a client connection to an okta site @@ -233,6 +234,7 @@ func Provider() *schema.Provider { userType: resourceUserType(), userGroupMemberships: resourceUserGroupMemberships(), userAdminRoles: resourceUserAdminRoles(), + userFactorQuestion: resourceUserFactorQuestion(), // The day I realized I was naming stuff wrong :'-( "okta_idp": deprecateIncorrectNaming(resourceIdpOidc(), idpOidc), diff --git a/okta/resource_okta_user_factor_question.go b/okta/resource_okta_user_factor_question.go new file mode 100644 index 000000000..89e33cb24 --- /dev/null +++ b/okta/resource_okta_user_factor_question.go @@ -0,0 +1,90 @@ +package okta + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/okta/okta-sdk-golang/v2/okta" +) + +func resourceUserFactorQuestion() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceUserFactorQuestionCreate, + ReadContext: resourceUserFactorQuestionRead, + UpdateContext: resourceUserFactorQuestionUpdate, + DeleteContext: resourceUserFactorQuestionDelete, + Importer: &schema.ResourceImporter{ + StateContext: func(_ context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + _ = d.Set("user_id", d.Id()) + d.SetId(d.Id()) + return []*schema.ResourceData{d}, nil + }, + }, + Description: "Resource to manage a set of Factors for a specific user,", + Schema: map[string]*schema.Schema{ + "user_id": { + Type: schema.TypeString, + Required: true, + Description: "ID of a Okta User", + ForceNew: true, + }, + "security_question_key": { + Type: schema.TypeString, + Required: true, + Description: "User Password Security Question", + }, + "security_answer": { + Type: schema.TypeString, + Required: true, + Sensitive: true, + ValidateDiagFunc: stringLenBetween(4, 1000), + Description: "User Password Security Answer", + }, + }, + } +} + +func resourceUserFactorQuestionCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + userId := d.Get("user_id").(string) + factorProfile := okta.NewSecurityQuestionUserFactorProfile() + factorProfile.Question = d.Get("security_question_key").(string) + factorProfile.Answer = d.Get("security_answer").(string) + factor := okta.NewSecurityQuestionUserFactor() + factor.Profile = factorProfile + responseFactor, _, err := getOktaClientFromMetadata(m).UserFactor.EnrollFactor(ctx, userId, factor, nil) + d.SetId(responseFactor.(*okta.UserFactor).Id) + if err != nil { + return diag.FromErr(err) + } + return resourceUserFactorQuestionRead(ctx, d, m) +} + +func resourceUserFactorQuestionRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + var uf *okta.SecurityQuestionUserFactor + _, _, err := getOktaClientFromMetadata(m).UserFactor.GetFactor(ctx, d.Get("user_id").(string), d.Id(), uf) + if err != nil { + return diag.FromErr(err) + } + return nil +} + +func resourceUserFactorQuestionUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + err := resourceUserFactorQuestionDelete(ctx, d, m) + if err != nil { + return err + } + err = resourceUserFactorQuestionCreate(ctx, d, m) + if err != nil { + return err + } + return resourceUserFactorQuestionRead(ctx, d, m) +} + +func resourceUserFactorQuestionDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + _, err := getOktaClientFromMetadata(m).UserFactor.DeleteFactor(ctx, d.Get("user_id").(string), d.Id()) + if err != nil { + return diag.FromErr(err) + } + return nil +} diff --git a/okta/resource_okta_user_factor_question_test.go b/okta/resource_okta_user_factor_question_test.go new file mode 100644 index 000000000..c7e2d8513 --- /dev/null +++ b/okta/resource_okta_user_factor_question_test.go @@ -0,0 +1,66 @@ +package okta + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/okta/okta-sdk-golang/v2/okta" +) + +func TestAccOktaUserFactorQuestion_crud(t *testing.T) { + ri := acctest.RandInt() + + mgr := newFixtureManager("okta_user_factor_question") + config := mgr.GetFixtures("okta_user_factor_question.tf", ri, t) + resourceName := fmt.Sprintf("%s.test", userFactorQuestion) + resource.Test( + t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProvidersFactories, + CheckDestroy: createUserFactorCheckDestroy(userFactorQuestion), + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + // ensureUserFactorExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "security_question_key", "disliked_food"), + resource.TestCheckResourceAttr(resourceName, "security_answer", "okta"), + ), + }, + }, + }) +} + +func createUserFactorCheckDestroy(FactorType string) func(*terraform.State) error { + return func(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != FactorType { + continue + } + userID := rs.Primary.Attributes["user_id"] + ID := rs.Primary.ID + exists, err := doesUserFactorExistsUpstream(userID, ID) + if err != nil { + return err + } + if exists { + return fmt.Errorf("user factor still exists,userID: %s, user factor ID: %s", userID, ID) + } + } + return nil + } +} + +func doesUserFactorExistsUpstream(userId string, factorId string) (bool, error) { + var uf *okta.SecurityQuestionUserFactor + _, resp, err := getOktaClientFromMetadata(testAccProvider.Meta()).UserFactor.GetFactor(context.Background(), userId, factorId, uf) + return doesResourceExist(resp, err) +} + +func ensureUserFactorExists(name string) resource.TestCheckFunc { + return nil +} From 1c3a89ec1c1533f8361ef9620eb0377d1bff634b Mon Sep 17 00:00:00 2001 From: Pengyu Wang Date: Tue, 27 Jul 2021 01:36:15 -0700 Subject: [PATCH 2/5] minor update --- .../okta_user_factor_question.tf | 2 +- okta/resource_okta_user_factor_question_test.go | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/examples/okta_user_factor_question/okta_user_factor_question.tf b/examples/okta_user_factor_question/okta_user_factor_question.tf index 45e2a5bed..26fc81925 100644 --- a/examples/okta_user_factor_question/okta_user_factor_question.tf +++ b/examples/okta_user_factor_question/okta_user_factor_question.tf @@ -13,6 +13,6 @@ resource "okta_factor" "test_factor" { resource "okta_user_factor_question" "test" { user_id = okta_user.test.id security_question_key = "disliked_food" - security_answer = "okta" + security_answer = "meatball" depends_on = [okta_factor.test_factor] } \ No newline at end of file diff --git a/okta/resource_okta_user_factor_question_test.go b/okta/resource_okta_user_factor_question_test.go index c7e2d8513..40c62f5f3 100644 --- a/okta/resource_okta_user_factor_question_test.go +++ b/okta/resource_okta_user_factor_question_test.go @@ -13,7 +13,6 @@ import ( func TestAccOktaUserFactorQuestion_crud(t *testing.T) { ri := acctest.RandInt() - mgr := newFixtureManager("okta_user_factor_question") config := mgr.GetFixtures("okta_user_factor_question.tf", ri, t) resourceName := fmt.Sprintf("%s.test", userFactorQuestion) @@ -26,9 +25,8 @@ func TestAccOktaUserFactorQuestion_crud(t *testing.T) { { Config: config, Check: resource.ComposeTestCheckFunc( - // ensureUserFactorExists(resourceName), resource.TestCheckResourceAttr(resourceName, "security_question_key", "disliked_food"), - resource.TestCheckResourceAttr(resourceName, "security_answer", "okta"), + resource.TestCheckResourceAttr(resourceName, "security_answer", "meatball"), ), }, }, @@ -60,7 +58,3 @@ func doesUserFactorExistsUpstream(userId string, factorId string) (bool, error) _, resp, err := getOktaClientFromMetadata(testAccProvider.Meta()).UserFactor.GetFactor(context.Background(), userId, factorId, uf) return doesResourceExist(resp, err) } - -func ensureUserFactorExists(name string) resource.TestCheckFunc { - return nil -} From 54860df9661628e07ce00d738d2d6ea781a2e202 Mon Sep 17 00:00:00 2001 From: Pengyu Wang Date: Wed, 28 Jul 2021 13:28:11 -0700 Subject: [PATCH 3/5] added user_factor_question branch --- examples/okta_user_factor_question/okta_user_factor_question.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/okta_user_factor_question/okta_user_factor_question.tf b/examples/okta_user_factor_question/okta_user_factor_question.tf index 26fc81925..cd741096b 100644 --- a/examples/okta_user_factor_question/okta_user_factor_question.tf +++ b/examples/okta_user_factor_question/okta_user_factor_question.tf @@ -10,6 +10,7 @@ resource "okta_factor" "test_factor" { active = true } + resource "okta_user_factor_question" "test" { user_id = okta_user.test.id security_question_key = "disliked_food" From e1a0000ea2095405bda1c8823903181ef10d5216 Mon Sep 17 00:00:00 2001 From: bogdanprodan-okta Date: Thu, 29 Jul 2021 00:10:12 +0300 Subject: [PATCH 4/5] Fixed update method, added extra test case, added custom sdk methods --- ...{okta_user_factor_question.tf => basic.tf} | 15 ++- examples/okta_user_factor_question/updated.tf | 18 +++ okta/resource_okta_factor.go | 12 +- okta/resource_okta_user_factor_question.go | 111 +++++++++++++----- ...resource_okta_user_factor_question_test.go | 16 ++- okta/user_factor.go | 24 ++++ sdk/{factor.go => org_factor.go} | 26 ++-- sdk/user_factor.go | 70 +++++++++++ 8 files changed, 232 insertions(+), 60 deletions(-) rename examples/okta_user_factor_question/{okta_user_factor_question.tf => basic.tf} (51%) create mode 100644 examples/okta_user_factor_question/updated.tf create mode 100644 okta/user_factor.go rename sdk/{factor.go => org_factor.go} (62%) create mode 100644 sdk/user_factor.go diff --git a/examples/okta_user_factor_question/okta_user_factor_question.tf b/examples/okta_user_factor_question/basic.tf similarity index 51% rename from examples/okta_user_factor_question/okta_user_factor_question.tf rename to examples/okta_user_factor_question/basic.tf index cd741096b..f05afbbf9 100644 --- a/examples/okta_user_factor_question/okta_user_factor_question.tf +++ b/examples/okta_user_factor_question/basic.tf @@ -1,19 +1,18 @@ resource "okta_user" "test" { first_name = "TestAcc" - last_name = "Jones" - login = "john_replace_with_uuid@ledzeppelin.com" - email = "john_replace_with_uuid@ledzeppelin.com" + last_name = "Jones" + login = "john_replace_with_uuid@ledzeppelin.com" + email = "john_replace_with_uuid@ledzeppelin.com" } resource "okta_factor" "test_factor" { provider_id = "okta_question" - active = true + active = true } - resource "okta_user_factor_question" "test" { user_id = okta_user.test.id - security_question_key = "disliked_food" - security_answer = "meatball" + key = "disliked_food" + answer = "meatball" depends_on = [okta_factor.test_factor] -} \ No newline at end of file +} diff --git a/examples/okta_user_factor_question/updated.tf b/examples/okta_user_factor_question/updated.tf new file mode 100644 index 000000000..88cd14ff6 --- /dev/null +++ b/examples/okta_user_factor_question/updated.tf @@ -0,0 +1,18 @@ +resource "okta_user" "test" { + first_name = "TestAcc" + last_name = "Jones" + login = "john_replace_with_uuid@ledzeppelin.com" + email = "john_replace_with_uuid@ledzeppelin.com" +} + +resource "okta_factor" "test_factor" { + provider_id = "okta_question" + active = true +} + +resource "okta_user_factor_question" "test" { + user_id = okta_user.test.id + key = "name_of_first_plush_toy" + answer = "meatball" + depends_on = [okta_factor.test_factor] +} diff --git a/okta/resource_okta_factor.go b/okta/resource_okta_factor.go index 88fde6ca0..fb7f7951f 100644 --- a/okta/resource_okta_factor.go +++ b/okta/resource_okta_factor.go @@ -58,7 +58,7 @@ func resourceFactor() *schema.Resource { } func resourceFactorPut(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - factor, _, err := getSupplementFromMetadata(m).GetFactor(ctx, d.Get("provider_id").(string)) + factor, _, err := getSupplementFromMetadata(m).GetOrgFactor(ctx, d.Get("provider_id").(string)) if err != nil { return diag.Errorf("failed to find factor: %v", err) } @@ -74,7 +74,7 @@ func resourceFactorPut(ctx context.Context, d *schema.ResourceData, m interface{ } func resourceFactorRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - factor, resp, err := getSupplementFromMetadata(m).GetFactor(ctx, d.Get("provider_id").(string)) + factor, resp, err := getSupplementFromMetadata(m).GetOrgFactor(ctx, d.Get("provider_id").(string)) if err := suppressErrorOn404(resp, err); err != nil { return diag.Errorf("failed to find factor: %v", err) } @@ -91,7 +91,7 @@ func resourceFactorDelete(ctx context.Context, d *schema.ResourceData, m interfa if !d.Get("active").(bool) { return nil } - _, resp, err := getSupplementFromMetadata(m).DeactivateFactor(ctx, d.Id()) + _, resp, err := getSupplementFromMetadata(m).DeactivateOrgFactor(ctx, d.Id()) // http.StatusBadRequest means that factor can not be deactivated if resp != nil && resp.StatusCode == http.StatusBadRequest { return nil @@ -106,14 +106,14 @@ func activateFactor(ctx context.Context, d *schema.ResourceData, m interface{}) var err error id := d.Get("provider_id").(string) if d.Get("active").(bool) { - _, _, err = getSupplementFromMetadata(m).ActivateFactor(ctx, id) + _, _, err = getSupplementFromMetadata(m).ActivateOrgFactor(ctx, id) } else { - _, _, err = getSupplementFromMetadata(m).DeactivateFactor(ctx, id) + _, _, err = getSupplementFromMetadata(m).DeactivateOrgFactor(ctx, id) } return err } -func statusMismatch(d *schema.ResourceData, factor *sdk.Factor) bool { +func statusMismatch(d *schema.ResourceData, factor *sdk.OrgFactor) bool { status := d.Get("active").(bool) // I miss ternary operators diff --git a/okta/resource_okta_user_factor_question.go b/okta/resource_okta_user_factor_question.go index 89e33cb24..e2c699e6e 100644 --- a/okta/resource_okta_user_factor_question.go +++ b/okta/resource_okta_user_factor_question.go @@ -2,10 +2,14 @@ package okta import ( "context" + "fmt" + "net/http" + "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/okta/okta-sdk-golang/v2/okta" + "github.com/okta/terraform-provider-okta/sdk" ) func resourceUserFactorQuestion() *schema.Resource { @@ -14,14 +18,8 @@ func resourceUserFactorQuestion() *schema.Resource { ReadContext: resourceUserFactorQuestionRead, UpdateContext: resourceUserFactorQuestionUpdate, DeleteContext: resourceUserFactorQuestionDelete, - Importer: &schema.ResourceImporter{ - StateContext: func(_ context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - _ = d.Set("user_id", d.Id()) - d.SetId(d.Id()) - return []*schema.ResourceData{d}, nil - }, - }, - Description: "Resource to manage a set of Factors for a specific user,", + Importer: createNestedResourceImporter([]string{"user_id", "id"}), + Description: "Resource to manage a question factor for a user", Schema: map[string]*schema.Schema{ "user_id": { Type: schema.TypeString, @@ -29,62 +27,115 @@ func resourceUserFactorQuestion() *schema.Resource { Description: "ID of a Okta User", ForceNew: true, }, - "security_question_key": { + "key": { Type: schema.TypeString, Required: true, - Description: "User Password Security Question", + Description: "Unique key for question", }, - "security_answer": { + "answer": { Type: schema.TypeString, Required: true, Sensitive: true, ValidateDiagFunc: stringLenBetween(4, 1000), - Description: "User Password Security Answer", + Description: "User password security answer", + }, + "text": { + Type: schema.TypeString, + Computed: true, + Description: "Display text for question", + }, + "status": { + Type: schema.TypeString, + Computed: true, + Description: "User factor status.", }, }, } } func resourceUserFactorQuestionCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - userId := d.Get("user_id").(string) - factorProfile := okta.NewSecurityQuestionUserFactorProfile() - factorProfile.Question = d.Get("security_question_key").(string) - factorProfile.Answer = d.Get("security_answer").(string) - factor := okta.NewSecurityQuestionUserFactor() - factor.Profile = factorProfile - responseFactor, _, err := getOktaClientFromMetadata(m).UserFactor.EnrollFactor(ctx, userId, factor, nil) - d.SetId(responseFactor.(*okta.UserFactor).Id) + err := validateQuestionKey(ctx, d, m) if err != nil { return diag.FromErr(err) } + sq := buildUserFactorQuestion(d) + _, err = getSupplementFromMetadata(m).EnrollUserFactor(ctx, d.Get("user_id").(string), sq, nil) + if err != nil { + return diag.Errorf("failed to enroll user question factor: %v", err) + } + d.SetId(sq.Id) return resourceUserFactorQuestionRead(ctx, d, m) } func resourceUserFactorQuestionRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - var uf *okta.SecurityQuestionUserFactor - _, _, err := getOktaClientFromMetadata(m).UserFactor.GetFactor(ctx, d.Get("user_id").(string), d.Id(), uf) - if err != nil { - return diag.FromErr(err) + var uf okta.SecurityQuestionUserFactor + resp, err := getSupplementFromMetadata(m).GetUserFactor(ctx, d.Get("user_id").(string), d.Id(), &uf) + if err := suppressErrorOn404(resp, err); err != nil { + return diag.Errorf("failed to get user question factor: %v", err) + } + if uf.Id == "" { + d.SetId("") + return nil } + _ = d.Set("status", uf.Status) + _ = d.Set("key", uf.Profile.Question) + _ = d.Set("text", uf.Profile.QuestionText) return nil } func resourceUserFactorQuestionUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - err := resourceUserFactorQuestionDelete(ctx, d, m) + err := validateQuestionKey(ctx, d, m) if err != nil { - return err + return diag.FromErr(err) } - err = resourceUserFactorQuestionCreate(ctx, d, m) + sq := &sdk.SecurityQuestionUserFactor{ + Profile: &okta.SecurityQuestionUserFactorProfile{ + Answer: d.Get("answer").(string), + Question: d.Get("key").(string), + }, + } + _, err = getSupplementFromMetadata(m).UpdateUserFactor(ctx, d.Get("user_id").(string), d.Id(), sq) if err != nil { - return err + return diag.Errorf("failed to update user question factor: %v", err) } return resourceUserFactorQuestionRead(ctx, d, m) } func resourceUserFactorQuestionDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - _, err := getOktaClientFromMetadata(m).UserFactor.DeleteFactor(ctx, d.Get("user_id").(string), d.Id()) + resp, err := getOktaClientFromMetadata(m).UserFactor.DeleteFactor(ctx, d.Get("user_id").(string), d.Id()) if err != nil { - return diag.FromErr(err) + // disabled factor can not be removed + if resp != nil && resp.StatusCode == http.StatusBadRequest { + return nil + } + return diag.Errorf("failed to delete user question factor: %v", err) } return nil } + +func buildUserFactorQuestion(d *schema.ResourceData) *sdk.SecurityQuestionUserFactor { + return &sdk.SecurityQuestionUserFactor{ + FactorType: "question", + Provider: "OKTA", + Profile: &okta.SecurityQuestionUserFactorProfile{ + Answer: d.Get("answer").(string), + Question: d.Get("key").(string), + }, + } +} + +func validateQuestionKey(ctx context.Context, d *schema.ResourceData, m interface{}) error { + sq, _, err := getOktaClientFromMetadata(m).UserFactor.ListSupportedSecurityQuestions(ctx, d.Get("user_id").(string)) + if err != nil { + return fmt.Errorf("failed to list security question keys: %v", err) + } + keys := make([]string, len(sq)) + for i := range sq { + if sq[i].Question == d.Get("key").(string) { + return nil + } + keys[i] = sq[i].Question + } + return fmt.Errorf("'%s' is missing from the available questions keys, please use one of [%s]", + d.Get("key").(string), strings.Join(keys, ",")) +} diff --git a/okta/resource_okta_user_factor_question_test.go b/okta/resource_okta_user_factor_question_test.go index 40c62f5f3..e0a7744ab 100644 --- a/okta/resource_okta_user_factor_question_test.go +++ b/okta/resource_okta_user_factor_question_test.go @@ -14,7 +14,8 @@ import ( func TestAccOktaUserFactorQuestion_crud(t *testing.T) { ri := acctest.RandInt() mgr := newFixtureManager("okta_user_factor_question") - config := mgr.GetFixtures("okta_user_factor_question.tf", ri, t) + config := mgr.GetFixtures("basic.tf", ri, t) + updated := mgr.GetFixtures("updated.tf", ri, t) resourceName := fmt.Sprintf("%s.test", userFactorQuestion) resource.Test( t, resource.TestCase{ @@ -25,8 +26,17 @@ func TestAccOktaUserFactorQuestion_crud(t *testing.T) { { Config: config, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "security_question_key", "disliked_food"), - resource.TestCheckResourceAttr(resourceName, "security_answer", "meatball"), + resource.TestCheckResourceAttr(resourceName, "key", "disliked_food"), + resource.TestCheckResourceAttr(resourceName, "answer", "meatball"), + resource.TestCheckResourceAttr(resourceName, "status", "ACTIVE"), + ), + }, + { + Config: updated, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "key", "name_of_first_plush_toy"), + resource.TestCheckResourceAttr(resourceName, "answer", "meatball"), + resource.TestCheckResourceAttr(resourceName, "status", "ACTIVE"), ), }, }, diff --git a/okta/user_factor.go b/okta/user_factor.go new file mode 100644 index 000000000..08def817d --- /dev/null +++ b/okta/user_factor.go @@ -0,0 +1,24 @@ +package okta + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func handleUserFactorLifecycle(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := getSupplementFromMetadata(m) + if d.Get("status").(string) == statusActive { + _, err := client.ActivateUserFactor(ctx, d.Get("user_id").(string), d.Id()) + if err != nil { + return diag.Errorf("failed to activate user factor: %v", err) + } + return nil + } + _, err := client.DeactivateUserFactor(ctx, d.Get("user_id").(string), d.Id()) + if err != nil { + return diag.Errorf("failed to deactivate user factor: %v", err) + } + return nil +} diff --git a/sdk/factor.go b/sdk/org_factor.go similarity index 62% rename from sdk/factor.go rename to sdk/org_factor.go index 20a57b7da..17ab5c1fc 100644 --- a/sdk/factor.go +++ b/sdk/org_factor.go @@ -8,14 +8,14 @@ import ( "github.com/okta/okta-sdk-golang/v2/okta" ) -type Factor struct { +type OrgFactor struct { Id string `json:"id"` Provider string `json:"provider"` FactorType string `json:"factorType"` Status string `json:"status"` } -// Current available factors for MFA +// Current available org factors for MFA const ( DuoFactor = "duo" FidoU2fFactor = "fido_u2f" @@ -34,14 +34,14 @@ const ( HotpFactor = "hotp" ) -// GetFactor gets a factor by ID. -func (m *ApiSupplement) GetFactor(ctx context.Context, id string) (*Factor, *okta.Response, error) { +// GetOrgFactor gets a factor by ID. +func (m *ApiSupplement) GetOrgFactor(ctx context.Context, id string) (*OrgFactor, *okta.Response, error) { url := fmt.Sprintf("/api/v1/org/factors/%s", id) req, err := m.RequestExecutor.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, nil, err } - var factor Factor + var factor OrgFactor resp, err := m.RequestExecutor.Do(ctx, req, &factor) if err != nil { return nil, resp, err @@ -49,17 +49,17 @@ func (m *ApiSupplement) GetFactor(ctx context.Context, id string) (*Factor, *okt return &factor, resp, nil } -// ActivateFactor allows multifactor authentication to use provided factor type -func (m *ApiSupplement) ActivateFactor(ctx context.Context, id string) (*Factor, *okta.Response, error) { - return m.lifecycleChangeFactor(ctx, id, "activate") +// ActivateOrgFactor allows multifactor authentication to use provided factor type +func (m *ApiSupplement) ActivateOrgFactor(ctx context.Context, id string) (*OrgFactor, *okta.Response, error) { + return m.lifecycleChangeOrgFactor(ctx, id, "activate") } -// ActivateFactor denies multifactor authentication to use provided factor type -func (m *ApiSupplement) DeactivateFactor(ctx context.Context, id string) (*Factor, *okta.Response, error) { - return m.lifecycleChangeFactor(ctx, id, "deactivate") +// DeactivateOrgFactor denies multifactor authentication to use provided factor type +func (m *ApiSupplement) DeactivateOrgFactor(ctx context.Context, id string) (*OrgFactor, *okta.Response, error) { + return m.lifecycleChangeOrgFactor(ctx, id, "deactivate") } -func (m *ApiSupplement) lifecycleChangeFactor(ctx context.Context, id, action string) (*Factor, *okta.Response, error) { +func (m *ApiSupplement) lifecycleChangeOrgFactor(ctx context.Context, id, action string) (*OrgFactor, *okta.Response, error) { url := fmt.Sprintf("/api/v1/org/factors/%s/lifecycle/%s", id, action) req, err := m.RequestExecutor. WithAccept("application/json"). @@ -68,7 +68,7 @@ func (m *ApiSupplement) lifecycleChangeFactor(ctx context.Context, id, action st if err != nil { return nil, nil, err } - var factor *Factor + var factor *OrgFactor resp, err := m.RequestExecutor.Do(ctx, req, factor) if err != nil { return nil, resp, err diff --git a/sdk/user_factor.go b/sdk/user_factor.go new file mode 100644 index 000000000..9d7cfc32f --- /dev/null +++ b/sdk/user_factor.go @@ -0,0 +1,70 @@ +package sdk + +import ( + "context" + "fmt" + "net/http" + "time" + + "github.com/okta/okta-sdk-golang/v2/okta" + "github.com/okta/okta-sdk-golang/v2/okta/query" +) + +type Factor interface { + IsUserFactorInstance() bool +} + +type SecurityQuestionUserFactor struct { + Embedded interface{} `json:"_embedded,omitempty"` + Links interface{} `json:"_links,omitempty"` + Created *time.Time `json:"created,omitempty"` + FactorType string `json:"factorType,omitempty"` + Id string `json:"id,omitempty"` + LastUpdated *time.Time `json:"lastUpdated,omitempty"` + Provider string `json:"provider,omitempty"` + Status string `json:"status,omitempty"` + Verify *okta.VerifyFactorRequest `json:"verify,omitempty"` + Profile *okta.SecurityQuestionUserFactorProfile `json:"profile,omitempty"` +} + +func (a *SecurityQuestionUserFactor) IsUserFactorInstance() bool { + return true +} + +// EnrollUserFactor enrolls a user with a supported factor. +func (m *ApiSupplement) EnrollUserFactor(ctx context.Context, userId string, factorInstance Factor, qp *query.Params) (*okta.Response, error) { + url := fmt.Sprintf("/api/v1/users/%v/factors", userId) + if qp != nil { + url = url + qp.String() + } + req, err := m.RequestExecutor. + WithAccept("application/json"). + WithContentType("application/json"). + NewRequest(http.MethodPost, url, factorInstance) + if err != nil { + return nil, err + } + return m.RequestExecutor.Do(ctx, req, factorInstance) +} + +// GetUserFactor fetches a factor for the specified user +func (m *ApiSupplement) GetUserFactor(ctx context.Context, userId, factorId string, factorInstance Factor) (*okta.Response, error) { + url := fmt.Sprintf("/api/v1/users/%v/factors/%v", userId, factorId) + req, err := m.RequestExecutor.NewRequest(http.MethodGet, url, nil) + if err != nil { + return nil, err + } + return m.RequestExecutor.Do(ctx, req, factorInstance) +} + +func (m *ApiSupplement) UpdateUserFactor(ctx context.Context, userId, factorId string, factorInstance Factor) (*okta.Response, error) { + url := fmt.Sprintf("/api/v1/users/%v/factors/%v", userId, factorId) + req, err := m.RequestExecutor. + WithAccept("application/json"). + WithContentType("application/json"). + NewRequest(http.MethodPut, url, factorInstance) + if err != nil { + return nil, err + } + return m.RequestExecutor.Do(ctx, req, factorInstance) +} From c53c7fb464cd82c50f4eede50d91cdeb230f5b25 Mon Sep 17 00:00:00 2001 From: bogdanprodan-okta Date: Thu, 29 Jul 2021 00:19:00 +0300 Subject: [PATCH 5/5] Removed dead code --- okta/user_factor.go | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 okta/user_factor.go diff --git a/okta/user_factor.go b/okta/user_factor.go deleted file mode 100644 index 08def817d..000000000 --- a/okta/user_factor.go +++ /dev/null @@ -1,24 +0,0 @@ -package okta - -import ( - "context" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func handleUserFactorLifecycle(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - client := getSupplementFromMetadata(m) - if d.Get("status").(string) == statusActive { - _, err := client.ActivateUserFactor(ctx, d.Get("user_id").(string), d.Id()) - if err != nil { - return diag.Errorf("failed to activate user factor: %v", err) - } - return nil - } - _, err := client.DeactivateUserFactor(ctx, d.Get("user_id").(string), d.Id()) - if err != nil { - return diag.Errorf("failed to deactivate user factor: %v", err) - } - return nil -}