Skip to content

Commit

Permalink
Auth Strength WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
alexwilcox9 committed Aug 14, 2023
1 parent 1e65f1b commit 5d8d3fd
Show file tree
Hide file tree
Showing 7 changed files with 377 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package conditionalaccess

import (
"context"
"errors"
"fmt"
"log"
"net/http"
"time"

"github.com/hashicorp/go-azure-sdk/sdk/odata"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-provider-azuread/internal/clients"
"github.com/hashicorp/terraform-provider-azuread/internal/helpers"
"github.com/hashicorp/terraform-provider-azuread/internal/tf"
"github.com/hashicorp/terraform-provider-azuread/internal/utils"
"github.com/hashicorp/terraform-provider-azuread/internal/validate"
"github.com/manicminer/hamilton/msgraph"
)

func authenticationStrengthPolicyResource() *schema.Resource {
return &schema.Resource{
CreateContext: authenticationStrengthPolicyCreate,
ReadContext: authenticationStrengthPolicyRead,
UpdateContext: authenticationStrengthPolicyUpdate,
DeleteContext: authenticationStrengthPolicyDelete,

Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(5 * time.Minute),
Read: schema.DefaultTimeout(5 * time.Minute),
Update: schema.DefaultTimeout(5 * time.Minute),
Delete: schema.DefaultTimeout(5 * time.Minute),
},

Importer: tf.ValidateResourceIDPriorToImport(func(id string) error {
if _, err := uuid.ParseUUID(id); err != nil {
return fmt.Errorf("specified ID (%q) is not valid: %s", id, err)
}
return nil
}),

Schema: map[string]*schema.Schema{

"display_name": {
Description: "The display name for the authentication strength policy",
Type: schema.TypeString,
Required: true,
ValidateDiagFunc: validate.NoEmptyStrings,
},

"description": {
Description: "The description for the authentication strength policy",
Type: schema.TypeString,
Optional: true,
},

"allowed_combinations": {
Description: "The allowed MFA methods for this policy",
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
}
}

func authenticationStrengthPolicyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client).ConditionalAccess.AuthenticationStrengthPoliciesClient

properties := msgraph.AuthenticationStrengthPolicy{
DisplayName: utils.String(d.Get("display_name").(string)),
Description: utils.String(d.Get("description").(string)),
AllowedCombinations: tf.ExpandStringSlicePtr(d.Get("allowed_combinations").(*schema.Set).List()),
}

authenticationStrengthPolicy, _, err := client.Create(ctx, properties)

if err != nil {
return tf.ErrorDiagF(err, "Could not create authentication strength policy")
}

d.SetId(*authenticationStrengthPolicy.ID)

return authenticationStrengthPolicyRead(ctx, d, meta)
}

func authenticationStrengthPolicyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client).ConditionalAccess.AuthenticationStrengthPoliciesClient

properties := msgraph.AuthenticationStrengthPolicy{
ID: utils.String(d.Id()),
DisplayName: utils.String(d.Get("display_name").(string)),
Description: utils.String(d.Get("description").(string)),
// AllowedCombinations: tf.ExpandStringSlicePtr(d.Get("allowed_combinations").(*schema.Set).List()),
}

_, err := client.Update(ctx, properties)

if err != nil {
return tf.ErrorDiagF(err, "Could not update authentication strength policy")
}

if d.HasChange("allowed_combinations") {
properties.AllowedCombinations = tf.ExpandStringSlicePtr(d.Get("allowed_combinations").(*schema.Set).List())
_, err := client.UpdateAllowedCombinations(ctx, properties)
if err != nil {
return tf.ErrorDiagF(err, "Could not update authentication strength policy allowed combinations")
}

Check failure on line 116 in internal/services/conditionalaccess/authentication_strength_policy_resource.go

View workflow job for this annotation

GitHub Actions / golint

unnecessary trailing newline (whitespace)
}

return authenticationStrengthPolicyRead(ctx, d, meta)

Check failure on line 120 in internal/services/conditionalaccess/authentication_strength_policy_resource.go

View workflow job for this annotation

