Skip to content

Commit 0e3df53

Browse files
committed
fix: validate password logintype combos
1 parent 9aa27ba commit 0e3df53

File tree

2 files changed

+56
-33
lines changed

2 files changed

+56
-33
lines changed

internal/provider/user_resource.go

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ func (r *UserResource) Metadata(ctx context.Context, req resource.MetadataReques
5656
func (r *UserResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
5757
resp.Schema = schema.Schema{
5858
MarkdownDescription: "A user on the Coder deployment.",
59-
6059
Attributes: map[string]schema.Attribute{
6160
"id": schema.StringAttribute{
6261
CustomType: UUIDType,
@@ -66,27 +65,23 @@ func (r *UserResource) Schema(ctx context.Context, req resource.SchemaRequest, r
6665
stringplanmodifier.UseStateForUnknown(),
6766
},
6867
},
69-
7068
"username": schema.StringAttribute{
7169
MarkdownDescription: "Username of the user.",
7270
Required: true,
7371
},
7472
"name": schema.StringAttribute{
75-
Computed: true,
7673
MarkdownDescription: "Display name of the user. Defaults to username.",
77-
Required: false,
74+
Computed: true,
7875
Optional: true,
79-
// Defaulted in Create
8076
},
8177
"email": schema.StringAttribute{
8278
MarkdownDescription: "Email address of the user.",
8379
Required: true,
8480
},
8581
"roles": schema.SetAttribute{
8682
MarkdownDescription: "Roles assigned to the user. Valid roles are 'owner', 'template-admin', 'user-admin', and 'auditor'.",
87-
Required: false,
88-
Optional: true,
8983
Computed: true,
84+
Optional: true,
9085
ElementType: types.StringType,
9186
Validators: []validator.Set{
9287
setvalidator.ValueStringsAre(
@@ -97,24 +92,24 @@ func (r *UserResource) Schema(ctx context.Context, req resource.SchemaRequest, r
9792
},
9893
"login_type": schema.StringAttribute{
9994
MarkdownDescription: "Type of login for the user. Valid types are 'none', 'password', 'github', and 'oidc'.",
100-
Required: false,
101-
Optional: true,
10295
Computed: true,
96+
Optional: true,
10397
Validators: []validator.String{
10498
stringvalidator.OneOf("none", "password", "github", "oidc"),
10599
},
106100
Default: stringdefault.StaticString("none"),
101+
PlanModifiers: []planmodifier.String{
102+
stringplanmodifier.RequiresReplaceIfConfigured(),
103+
},
107104
},
108105
"password": schema.StringAttribute{
109106
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.",
110-
Required: false,
111107
Optional: true,
112108
Sensitive: true,
113109
},
114110
"suspended": schema.BoolAttribute{
115-
Computed: true,
116111
MarkdownDescription: "Whether the user is suspended.",
117-
Required: false,
112+
Computed: true,
118113
Optional: true,
119114
Default: booldefault.StaticBool(false),
120115
},
@@ -164,14 +159,15 @@ func (r *UserResource) Create(ctx context.Context, req resource.CreateRequest, r
164159
}
165160

166161
tflog.Trace(ctx, "creating user")
167-
loginType := codersdk.LoginTypeNone
168-
if data.LoginType.ValueString() != "" {
169-
loginType = codersdk.LoginType(data.LoginType.ValueString())
170-
}
171-
if loginType == codersdk.LoginTypePassword && data.Password.ValueString() == "" {
162+
loginType := codersdk.LoginType(data.LoginType.ValueString())
163+
if loginType == codersdk.LoginTypePassword && data.Password.IsNull() {
172164
resp.Diagnostics.AddError("Data Error", "Password is required when login_type is 'password'")
173165
return
174166
}
167+
if loginType != codersdk.LoginTypePassword && !data.Password.IsNull() {
168+
resp.Diagnostics.AddError("Data Error", "Password is only allowed when login_type is 'password'")
169+
return
170+
}
175171
user, err := client.CreateUser(ctx, codersdk.CreateUserRequest{
176172
Email: data.Email.ValueString(),
177173
Username: data.Username.ValueString(),
@@ -189,13 +185,13 @@ func (r *UserResource) Create(ctx context.Context, req resource.CreateRequest, r
189185
data.ID = UUIDValue(user.ID)
190186

191187
tflog.Trace(ctx, "updating user profile")
192-
name := data.Username.ValueString()
188+
name := data.Username
193189
if data.Name.ValueString() != "" {
194-
name = data.Name.ValueString()
190+
name = data.Name
195191
}
196192
user, err = client.UpdateUserProfile(ctx, user.ID.String(), codersdk.UpdateUserProfileRequest{
197193
Username: data.Username.ValueString(),
198-
Name: name,
194+
Name: name.ValueString(),
199195
})
200196
if err != nil {
201197
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update newly created user profile, got error: %s", err))
@@ -290,18 +286,23 @@ func (r *UserResource) Update(ctx context.Context, req resource.UpdateRequest, r
290286
return
291287
}
292288

289+
name := data.Username
290+
if data.Name.ValueString() != "" {
291+
name = data.Name
292+
}
293293
tflog.Trace(ctx, "updating user", map[string]any{
294294
"new_username": data.Username.ValueString(),
295-
"new_name": data.Name.ValueString(),
295+
"new_name": name.ValueString(),
296296
})
297297
_, err = client.UpdateUserProfile(ctx, user.ID.String(), codersdk.UpdateUserProfileRequest{
298298
Username: data.Username.ValueString(),
299-
Name: data.Name.ValueString(),
299+
Name: name.ValueString(),
300300
})
301301
if err != nil {
302302
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update user profile, got error: %s", err))
303303
return
304304
}
305+
data.Name = name
305306
tflog.Trace(ctx, "successfully updated user profile")
306307

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

323-
tflog.Trace(ctx, "updating password")
324-
err = client.UpdateUserPassword(ctx, user.ID.String(), codersdk.UpdateUserPasswordRequest{
325-
Password: data.Password.ValueString(),
326-
})
327-
if err != nil && !strings.Contains(err.Error(), "New password cannot match old password.") {
328-
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update password, got error: %s", err))
329-
return
324+
if data.LoginType.ValueString() == string(codersdk.LoginTypePassword) && !data.Password.IsNull() {
325+
tflog.Trace(ctx, "updating password")
326+
err = client.UpdateUserPassword(ctx, user.ID.String(), codersdk.UpdateUserPasswordRequest{
327+
Password: data.Password.ValueString(),
328+
})
329+
if err != nil && !strings.Contains(err.Error(), "New password cannot match old password.") {
330+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update password, got error: %s", err))
331+
return
332+
}
333+
tflog.Trace(ctx, "successfully updated password")
330334
}
331-
tflog.Trace(ctx, "successfully updated password")
332335

333336
var statusErr error
334337
if data.Suspended.ValueBool() {

internal/provider/user_resource_test.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,16 @@ func TestAccUserResource(t *testing.T) {
3232

3333
cfg2 := cfg1
3434
cfg2.Username = PtrTo("exampleNew")
35-
cfg2.Name = PtrTo("Example User New")
35+
36+
cfg3 := cfg2
37+
cfg3.Name = PtrTo("Example New")
38+
39+
cfg4 := cfg3
40+
cfg4.LoginType = PtrTo("github")
41+
cfg4.Password = nil
3642

3743
resource.Test(t, resource.TestCase{
44+
IsUnitTest: true,
3845
PreCheck: func() { testAccPreCheck(t) },
3946
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
4047
Steps: []resource.TestStep{
@@ -66,10 +73,23 @@ func TestAccUserResource(t *testing.T) {
6673
Config: cfg2.String(t),
6774
Check: resource.ComposeAggregateTestCheckFunc(
6875
resource.TestCheckResourceAttr("coderd_user.test", "username", "exampleNew"),
69-
resource.TestCheckResourceAttr("coderd_user.test", "name", "Example User New"),
76+
resource.TestCheckResourceAttr("coderd_user.test", "name", "Example User"),
77+
),
78+
},
79+
{
80+
Config: cfg3.String(t),
81+
Check: resource.ComposeAggregateTestCheckFunc(
82+
resource.TestCheckResourceAttr("coderd_user.test", "username", "exampleNew"),
83+
resource.TestCheckResourceAttr("coderd_user.test", "name", "Example New"),
84+
),
85+
},
86+
// Replace triggered
87+
{
88+
Config: cfg4.String(t),
89+
Check: resource.ComposeAggregateTestCheckFunc(
90+
resource.TestCheckResourceAttr("coderd_user.test", "login_type", "github"),
7091
),
7192
},
72-
// Delete testing automatically occurs in TestCase
7393
},
7494
})
7595
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy