Thanks to visit codestin.com
Credit goes to github.com

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

+33-30
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

+23-3
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)