From fd97dd9976ff6b88ae07997acd3d939de0b40830 Mon Sep 17 00:00:00 2001 From: Isaac Lopez Date: Thu, 21 Dec 2023 18:47:54 -0600 Subject: [PATCH 1/2] enable custom OTP on okta_authenticator --- okta/data_source_okta_authenticator.go | 10 +---- okta/resource_okta_authenticator.go | 50 +++++++++++++++++++--- okta/resource_okta_authenticator_test.go | 38 ++++++++++++++++ sdk/v2_authenticator.go | 24 +++++++++++ sdk/v2_authenticatorSettingsOTP.go | 36 ++++++++++++++++ website/docs/r/authenticator.html.markdown | 18 +++++++- 6 files changed, 162 insertions(+), 14 deletions(-) create mode 100644 sdk/v2_authenticatorSettingsOTP.go diff --git a/okta/data_source_okta_authenticator.go b/okta/data_source_okta_authenticator.go index 13abcb8c1..73691e671 100644 --- a/okta/data_source_okta_authenticator.go +++ b/okta/data_source_okta_authenticator.go @@ -150,15 +150,9 @@ func findAuthenticator(ctx context.Context, m interface{}, name, key string) (*s return nil, err } for _, authenticator := range authenticators { - if authenticator.Name == name { + if authenticator.Name == name && authenticator.Key == key { return authenticator, nil } - if authenticator.Key == key { - return authenticator, nil - } - } - if key != "" { - return nil, fmt.Errorf("authenticator with key '%s' does not exist", key) } - return nil, fmt.Errorf("authenticator with name '%s' does not exist", name) + return nil, fmt.Errorf("authenticator with name '%s' and/or key '%s' does not exist", name, key) } diff --git a/okta/resource_okta_authenticator.go b/okta/resource_okta_authenticator.go index 9978a4431..3d39827b3 100644 --- a/okta/resource_okta_authenticator.go +++ b/okta/resource_okta_authenticator.go @@ -31,9 +31,6 @@ func resourceAuthenticator() *schema.Resource { Type: schema.TypeString, Required: true, Description: "Display name of the Authenticator", - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - return true - }, }, "settings": { Type: schema.TypeString, @@ -157,7 +154,7 @@ func resourceAuthenticatorCreate(ctx context.Context, d *schema.ResourceData, m var err error // soft create if the authenticator already exists - authenticator, _ := findAuthenticator(ctx, m, "", d.Get("key").(string)) + authenticator, _ := findAuthenticator(ctx, m, d.Get("name").(string), d.Get("key").(string)) if authenticator == nil { // otherwise hard create authenticator, err = buildAuthenticator(d) @@ -168,10 +165,26 @@ func resourceAuthenticatorCreate(ctx context.Context, d *schema.ResourceData, m qp := &query.Params{ Activate: boolPtr(activate), } + if(d.Get("key").(string) == "custom_otp"){ + qp = &query.Params{ + Activate: boolPtr(false), + } + } authenticator, _, err = getOktaClientFromMetadata(m).Authenticator.CreateAuthenticator(ctx, *authenticator, qp) if err != nil { return diag.FromErr(err) } + if(d.Get("key").(string) == "custom_otp"){ + var otp *sdk.OTP + otp, err = buildOTP(d) + if err != nil { + return diag.FromErr(err) + } + _, err = getOktaClientFromMetadata(m).Authenticator.SetSettingsOTP(ctx, *otp, authenticator.Id) + if err != nil { + return diag.FromErr(err) + } + } } d.SetId(authenticator.Id) @@ -184,6 +197,17 @@ func resourceAuthenticatorCreate(ctx context.Context, d *schema.ResourceData, m if status.(string) == statusInactive { authenticator, _, err = getOktaClientFromMetadata(m).Authenticator.DeactivateAuthenticator(ctx, d.Id()) } else { + if(d.Get("key").(string) == "custom_otp"){ + var otp *sdk.OTP + otp, err = buildOTP(d) + if err != nil { + return diag.FromErr(err) + } + _, err = getOktaClientFromMetadata(m).Authenticator.SetSettingsOTP(ctx, *otp, d.Id()) + if err != nil { + return diag.FromErr(err) + } + } authenticator, _, err = getOktaClientFromMetadata(m).Authenticator.ActivateAuthenticator(ctx, d.Id()) } if err != nil { @@ -263,7 +287,9 @@ func buildAuthenticator(d *schema.ResourceData) (*sdk.Authenticator, error) { Key: d.Get("key").(string), Name: d.Get("name").(string), } - if d.Get("type").(string) == "security_key" { + if d.Get("key").(string) == "custom_otp" { + + } else if d.Get("type").(string) == "security_key" { authenticator.Provider = &sdk.AuthenticatorProvider{ Type: d.Get("provider_type").(string), Configuration: &sdk.AuthenticatorProviderConfiguration{ @@ -311,6 +337,20 @@ func buildAuthenticator(d *schema.ResourceData) (*sdk.Authenticator, error) { return &authenticator, nil } +func buildOTP(d *schema.ResourceData) (*sdk.OTP, error) { + otp := sdk.OTP{} + if s, ok := d.GetOk("settings"); ok { + var settings sdk.AuthenticatorSettingsOTP + err := json.Unmarshal([]byte(s.(string)), &settings) + if err != nil { + return nil, err + } + otp.Settings = &settings + } + + return &otp, nil +} + func validateAuthenticator(d *schema.ResourceData) error { typ := d.Get("type").(string) if typ == "security_key" { diff --git a/okta/resource_okta_authenticator_test.go b/okta/resource_okta_authenticator_test.go index 9782c58c3..39e052f19 100644 --- a/okta/resource_okta_authenticator_test.go +++ b/okta/resource_okta_authenticator_test.go @@ -7,6 +7,44 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) +func TestAccResourceOktaAuthenticatorOTP(t *testing.T) { + config := ` + resource "okta_authenticator" "otp" { + name = "Custom OTP" + key = "custom_otp" + status = "ACTIVE" + settings = jsonencode({ + "protocol" : "TOTP", + "acceptableAdjacentIntervals" : 3, + "timeIntervalInSeconds" : 30, + "encoding" : "base32", + "algorithm" : "HMacSHA256", + "passCodeLength" : 6 + }) + } +` + resourceName := fmt.Sprintf("%s.otp", authenticator) + + oktaResourceTest(t, resource.TestCase{ + PreCheck: testAccPreCheck(t), + ErrorCheck: testAccErrorChecks(t), + ProviderFactories: testAccProvidersFactories, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "status", statusActive), + resource.TestCheckResourceAttr(resourceName, "type", "security_key"), + resource.TestCheckResourceAttr(resourceName, "key", "custom_otp"), + resource.TestCheckResourceAttr(resourceName, "name", "Custom OTP"), + testAttributeJSON(resourceName, "settings", `{"acceptableAdjacentIntervals":3,"algorithm":"HMacSHA256","encoding":"base32","passCodeLength":6,"protocol":"TOTP","timeIntervalInSeconds":30}`), + ), + }, + }, + }) +} + func TestAccResourceOktaAuthenticator_crud(t *testing.T) { resourceName := fmt.Sprintf("%s.test", authenticator) mgr := newFixtureManager("resources", authenticator, t.Name()) diff --git a/sdk/v2_authenticator.go b/sdk/v2_authenticator.go index 7eaf145f6..39b22e2d8 100644 --- a/sdk/v2_authenticator.go +++ b/sdk/v2_authenticator.go @@ -24,6 +24,10 @@ type Authenticator struct { Type string `json:"type,omitempty"` } +type OTP struct { + Settings *AuthenticatorSettingsOTP `json:"settings"` +} + func (m *AuthenticatorResource) GetAuthenticator(ctx context.Context, authenticatorId string) (*Authenticator, *Response, error) { url := fmt.Sprintf("/api/v1/authenticators/%v", authenticatorId) @@ -110,6 +114,26 @@ func (m *AuthenticatorResource) CreateAuthenticator(ctx context.Context, body Au return authenticator, resp, nil } +func (m *AuthenticatorResource) SetSettingsOTP(ctx context.Context, body OTP, authenticatorId string)(*Response, error){ + url := fmt.Sprintf("/api/v1/authenticators/%v/methods/otp", authenticatorId) + + rq := m.client.CloneRequestExecutor() + + req, err := rq.WithAccept("application/json").WithContentType("application/json").NewRequest("PUT", url, body) + if err != nil { + return nil, err + } + + var otp *OTP + + resp, err := rq.Do(ctx, req, &otp) + if err != nil { + return nil, err + } + + return resp, nil +} + func (m *AuthenticatorResource) ActivateAuthenticator(ctx context.Context, authenticatorId string) (*Authenticator, *Response, error) { url := fmt.Sprintf("/api/v1/authenticators/%v/lifecycle/activate", authenticatorId) diff --git a/sdk/v2_authenticatorSettingsOTP.go b/sdk/v2_authenticatorSettingsOTP.go new file mode 100644 index 000000000..b454c4fb6 --- /dev/null +++ b/sdk/v2_authenticatorSettingsOTP.go @@ -0,0 +1,36 @@ +// DO NOT EDIT LOCAL SDK - USE v3 okta-sdk-golang FOR API CALLS THAT DO NOT EXIST IN LOCAL SDK +package sdk + +import "encoding/json" + +type AuthenticatorSettingsOTP struct { + AcceptableAdjacentIntervals int `json:"acceptableAdjacentIntervals"` + Algorithm string `json:"algorithm"` + Encoding string `json:"encoding"` + PassCodeLength int `json:"passCodeLength"` + Protocol string `json:"protocol"` + TimeIntervalInSeconds int `json:"timeIntervalInSeconds"` +} + +func (a *AuthenticatorSettingsOTP) MarshalJSON() ([]byte, error) { + type Alias AuthenticatorSettingsOTP + type local struct { + *Alias + } + result := local{Alias: (*Alias)(a)} + return json.Marshal(&result) +} + +func (a *AuthenticatorSettingsOTP) UnmarshalJSON(data []byte) error { + type Alias AuthenticatorSettingsOTP + + result := &struct { + *Alias + }{ + Alias: (*Alias)(a), + } + if err := json.Unmarshal(data, &result); err != nil { + return err + } + return nil +} diff --git a/website/docs/r/authenticator.html.markdown b/website/docs/r/authenticator.html.markdown index 9ff061648..3e24fb4bf 100644 --- a/website/docs/r/authenticator.html.markdown +++ b/website/docs/r/authenticator.html.markdown @@ -37,11 +37,27 @@ resource "okta_authenticator" "test" { } ``` +```hcl +resource "okta_authenticator" "test" { + name = "Custom OTP" + key = "custom_otp" + status = "ACTIVE" + settings = jsonencode({ + "protocol" : "TOTP", + "acceptableAdjacentIntervals" : 3, + "timeIntervalInSeconds" : 30, + "encoding" : "base32", + "algorithm" : "HMacSHA256", + "passCodeLength" : 6 + }) +} +``` + ## Argument Reference The following arguments are supported: -- `key` (Required) A human-readable string that identifies the authenticator. Some authenticators are available by feature flag on the organization. Possible values inclue: `duo`, `external_idp`, `google_otp`, `okta_email`, `okta_password`, `okta_verify`, `onprem_mfa`, `phone_number`, `rsa_token`, `security_question`, `webauthn` +- `key` (Required) A human-readable string that identifies the authenticator. Some authenticators are available by feature flag on the organization. Possible values inclue: `duo`, `external_idp`, `google_otp`, `okta_email`, `okta_password`, `okta_verify`, `onprem_mfa`, `phone_number`, `rsa_token`, `security_question`, `webauthn`, `custom_otp` - `name` - (Required) Name of the authenticator. From a1f11b6eeee815d766df23f2bb34dfbab8cf8229 Mon Sep 17 00:00:00 2001 From: Tien Nguyen Date: Thu, 22 Feb 2024 22:43:55 -0500 Subject: [PATCH 2/2] update findAuthenticator --- okta/data_source_okta_authenticator.go | 10 +++++-- okta/resource_okta_authenticator.go | 38 ++++++++------------------ 2 files changed, 19 insertions(+), 29 deletions(-) diff --git a/okta/data_source_okta_authenticator.go b/okta/data_source_okta_authenticator.go index 73691e671..13abcb8c1 100644 --- a/okta/data_source_okta_authenticator.go +++ b/okta/data_source_okta_authenticator.go @@ -150,9 +150,15 @@ func findAuthenticator(ctx context.Context, m interface{}, name, key string) (*s return nil, err } for _, authenticator := range authenticators { - if authenticator.Name == name && authenticator.Key == key { + if authenticator.Name == name { return authenticator, nil } + if authenticator.Key == key { + return authenticator, nil + } + } + if key != "" { + return nil, fmt.Errorf("authenticator with key '%s' does not exist", key) } - return nil, fmt.Errorf("authenticator with name '%s' and/or key '%s' does not exist", name, key) + return nil, fmt.Errorf("authenticator with name '%s' does not exist", name) } diff --git a/okta/resource_okta_authenticator.go b/okta/resource_okta_authenticator.go index 3d39827b3..5e318a5ad 100644 --- a/okta/resource_okta_authenticator.go +++ b/okta/resource_okta_authenticator.go @@ -154,7 +154,7 @@ func resourceAuthenticatorCreate(ctx context.Context, d *schema.ResourceData, m var err error // soft create if the authenticator already exists - authenticator, _ := findAuthenticator(ctx, m, d.Get("name").(string), d.Get("key").(string)) + authenticator, _ := findAuthenticator(ctx, m, "", d.Get("key").(string)) if authenticator == nil { // otherwise hard create authenticator, err = buildAuthenticator(d) @@ -165,16 +165,11 @@ func resourceAuthenticatorCreate(ctx context.Context, d *schema.ResourceData, m qp := &query.Params{ Activate: boolPtr(activate), } - if(d.Get("key").(string) == "custom_otp"){ - qp = &query.Params{ - Activate: boolPtr(false), - } - } authenticator, _, err = getOktaClientFromMetadata(m).Authenticator.CreateAuthenticator(ctx, *authenticator, qp) if err != nil { return diag.FromErr(err) } - if(d.Get("key").(string) == "custom_otp"){ + if d.Get("key").(string) == "custom_otp" { var otp *sdk.OTP otp, err = buildOTP(d) if err != nil { @@ -197,17 +192,6 @@ func resourceAuthenticatorCreate(ctx context.Context, d *schema.ResourceData, m if status.(string) == statusInactive { authenticator, _, err = getOktaClientFromMetadata(m).Authenticator.DeactivateAuthenticator(ctx, d.Id()) } else { - if(d.Get("key").(string) == "custom_otp"){ - var otp *sdk.OTP - otp, err = buildOTP(d) - if err != nil { - return diag.FromErr(err) - } - _, err = getOktaClientFromMetadata(m).Authenticator.SetSettingsOTP(ctx, *otp, d.Id()) - if err != nil { - return diag.FromErr(err) - } - } authenticator, _, err = getOktaClientFromMetadata(m).Authenticator.ActivateAuthenticator(ctx, d.Id()) } if err != nil { @@ -287,9 +271,7 @@ func buildAuthenticator(d *schema.ResourceData) (*sdk.Authenticator, error) { Key: d.Get("key").(string), Name: d.Get("name").(string), } - if d.Get("key").(string) == "custom_otp" { - - } else if d.Get("type").(string) == "security_key" { + if d.Get("type").(string) == "security_key" { authenticator.Provider = &sdk.AuthenticatorProvider{ Type: d.Get("provider_type").(string), Configuration: &sdk.AuthenticatorProviderConfiguration{ @@ -315,13 +297,15 @@ func buildAuthenticator(d *schema.ResourceData) (*sdk.Authenticator, error) { }, } } else { - if s, ok := d.GetOk("settings"); ok { - var settings sdk.AuthenticatorSettings - err := json.Unmarshal([]byte(s.(string)), &settings) - if err != nil { - return nil, err + if d.Get("key").(string) != "custom_otp" { + if s, ok := d.GetOk("settings"); ok { + var settings sdk.AuthenticatorSettings + err := json.Unmarshal([]byte(s.(string)), &settings) + if err != nil { + return nil, err + } + authenticator.Settings = &settings } - authenticator.Settings = &settings } }