Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Count UTF-8 string length by # of chars vs. bytes in message and subject args for aws_cognito_user_pool #36661

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/36661.txt
@@ -0,0 +1,3 @@
```release-note:bug
resource/aws_cognito_user_pool: Updated string validation of `*_message` and `*_subject` arguments to count UTF-8 characters properly
```
124 changes: 96 additions & 28 deletions internal/service/cognitoidp/user_pool_test.go
Expand Up @@ -1500,29 +1500,35 @@ func TestAccCognitoIDPUserPool_withVerificationMessageTemplate(t *testing.T) {
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_cognito_user_pool.test"

emailMessage := "foo {####} bar"
emailMessageByLink := "{##foobar##}"
emailSubject := "foobar {####}"
emailSubjectByLink := "foobar"
smsMessage := "{####} baz"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheckIdentityProvider(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, names.CognitoIDPServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckUserPoolDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccUserPoolConfig_verificationMessageTemplate(rName),
Config: testAccUserPoolConfig_verificationMessageTemplate(rName, emailMessage, emailMessageByLink, emailSubject, emailSubjectByLink, smsMessage),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckUserPoolExists(ctx, resourceName, &pool),
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.default_email_option", "CONFIRM_WITH_LINK"),
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.email_message", "foo {####} bar"),
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.email_message_by_link", "{##foobar##}"),
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.email_subject", "foobar {####}"),
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.email_subject_by_link", "foobar"),
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.sms_message", "{####} baz"),
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.email_message", emailMessage),
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.email_message_by_link", emailMessageByLink),
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.email_subject", emailSubject),
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.email_subject_by_link", emailSubjectByLink),
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.sms_message", smsMessage),

/* Setting Verification template attributes like EmailMessage, EmailSubject or SmsMessage
will implicitly set EmailVerificationMessage, EmailVerificationSubject and SmsVerificationMessage attributes.
*/
resource.TestCheckResourceAttr(resourceName, "email_verification_message", "foo {####} bar"),
resource.TestCheckResourceAttr(resourceName, "email_verification_subject", "foobar {####}"),
resource.TestCheckResourceAttr(resourceName, "sms_verification_message", "{####} baz"),
resource.TestCheckResourceAttr(resourceName, "email_verification_message", emailMessage),
resource.TestCheckResourceAttr(resourceName, "email_verification_subject", emailSubject),
resource.TestCheckResourceAttr(resourceName, "sms_verification_message", smsMessage),
),
},
{
Expand All @@ -1531,19 +1537,81 @@ func TestAccCognitoIDPUserPool_withVerificationMessageTemplate(t *testing.T) {
ImportStateVerify: true,
},
{
Config: testAccUserPoolConfig_verificationMessageTemplateDefaultEmailOption(rName),
Config: testAccUserPoolConfig_verificationMessageTemplateDefaultEmailOption(rName, emailMessage, emailSubject, smsMessage),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.default_email_option", "CONFIRM_WITH_CODE"),
resource.TestCheckResourceAttr(resourceName, "email_verification_message", "{####} Baz"),
resource.TestCheckResourceAttr(resourceName, "email_verification_subject", "BazBaz {####}"),
resource.TestCheckResourceAttr(resourceName, "sms_verification_message", "{####} BazBazBar?"),
resource.TestCheckResourceAttr(resourceName, "email_verification_message", emailMessage),
resource.TestCheckResourceAttr(resourceName, "email_verification_subject", emailSubject),
resource.TestCheckResourceAttr(resourceName, "sms_verification_message", smsMessage),

/* Setting EmailVerificationMessage, EmailVerificationSubject and SmsVerificationMessage attributes
will implicitly set verification template attributes like EmailMessage, EmailSubject or SmsMessage.
*/
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.email_message", "{####} Baz"),
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.email_subject", "BazBaz {####}"),
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.sms_message", "{####} BazBazBar?"),
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.email_message", emailMessage),
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.email_subject", emailSubject),
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.sms_message", smsMessage),
),
},
},
})
}

func TestAccCognitoIDPUserPool_withVerificationMessageTemplateUTF8(t *testing.T) {
ctx := acctest.Context(t)
var pool cognitoidentityprovider.UserPoolType
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_cognito_user_pool.test"

emailMessage := "{####}" + strings.Repeat("あ", 994) // = 1000
emailMessageByLink := "{##foobar##}" + strings.Repeat("い", 988) // = 1000
emailSubject := strings.Repeat("う", 140)
emailSubjectByLink := strings.Repeat("え", 140)
smsMessage := "{####}" + strings.Repeat("お", 134) // = 140

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheckIdentityProvider(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, names.CognitoIDPServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckUserPoolDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccUserPoolConfig_verificationMessageTemplate(rName, emailMessage, emailMessageByLink, emailSubject, emailSubjectByLink, smsMessage),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckUserPoolExists(ctx, resourceName, &pool),
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.default_email_option", "CONFIRM_WITH_LINK"),
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.email_message", emailMessage),
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.email_message_by_link", emailMessageByLink),
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.email_subject", emailSubject),
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.email_subject_by_link", emailSubjectByLink),
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.sms_message", smsMessage),

/* Setting Verification template attributes like EmailMessage, EmailSubject or SmsMessage
will implicitly set EmailVerificationMessage, EmailVerificationSubject and SmsVerificationMessage attributes.
*/
resource.TestCheckResourceAttr(resourceName, "email_verification_message", emailMessage),
resource.TestCheckResourceAttr(resourceName, "email_verification_subject", emailSubject),
resource.TestCheckResourceAttr(resourceName, "sms_verification_message", smsMessage),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
{
Config: testAccUserPoolConfig_verificationMessageTemplateDefaultEmailOption(rName, emailMessage, emailSubject, smsMessage),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.default_email_option", "CONFIRM_WITH_CODE"),
resource.TestCheckResourceAttr(resourceName, "email_verification_message", emailMessage),
resource.TestCheckResourceAttr(resourceName, "email_verification_subject", emailSubject),
resource.TestCheckResourceAttr(resourceName, "sms_verification_message", smsMessage),

/* Setting EmailVerificationMessage, EmailVerificationSubject and SmsVerificationMessage attributes
will implicitly set verification template attributes like EmailMessage, EmailSubject or SmsMessage.
*/
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.email_message", emailMessage),
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.email_subject", emailSubject),
resource.TestCheckResourceAttr(resourceName, "verification_message_template.0.sms_message", smsMessage),
),
},
},
Expand Down Expand Up @@ -2657,7 +2725,7 @@ resource "aws_cognito_user_pool" "test" {
`, name)
}

func testAccUserPoolConfig_verificationMessageTemplate(name string) string {
func testAccUserPoolConfig_verificationMessageTemplate(name, emailMessage, emailMessageByLink, emailSubject, emailSubjectByLink, smsMessage string) string {
return fmt.Sprintf(`
resource "aws_cognito_user_pool" "test" {
name = %[1]q
Expand All @@ -2668,30 +2736,30 @@ resource "aws_cognito_user_pool" "test" {

verification_message_template {
default_email_option = "CONFIRM_WITH_LINK"
email_message = "foo {####} bar"
email_message_by_link = "{##foobar##}"
email_subject = "foobar {####}"
email_subject_by_link = "foobar"
sms_message = "{####} baz"
email_message = %[2]q
email_message_by_link = %[3]q
email_subject = %[4]q
email_subject_by_link = %[5]q
sms_message = %[6]q
}
}
`, name)
`, name, emailMessage, emailMessageByLink, emailSubject, emailSubjectByLink, smsMessage)
}

func testAccUserPoolConfig_verificationMessageTemplateDefaultEmailOption(name string) string {
func testAccUserPoolConfig_verificationMessageTemplateDefaultEmailOption(name, emailVerificationMessage, emailVerificationSubject, smsVerificationMessage string) string {
return fmt.Sprintf(`
resource "aws_cognito_user_pool" "test" {
name = %[1]q

email_verification_message = "{####} Baz"
email_verification_subject = "BazBaz {####}"
sms_verification_message = "{####} BazBazBar?"
email_verification_message = %[2]q
email_verification_subject = %[3]q
sms_verification_message = %[4]q

verification_message_template {
default_email_option = "CONFIRM_WITH_CODE"
}
}
`, name)
`, name, emailVerificationMessage, emailVerificationSubject, smsVerificationMessage)
}

func testAccUserPoolConfig_update(name string, mfaconfig, smsAuthMsg string) string {
Expand Down
100 changes: 56 additions & 44 deletions internal/service/cognitoidp/validate.go
Expand Up @@ -5,6 +5,7 @@ package cognitoidp

import (
"fmt"
"unicode/utf8"

"github.com/YakDriver/regexache"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
Expand Down Expand Up @@ -44,12 +45,13 @@ func validUserGroupName(v interface{}, k string) (ws []string, es []error) {

func validUserPoolEmailVerificationMessage(v interface{}, k string) (ws []string, es []error) {
value := v.(string)
if len(value) < 6 {
es = append(es, fmt.Errorf("%q cannot be less than 6 characters", k))
count := utf8.RuneCountInString(value)
if count < 6 {
es = append(es, fmt.Errorf("%q cannot be less than 6 UTF-8 characters", k))
}

if len(value) > 20000 {
es = append(es, fmt.Errorf("%q cannot be longer than 20000 characters", k))
if count > 20000 {
es = append(es, fmt.Errorf("%q cannot be longer than 20000 UTF-8 characters", k))
}

if !regexache.MustCompile(`[\p{L}\p{M}\p{S}\p{N}\p{P}\s*]*\{####\}[\p{L}\p{M}\p{S}\p{N}\p{P}\s*]*`).MatchString(value) {
Expand All @@ -60,12 +62,13 @@ func validUserPoolEmailVerificationMessage(v interface{}, k string) (ws []string

func validUserPoolEmailVerificationSubject(v interface{}, k string) (ws []string, es []error) {
value := v.(string)
if len(value) < 6 {
es = append(es, fmt.Errorf("%q cannot be less than 6 characters", k))
count := utf8.RuneCountInString(value)
if count < 1 {
es = append(es, fmt.Errorf("%q cannot be less than 1 UTF-8 character", k))
}

if len(value) > 140 {
es = append(es, fmt.Errorf("%q cannot be longer than 140 characters", k))
if count > 140 {
es = append(es, fmt.Errorf("%q cannot be longer than 140 UTF-8 characters", k))
}

if !regexache.MustCompile(`[\p{L}\p{M}\p{S}\p{N}\p{P}\s]+`).MatchString(value) {
Expand All @@ -84,12 +87,13 @@ func validUserPoolID(v interface{}, k string) (ws []string, es []error) {

func validUserPoolInviteTemplateEmailMessage(v interface{}, k string) (ws []string, es []error) {
value := v.(string)
if len(value) < 6 {
es = append(es, fmt.Errorf("%q cannot be less than 6 characters", k))
count := utf8.RuneCountInString(value)
if count < 6 {
es = append(es, fmt.Errorf("%q cannot be less than 6 UTF-8 characters", k))
}

if len(value) > 20000 {
es = append(es, fmt.Errorf("%q cannot be longer than 20000 characters", k))
if count > 20000 {
es = append(es, fmt.Errorf("%q cannot be longer than 20000 UTF-8 characters", k))
}

if !regexache.MustCompile(`[\p{L}\p{M}\p{S}\p{N}\p{P}\s*]*\{####\}[\p{L}\p{M}\p{S}\p{N}\p{P}\s*]*`).MatchString(value) {
Expand All @@ -104,12 +108,13 @@ func validUserPoolInviteTemplateEmailMessage(v interface{}, k string) (ws []stri

func validUserPoolInviteTemplateSMSMessage(v interface{}, k string) (ws []string, es []error) {
value := v.(string)
if len(value) < 6 {
es = append(es, fmt.Errorf("%q cannot be less than 6 characters", k))
count := utf8.RuneCountInString(value)
if count < 6 {
es = append(es, fmt.Errorf("%q cannot be less than 6 UTF-8 characters", k))
}

if len(value) > 140 {
es = append(es, fmt.Errorf("%q cannot be longer than 140 characters", k))
if count > 140 {
es = append(es, fmt.Errorf("%q cannot be longer than 140 UTF-8 characters", k))
}

if !regexache.MustCompile(`.*\{####\}.*`).MatchString(value) {
Expand Down Expand Up @@ -140,12 +145,13 @@ func validUserPoolSchemaName(v interface{}, k string) (ws []string, es []error)

func validUserPoolSMSAuthenticationMessage(v interface{}, k string) (ws []string, es []error) {
value := v.(string)
if len(value) < 6 {
es = append(es, fmt.Errorf("%q cannot be less than 6 characters", k))
count := utf8.RuneCountInString(value)
if count < 6 {
es = append(es, fmt.Errorf("%q cannot be less than 6 UTF-8 characters", k))
}

if len(value) > 140 {
es = append(es, fmt.Errorf("%q cannot be longer than 140 characters", k))
if count > 140 {
es = append(es, fmt.Errorf("%q cannot be longer than 140 UTF-8 characters", k))
}

if !regexache.MustCompile(`.*\{####\}.*`).MatchString(value) {
Expand All @@ -156,12 +162,13 @@ func validUserPoolSMSAuthenticationMessage(v interface{}, k string) (ws []string

func validUserPoolSMSVerificationMessage(v interface{}, k string) (ws []string, es []error) {
value := v.(string)
if len(value) < 6 {
es = append(es, fmt.Errorf("%q cannot be less than 6 characters", k))
count := utf8.RuneCountInString(value)
if count < 6 {
es = append(es, fmt.Errorf("%q cannot be less than 6 UTF-8 characters", k))
}

if len(value) > 140 {
es = append(es, fmt.Errorf("%q cannot be longer than 140 characters", k))
if count > 140 {
es = append(es, fmt.Errorf("%q cannot be longer than 140 UTF-8 characters", k))
}

if !regexache.MustCompile(`.*\{####\}.*`).MatchString(value) {
Expand All @@ -172,12 +179,13 @@ func validUserPoolSMSVerificationMessage(v interface{}, k string) (ws []string,

func validUserPoolTemplateEmailMessage(v interface{}, k string) (ws []string, es []error) {
value := v.(string)
if len(value) < 6 {
es = append(es, fmt.Errorf("%q cannot be less than 6 characters", k))
count := utf8.RuneCountInString(value)
if count < 6 {
es = append(es, fmt.Errorf("%q cannot be less than 6 UTF-8 characters", k))
}

if len(value) > 20000 {
es = append(es, fmt.Errorf("%q cannot be longer than 20000 characters", k))
if count > 20000 {
es = append(es, fmt.Errorf("%q cannot be longer than 20000 UTF-8 characters", k))
}

if !regexache.MustCompile(`[\p{L}\p{M}\p{S}\p{N}\p{P}\s*]*\{####\}[\p{L}\p{M}\p{S}\p{N}\p{P}\s*]*`).MatchString(value) {
Expand All @@ -188,12 +196,13 @@ func validUserPoolTemplateEmailMessage(v interface{}, k string) (ws []string, es

func validUserPoolTemplateEmailMessageByLink(v interface{}, k string) (ws []string, es []error) {
value := v.(string)
if len(value) < 1 {
es = append(es, fmt.Errorf("%q cannot be less than 1 character", k))
count := utf8.RuneCountInString(value)
if count < 6 {
es = append(es, fmt.Errorf("%q cannot be less than 6 UTF-8 characters", k))
}

if len(value) > 20000 {
es = append(es, fmt.Errorf("%q cannot be longer than 20000 characters", k))
if count > 20000 {
es = append(es, fmt.Errorf("%q cannot be longer than 20000 UTF-8 characters", k))
}

if !regexache.MustCompile(`[\p{L}\p{M}\p{S}\p{N}\p{P}\s*]*\{##[\p{L}\p{M}\p{S}\p{N}\p{P}\s*]*##\}[\p{L}\p{M}\p{S}\p{N}\p{P}\s*]*`).MatchString(value) {
Expand All @@ -204,12 +213,13 @@ func validUserPoolTemplateEmailMessageByLink(v interface{}, k string) (ws []stri

func validUserPoolTemplateEmailSubject(v interface{}, k string) (ws []string, es []error) {
value := v.(string)
if len(value) < 1 {
es = append(es, fmt.Errorf("%q cannot be less than 1 character", k))
count := utf8.RuneCountInString(value)
if count < 1 {
es = append(es, fmt.Errorf("%q cannot be less than 1 UTF-8 character", k))
}

if len(value) > 140 {
es = append(es, fmt.Errorf("%q cannot be longer than 140 characters", k))
if count > 140 {
es = append(es, fmt.Errorf("%q cannot be longer than 140 UTF-8 characters", k))
}

if !regexache.MustCompile(`[\p{L}\p{M}\p{S}\p{N}\p{P}\s]+`).MatchString(value) {
Expand All @@ -220,12 +230,13 @@ func validUserPoolTemplateEmailSubject(v interface{}, k string) (ws []string, es

func validUserPoolTemplateEmailSubjectByLink(v interface{}, k string) (ws []string, es []error) {
value := v.(string)
if len(value) < 1 {
es = append(es, fmt.Errorf("%q cannot be less than 1 character", k))
count := utf8.RuneCountInString(value)
if count < 1 {
es = append(es, fmt.Errorf("%q cannot be less than 1 UTF-8 character", k))
}

if len(value) > 140 {
es = append(es, fmt.Errorf("%q cannot be longer than 140 characters", k))
if count > 140 {
es = append(es, fmt.Errorf("%q cannot be longer than 140 UTF-8 characters", k))
}

if !regexache.MustCompile(`[\p{L}\p{M}\p{S}\p{N}\p{P}\s]+`).MatchString(value) {
Expand All @@ -236,12 +247,13 @@ func validUserPoolTemplateEmailSubjectByLink(v interface{}, k string) (ws []stri

func validUserPoolTemplateSMSMessage(v interface{}, k string) (ws []string, es []error) {
value := v.(string)
if len(value) < 6 {
es = append(es, fmt.Errorf("%q cannot be less than 6 characters", k))
count := utf8.RuneCountInString(value)
if count < 6 {
es = append(es, fmt.Errorf("%q cannot be less than 6 UTF-8 characters", k))
}

if len(value) > 140 {
es = append(es, fmt.Errorf("%q cannot be longer than 140 characters", k))
if count > 140 {
es = append(es, fmt.Errorf("%q cannot be longer than 140 UTF-8 characters", k))
}

if !regexache.MustCompile(`.*\{####\}.*`).MatchString(value) {
Expand Down