GitHub Actions / golint

unnecessary trailing newline (whitespace)
}

func authenticationStrengthPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client).ConditionalAccess.AuthenticationStrengthPoliciesClient

authenticationStrengthPolicy, status, err := client.Get(ctx, d.Id(), odata.Query{})
if err != nil {
if status == http.StatusNotFound {
log.Printf("[DEBUG] Authentication Strength Policy with Object ID %q was not found - removing from state", d.Id())
d.SetId("")
return nil
}
}
if authenticationStrengthPolicy == nil {
return tf.ErrorDiagF(errors.New("Bad API response"), "Result is nil")
}

d.SetId(*authenticationStrengthPolicy.ID)
tf.Set(d, "display_name", authenticationStrengthPolicy.DisplayName)
tf.Set(d, "description", authenticationStrengthPolicy.Description)
tf.Set(d, "allowed_combinations", tf.FlattenStringSlicePtr(authenticationStrengthPolicy.AllowedCombinations))

return nil
}

func authenticationStrengthPolicyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients.Client).ConditionalAccess.AuthenticationStrengthPoliciesClient
authenticationStrengthPolicyId := d.Id()

if _, status, err := client.Get(ctx, authenticationStrengthPolicyId, odata.Query{}); err != nil {
if status == http.StatusNotFound {
log.Printf("[DEBUG] Authentication Strength Policy with ID %q already deleted", authenticationStrengthPolicyId)
return nil
}

return tf.ErrorDiagPathF(err, "id", "Retrieving Authentication Strength Policy with ID %q", authenticationStrengthPolicyId)
}

status, err := client.Delete(ctx, authenticationStrengthPolicyId)
if err != nil {
return tf.ErrorDiagPathF(err, "id", "Deleting Authentication Strength Policy with ID %q, got status %d", authenticationStrengthPolicyId, status)
}

if err := helpers.WaitForDeletion(ctx, func(ctx context.Context) (*bool, error) {
defer func() { client.BaseClient.DisableRetries = false }()
client.BaseClient.DisableRetries = true
if _, status, err := client.Get(ctx, authenticationStrengthPolicyId, odata.Query{}); err != nil {
if status == http.StatusNotFound {
return utils.Bool(false), nil
}
return nil, err
}
return utils.Bool(true), nil
}); err != nil {
return tf.ErrorDiagF(err, "waiting for deletion of Authentication Strength Policy with ID %q", authenticationStrengthPolicyId)
}

return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package conditionalaccess_test

import (
"context"
"fmt"
"net/http"
"testing"

"github.com/hashicorp/go-azure-sdk/sdk/odata"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/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/utils"
)

type AuthenticationStrengthPolicyResource struct{}

