From d0ae4f62f90991e3527d742d81674fbcc2fa390b Mon Sep 17 00:00:00 2001 From: Aleksander Zaruczewski Date: Thu, 16 Nov 2023 13:21:00 +0200 Subject: [PATCH] feat: make sure emails are lowercase and valid (#1451) --- CHANGELOG.md | 2 ++ docs/data-sources/account_team_member.md | 2 +- docs/data-sources/organization_user.md | 2 +- docs/data-sources/project_user.md | 2 +- docs/resources/account_team_member.md | 2 +- docs/resources/organization_user.md | 2 +- docs/resources/project_user.md | 2 +- internal/schemautil/schemautil.go | 17 +++++++++++++++++ .../service/account/account_team_member.go | 12 ++++++++---- .../service/organization/organization_user.go | 6 ++++-- .../sdkprovider/service/project/project_user.go | 9 +++++---- 11 files changed, 42 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f42a52077..c1b65e230 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ nav_order: 1 - Fix unmarshalling empty userconfig crash - Never skip basic auth username/password in service integrations user config options when sending them to the API - Add `emit_backward_heartbeats_enabled` field support in Mirrormaker replication flow +- Add validation for email fields in `account_team_member`, `organization_user` and `project_user` resources to check + if email is lowercase and valid ## [4.9.3] - 2023-10-27 diff --git a/docs/data-sources/account_team_member.md b/docs/data-sources/account_team_member.md index 31cb71c47..baaa03f29 100644 --- a/docs/data-sources/account_team_member.md +++ b/docs/data-sources/account_team_member.md @@ -27,7 +27,7 @@ data "aiven_account_team_member" "foo" { - `account_id` (String) The unique account id. This property cannot be changed, doing so forces recreation of the resource. - `team_id` (String) An account team id. This property cannot be changed, doing so forces recreation of the resource. -- `user_email` (String) Is a user email address that first will be invited, and after accepting an invitation, he or she becomes a member of a team. This property cannot be changed, doing so forces recreation of the resource. +- `user_email` (String) Is a user email address that first will be invited, and after accepting an invitation, he or she becomes a member of a team. Should be lowercase. This property cannot be changed, doing so forces recreation of the resource. ### Read-Only diff --git a/docs/data-sources/organization_user.md b/docs/data-sources/organization_user.md index 1d0ba7af4..40857e324 100644 --- a/docs/data-sources/organization_user.md +++ b/docs/data-sources/organization_user.md @@ -18,7 +18,7 @@ The Organization User data source provides information about the existing Aiven ### Required - `organization_id` (String) The unique organization ID. This property cannot be changed, doing so forces recreation of the resource. -- `user_email` (String) This is a user email address that first will be invited, and after accepting an invitation, they become a member of the organization. This property cannot be changed, doing so forces recreation of the resource. +- `user_email` (String) This is a user email address that first will be invited, and after accepting an invitation, they become a member of the organization. Should be lowercase. This property cannot be changed, doing so forces recreation of the resource. ### Read-Only diff --git a/docs/data-sources/project_user.md b/docs/data-sources/project_user.md index 4aaf6170e..a8b138627 100644 --- a/docs/data-sources/project_user.md +++ b/docs/data-sources/project_user.md @@ -24,7 +24,7 @@ data "aiven_project_user" "mytestuser" { ### Required -- `email` (String) Email address of the user. This property cannot be changed, doing so forces recreation of the resource. +- `email` (String) Email address of the user. Should be lowercase. This property cannot be changed, doing so forces recreation of the resource. - `project` (String) Identifies the project this resource belongs to. To set up proper dependencies please refer to this variable as a reference. This property cannot be changed, doing so forces recreation of the resource. ### Read-Only diff --git a/docs/resources/account_team_member.md b/docs/resources/account_team_member.md index 6e5324300..3cebe349c 100644 --- a/docs/resources/account_team_member.md +++ b/docs/resources/account_team_member.md @@ -38,7 +38,7 @@ resource "aiven_account_team_member" "foo" { - `account_id` (String) The unique account id. This property cannot be changed, doing so forces recreation of the resource. - `team_id` (String) An account team id. This property cannot be changed, doing so forces recreation of the resource. -- `user_email` (String) Is a user email address that first will be invited, and after accepting an invitation, he or she becomes a member of a team. This property cannot be changed, doing so forces recreation of the resource. +- `user_email` (String) Is a user email address that first will be invited, and after accepting an invitation, he or she becomes a member of a team. Should be lowercase. This property cannot be changed, doing so forces recreation of the resource. ### Optional diff --git a/docs/resources/organization_user.md b/docs/resources/organization_user.md index 80bea0f72..12d550d54 100644 --- a/docs/resources/organization_user.md +++ b/docs/resources/organization_user.md @@ -29,7 +29,7 @@ eliminate the member from the organization if one has accepted an invitation pre ### Required - `organization_id` (String) The unique organization ID. This property cannot be changed, doing so forces recreation of the resource. -- `user_email` (String) This is a user email address that first will be invited, and after accepting an invitation, they become a member of the organization. This property cannot be changed, doing so forces recreation of the resource. +- `user_email` (String) This is a user email address that first will be invited, and after accepting an invitation, they become a member of the organization. Should be lowercase. This property cannot be changed, doing so forces recreation of the resource. ### Optional diff --git a/docs/resources/project_user.md b/docs/resources/project_user.md index b3ab8e863..27dba8360 100644 --- a/docs/resources/project_user.md +++ b/docs/resources/project_user.md @@ -25,7 +25,7 @@ resource "aiven_project_user" "mytestuser" { ### Required -- `email` (String) Email address of the user. This property cannot be changed, doing so forces recreation of the resource. +- `email` (String) Email address of the user. Should be lowercase. This property cannot be changed, doing so forces recreation of the resource. - `member_type` (String) Project membership type. The possible values are `admin`, `developer` and `operator`. - `project` (String) Identifies the project this resource belongs to. To set up proper dependencies please refer to this variable as a reference. This property cannot be changed, doing so forces recreation of the resource. diff --git a/internal/schemautil/schemautil.go b/internal/schemautil/schemautil.go index 06b39a90f..ab071c760 100644 --- a/internal/schemautil/schemautil.go +++ b/internal/schemautil/schemautil.go @@ -2,6 +2,7 @@ package schemautil import ( "fmt" + "net/mail" "net/url" "regexp" "strconv" @@ -222,6 +223,22 @@ func ValidateHumanByteSizeString(v interface{}, k string) (ws []string, errors [ return } +// ValidateEmailAddress is a ValidateFunc that ensures a string is a valid email address +func ValidateEmailAddress(v any, k string) (ws []string, errors []error) { + addr, err := mail.ParseAddress(v.(string)) + if err != nil { + errors = append(errors, err) + + return + } + + if strings.ToLower(addr.Address) != addr.Address { + errors = append(errors, fmt.Errorf("%q: invalid email address", k)) + } + + return +} + func BuildResourceID(parts ...string) string { finalParts := make([]string, len(parts)) for idx, part := range parts { diff --git a/internal/sdkprovider/service/account/account_team_member.go b/internal/sdkprovider/service/account/account_team_member.go index 0b7a010e5..ba504f538 100644 --- a/internal/sdkprovider/service/account/account_team_member.go +++ b/internal/sdkprovider/service/account/account_team_member.go @@ -26,10 +26,14 @@ var aivenAccountTeamMemberSchema = map[string]*schema.Schema{ Description: userconfig.Desc("An account team id").ForceNew().Build(), }, "user_email": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - Description: userconfig.Desc("Is a user email address that first will be invited, and after accepting an invitation, he or she becomes a member of a team.").ForceNew().Build(), + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: userconfig.Desc( + "Is a user email address that first will be invited, and after accepting an invitation, he " + + "or she becomes a member of a team. Should be lowercase.", + ).ForceNew().Build(), + ValidateFunc: schemautil.ValidateEmailAddress, }, "invited_by_user_email": { Type: schema.TypeString, diff --git a/internal/sdkprovider/service/organization/organization_user.go b/internal/sdkprovider/service/organization/organization_user.go index 60e75f62f..1b9c2f095 100644 --- a/internal/sdkprovider/service/organization/organization_user.go +++ b/internal/sdkprovider/service/organization/organization_user.go @@ -23,8 +23,10 @@ var aivenOrganizationUserSchema = map[string]*schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: true, - Description: userconfig.Desc("This is a user email address that first will be invited, and after accepting" + - " an invitation, they become a member of the organization.").ForceNew().Build(), + Description: userconfig.Desc("This is a user email address that first will be invited, " + + "and after accepting an invitation, they become a member of the organization. Should be lowercase.", + ).ForceNew().Build(), + ValidateFunc: schemautil.ValidateEmailAddress, }, "invited_by": { Type: schema.TypeString, diff --git a/internal/sdkprovider/service/project/project_user.go b/internal/sdkprovider/service/project/project_user.go index 7480f0bb3..02b09c625 100644 --- a/internal/sdkprovider/service/project/project_user.go +++ b/internal/sdkprovider/service/project/project_user.go @@ -15,10 +15,11 @@ import ( var aivenProjectUserSchema = map[string]*schema.Schema{ "project": schemautil.CommonSchemaProjectReference, "email": { - ForceNew: true, - Required: true, - Type: schema.TypeString, - Description: userconfig.Desc("Email address of the user.").ForceNew().Build(), + ForceNew: true, + Required: true, + Type: schema.TypeString, + Description: userconfig.Desc("Email address of the user. Should be lowercase.").ForceNew().Build(), + ValidateFunc: schemautil.ValidateEmailAddress, }, "member_type": { Required: true,