Skip to content

Commit

Permalink
fix: validate password logintype combos
Browse files Browse the repository at this point in the history
  • Loading branch information
ethanndickson committed Jul 31, 2024
1 parent 9aa27ba commit d1f4820
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 32 deletions.
62 changes: 33 additions & 29 deletions internal/provider/user_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,27 +66,23 @@ func (r *UserResource) Schema(ctx context.Context, req resource.SchemaRequest, r
stringplanmodifier.UseStateForUnknown(),
},
},

"username": schema.StringAttribute{
MarkdownDescription: "Username of the user.",
Required: true,
},
"name": schema.StringAttribute{
Computed: true,
MarkdownDescription: "Display name of the user. Defaults to username.",
Required: false,
Computed: true,
Optional: true,
// Defaulted in Create
},
"email": schema.StringAttribute{
MarkdownDescription: "Email address of the user.",
Required: true,
},
"roles": schema.SetAttribute{
MarkdownDescription: "Roles assigned to the user. Valid roles are 'owner', 'template-admin', 'user-admin', and 'auditor'.",
Required: false,
Optional: true,
Computed: true,
Optional: true,
ElementType: types.StringType,
Validators: []validator.Set{
setvalidator.ValueStringsAre(
Expand All @@ -97,24 +93,24 @@ func (r *UserResource) Schema(ctx context.Context, req resource.SchemaRequest, r
},
"login_type": schema.StringAttribute{
MarkdownDescription: "Type of login for the user. Valid types are 'none', 'password', 'github', and 'oidc'.",
Required: false,
Optional: true,
Computed: true,
Optional: true,
Validators: []validator.String{
stringvalidator.OneOf("none", "password", "github", "oidc"),
},
Default: stringdefault.StaticString("none"),
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplaceIfConfigured(),
},
},
"password": schema.StringAttribute{
MarkdownDescription: "Password for the user. Required when login_type is 'password'. Passwords are saved into the state as plain text and should only be used for testing purposes.",
Required: false,
Optional: true,
Sensitive: true,
},
"suspended": schema.BoolAttribute{
Computed: true,
MarkdownDescription: "Whether the user is suspended.",
Required: false,
Computed: true,
Optional: true,
Default: booldefault.StaticBool(false),
},
Expand Down Expand Up @@ -164,14 +160,15 @@ func (r *UserResource) Create(ctx context.Context, req resource.CreateRequest, r
}

tflog.Trace(ctx, "creating user")
loginType := codersdk.LoginTypeNone
if data.LoginType.ValueString() != "" {
loginType = codersdk.LoginType(data.LoginType.ValueString())
}
if loginType == codersdk.LoginTypePassword && data.Password.ValueString() == "" {
loginType := codersdk.LoginType(data.LoginType.ValueString())
if loginType == codersdk.LoginTypePassword && data.Password.IsNull() {
resp.Diagnostics.AddError("Data Error", "Password is required when login_type is 'password'")
return
}
if loginType != codersdk.LoginTypePassword && !data.Password.IsNull() {
resp.Diagnostics.AddError("Data Error", "Password is only allowed when login_type is 'password'")
return
}
user, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
Email: data.Email.ValueString(),
Username: data.Username.ValueString(),
Expand All @@ -189,13 +186,13 @@ func (r *UserResource) Create(ctx context.Context, req resource.CreateRequest, r
data.ID = UUIDValue(user.ID)

tflog.Trace(ctx, "updating user profile")
name := data.Username.ValueString()
name := data.Username
if data.Name.ValueString() != "" {
name = data.Name.ValueString()
name = data.Name
}
user, err = client.UpdateUserProfile(ctx, user.ID.String(), codersdk.UpdateUserProfileRequest{
Username: data.Username.ValueString(),
Name: name,
Name: name.ValueString(),
})
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update newly created user profile, got error: %s", err))
Expand Down Expand Up @@ -290,18 +287,23 @@ func (r *UserResource) Update(ctx context.Context, req resource.UpdateRequest, r
return
}

name := data.Username
if data.Name.ValueString() != "" {
name = data.Name
}
tflog.Trace(ctx, "updating user", map[string]any{
"new_username": data.Username.ValueString(),
"new_name": data.Name.ValueString(),
"new_name": name.ValueString(),
})
_, err = client.UpdateUserProfile(ctx, user.ID.String(), codersdk.UpdateUserProfileRequest{
Username: data.Username.ValueString(),
Name: data.Name.ValueString(),
Name: name.ValueString(),
})
if err != nil {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update user profile, got error: %s", err))
return
}
data.Name = name
tflog.Trace(ctx, "successfully updated user profile")

var roles []string
Expand All @@ -320,15 +322,17 @@ func (r *UserResource) Update(ctx context.Context, req resource.UpdateRequest, r
}
tflog.Trace(ctx, "successfully updated user roles")

tflog.Trace(ctx, "updating password")
err = client.UpdateUserPassword(ctx, user.ID.String(), codersdk.UpdateUserPasswordRequest{
Password: data.Password.ValueString(),
})
if err != nil && !strings.Contains(err.Error(), "New password cannot match old password.") {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update password, got error: %s", err))
return
if data.LoginType.ValueString() == string(codersdk.LoginTypePassword) && !data.Password.IsNull() {
tflog.Trace(ctx, "updating password")
err = client.UpdateUserPassword(ctx, user.ID.String(), codersdk.UpdateUserPasswordRequest{
Password: data.Password.ValueString(),
})
if err != nil && !strings.Contains(err.Error(), "New password cannot match old password.") {
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update password, got error: %s", err))
return
}
tflog.Trace(ctx, "successfully updated password")
}
tflog.Trace(ctx, "successfully updated password")

var statusErr error
if data.Suspended.ValueBool() {
Expand Down
26 changes: 23 additions & 3 deletions internal/provider/user_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,16 @@ func TestAccUserResource(t *testing.T) {

cfg2 := cfg1
cfg2.Username = PtrTo("exampleNew")
cfg2.Name = PtrTo("Example User New")

cfg3 := cfg2
cfg3.Name = PtrTo("Example New")

cfg4 := cfg3
cfg4.LoginType = PtrTo("github")
cfg4.Password = nil

resource.Test(t, resource.TestCase{
IsUnitTest: true,
PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
Expand Down Expand Up @@ -66,10 +73,23 @@ func TestAccUserResource(t *testing.T) {
Config: cfg2.String(t),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("coderd_user.test", "username", "exampleNew"),
resource.TestCheckResourceAttr("coderd_user.test", "name", "Example User New"),
resource.TestCheckResourceAttr("coderd_user.test", "name", "Example User"),
),
},
{
Config: cfg3.String(t),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("coderd_user.test", "username", "exampleNew"),
resource.TestCheckResourceAttr("coderd_user.test", "name", "Example New"),
),
},
// Replace triggered
{
Config: cfg4.String(t),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("coderd_user.test", "login_type", "github"),
),
},
// Delete testing automatically occurs in TestCase
},
})
}
Expand Down

0 comments on commit d1f4820

Please sign in to comment.