func TestAccAuthenticationStrengthPolicy_basic(t *testing.T) {
data := acceptance.BuildTestData(t, "azuread_authentication_strength_policy", "test")
r := AuthenticationStrengthPolicyResource{}

data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.basic(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func TestAccAuthenticationStrengthPolicy_complete(t *testing.T) {
data := acceptance.BuildTestData(t, "azuread_authentication_strength_policy", "test")
r := AuthenticationStrengthPolicyResource{}

data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.complete(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func TestAccAuthenticationStrengthPolicy_update(t *testing.T) {
data := acceptance.BuildTestData(t, "azuread_authentication_strength_policy", "test")
r := AuthenticationStrengthPolicyResource{}

data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.basic(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.complete(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
{
Config: r.basic(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
),
},
data.ImportStep(),
})
}

func (r AuthenticationStrengthPolicyResource) Exists(ctx context.Context, clients *clients.Client, state *terraform.InstanceState) (*bool, error) {
var id *string

authstrengthpolicy, status, err := clients.ConditionalAccess.AuthenticationStrengthPoliciesClient.Get(ctx, state.ID, odata.Query{})
if err != nil {
if status == http.StatusNotFound {
return nil, fmt.Errorf("Authentication Strength Policy with ID %q does not exist", state.ID)
}
return nil, fmt.Errorf("failed to retrieve Authentication Strength Policy with ID %q: %+v", state.ID, err)
}
id = authstrengthpolicy.ID

return utils.Bool(id != nil && *id == state.ID), nil
}

func (AuthenticationStrengthPolicyResource) basic(data acceptance.TestData) string {
return fmt.Sprintf(`
resource "azuread_authentication_strength_policy" "test" {
display_name = "acctestASP-%[1]d"
description = "test"
allowed_combinations = ["password"]
}
`, data.RandomInteger)
}

func (AuthenticationStrengthPolicyResource) complete(data acceptance.TestData) string {
return fmt.Sprintf(`
resource "azuread_authentication_strength_policy" "test" {
display_name = "acctestASP-%[1]d"
description = "test"
allowed_combinations = [
"fido2",
"password",
"deviceBasedPush",
"temporaryAccessPassOneTime",
"federatedMultiFactor",
"federatedSingleFactor",
"hardwareOath,federatedSingleFactor",
"microsoftAuthenticatorPush,federatedSingleFactor",
"password,hardwareOath",
"password,microsoftAuthenticatorPush",
"password,sms",
"password,softwareOath",
"password,voice",
"sms",
"sms,federatedSingleFactor",
"softwareOath,federatedSingleFactor",
"temporaryAccessPassMultiUse",
"voice,federatedSingleFactor",
"windowsHelloForBusiness",
"x509CertificateMultiFactor",
"x509CertificateSingleFactor",
]
}
`, data.RandomInteger)
}
13 changes: 9 additions & 4 deletions internal/services/conditionalaccess/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import (
)

type Client struct {
NamedLocationsClient *msgraph.NamedLocationsClient
PoliciesClient *msgraph.ConditionalAccessPoliciesClient
NamedLocationsClient *msgraph.NamedLocationsClient
PoliciesClient *msgraph.ConditionalAccessPoliciesClient
AuthenticationStrengthPoliciesClient *msgraph.AuthenticationStrengthPoliciesClient
}

func NewClient(o *common.ClientOptions) *Client {
Expand All @@ -20,8 +21,12 @@ func NewClient(o *common.ClientOptions) *Client {
policiesClient := msgraph.NewConditionalAccessPoliciesClient()
o.ConfigureClient(&policiesClient.BaseClient)

authenticationStrengthpoliciesClient := msgraph.NewAuthenticationStrengthPoliciesClient()
o.ConfigureClient(&authenticationStrengthpoliciesClient.BaseClient)

return &Client{
NamedLocationsClient: namedLocationsClient,
PoliciesClient: policiesClient,
NamedLocationsClient: namedLocationsClient,
PoliciesClient: policiesClient,
AuthenticationStrengthPoliciesClient: authenticationStrengthpoliciesClient,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -387,8 +387,9 @@ func conditionalAccessPolicyResource() *schema.Resource {
},

"built_in_controls": {
Type: schema.TypeList,
Required: true,
Type: schema.TypeList,
Optional: true,
AtLeastOneOf: []string{"grant_controls.0.built_in_controls", "grant_controls.0.authentication_strength_id", "grant_controls.0.terms_of_use"},
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{
Expand All @@ -404,6 +405,13 @@ func conditionalAccessPolicyResource() *schema.Resource {
},
},

"authentication_strength_id": {
AtLeastOneOf: []string{"grant_controls.0.built_in_controls", "grant_controls.0.authentication_strength_id", "grant_controls.0.terms_of_use"},
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.IsUUID,
},

"custom_authentication_factors": {
Type: schema.TypeList,
Optional: true,
Expand All @@ -414,8 +422,9 @@ func conditionalAccessPolicyResource() *schema.Resource {
},

"terms_of_use": {
Type: schema.TypeList,
Optional: true,
Type: schema.TypeList,
Optional: true,
AtLeastOneOf: []string{"grant_controls.0.built_in_controls", "grant_controls.0.authentication_strength_id", "grant_controls.0.terms_of_use"},
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateDiagFunc: validate.NoEmptyStrings,
Expand Down
Loading

0 comments on commit 5d8d3fd

Please sign in to comment.