Skip to content

Commit f677ae3

Browse files
authored
Merge pull request #1389 from salesforcecli/d/W-21055675
feat: generated passwords default to complexity 3 if a lower value is provided @W-21055675@
2 parents d47d687 + 4e9e275 commit f677ae3

6 files changed

Lines changed: 2889 additions & 2154 deletions

File tree

messages/password.generate.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ version 51.0 of the Metadata API.
7070

7171
Starting in Summer '26, this command will fail if you specify a password length below 20. For now, the command is generating a password of length 20 instead of the requested length.
7272

73+
# defaultingToComplexity3Password
74+
75+
Starting in Summer '26, this command will fail if you specify a password complexity below 3. For now, the command is generating a password of complexity 3 instead of the requested complexity.
76+
7377
# scratchFeaturesUrl
7478

7579
see https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_scratch_orgs_def_file_config_values.htm

src/commands/org/generate/password.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,15 @@ export class GenerateUserPasswordCommand extends UserPasswordGenerateBaseCommand
7171
this.warn(messages.getMessage('defaultingToLength20Password'));
7272
length = 20;
7373
}
74+
let complexity: number = flags.complexity;
75+
if (complexity < 3) {
76+
this.warn(messages.getMessage('defaultingToComplexity3Password'));
77+
complexity = 3;
78+
}
7479
return this.generate({
7580
usernames: ensureArray(flags['on-behalf-of'] ?? flags['target-org'].getUsername()),
7681
length,
77-
complexity: flags.complexity,
82+
complexity,
7883
conn: flags['target-org'].getConnection(flags['api-version']),
7984
});
8085
}

test/allCommands.nut.ts

Lines changed: 74 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ let session: TestSession;
3030

3131
let mainUserId: string | undefined;
3232

33+
const digitArray: string[] = '0123456789'.split('');
34+
const upperArray: string[] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
35+
const lowerArray: string[] = 'abcdefghijklmnopqrstuvwxyz'.split('');
36+
const symbolArray: string[] = '!@#$|%^&*()[]_-'.split('');
37+
3338
describe('verifies all commands run successfully ', () => {
3439
before(async () => {
3540
session = await TestSession.create({
@@ -133,19 +138,49 @@ describe('verifies all commands run successfully ', () => {
133138
}).jsonOutput?.result;
134139
// testing default length
135140
expect(output?.password?.length).to.equal(20);
136-
const complexity5Regex = new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$|%^&*()[\\]_-])');
137141
// testing default complexity
138-
expect(complexity5Regex.test(output?.password ?? '')).to.be.true;
142+
const passwordAsCharArray = (output?.password ?? '').split('');
143+
expect(passwordAsCharArray.some((c) => digitArray.includes(c))).to.equal(
144+
true,
145+
'complexity 5 passwords have digits'
146+
);
147+
expect(passwordAsCharArray.some((c) => upperArray.includes(c))).to.equal(
148+
true,
149+
'complexity 5 passwords have uppercase chars'
150+
);
151+
expect(passwordAsCharArray.some((c) => lowerArray.includes(c))).to.equal(
152+
true,
153+
'complexity 5 passwords have lowercase chars'
154+
);
155+
expect(passwordAsCharArray.some((c) => symbolArray.includes(c))).to.equal(
156+
true,
157+
'complexity 5 passwords have symbols'
158+
);
139159
});
140160

141161
it('generates new passwords for main user testing length 11 and complexity 5', () => {
142-
const output = execCmd<{ username: string; password: string }>('org:generate:password --json -l 11 -c 3', {
162+
const output = execCmd<{ username: string; password: string }>('org:generate:password --json -l 11 -c 5', {
143163
ensureExitCode: 0,
144164
}).jsonOutput?.result;
145165
// Password length gets overridden to 20
146166
expect(output?.password.length).to.equal(20);
147-
const complexity3Regex = new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])');
148-
expect(complexity3Regex.test(output?.password ?? ''));
167+
const passwordAsCharArray = (output?.password ?? '').split('');
168+
expect(passwordAsCharArray.some((c) => digitArray.includes(c))).to.equal(
169+
true,
170+
'complexity 5 passwords have digits'
171+
);
172+
expect(passwordAsCharArray.some((c) => upperArray.includes(c))).to.equal(
173+
true,
174+
'complexity 5 passwords have uppercase chars'
175+
);
176+
expect(passwordAsCharArray.some((c) => lowerArray.includes(c))).to.equal(
177+
true,
178+
'complexity 5 passwords have lowercase chars'
179+
);
180+
expect(passwordAsCharArray.some((c) => symbolArray.includes(c))).to.equal(
181+
true,
182+
'complexity 5 passwords have symbols'
183+
);
149184
});
150185

151186
it('generates new password for secondary user (onbehalfof)', () => {
@@ -160,9 +195,24 @@ describe('verifies all commands run successfully ', () => {
160195

161196
// Password length overridden to 20
162197
expect(output?.password.length).to.equal(20);
163-
const complexity5Regex = new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$|%^&*()[\\]_-])');
164198
// testing the default complexity
165-
expect(complexity5Regex.test(output?.password ?? ''));
199+
const passwordAsCharArray = (output?.password ?? '').split('');
200+
expect(passwordAsCharArray.some((c) => digitArray.includes(c))).to.equal(
201+
true,
202+
'complexity 5 passwords have digits'
203+
);
204+
expect(passwordAsCharArray.some((c) => upperArray.includes(c))).to.equal(
205+
true,
206+
'complexity 5 passwords have uppercase chars'
207+
);
208+
expect(passwordAsCharArray.some((c) => lowerArray.includes(c))).to.equal(
209+
true,
210+
'complexity 5 passwords have lowercase chars'
211+
);
212+
expect(passwordAsCharArray.some((c) => symbolArray.includes(c))).to.equal(
213+
true,
214+
'complexity 5 passwords have symbols'
215+
);
166216
});
167217

168218
it('generates new password for secondary user (onbehalfof) with complexity 3', () => {
@@ -171,8 +221,23 @@ describe('verifies all commands run successfully ', () => {
171221
}).jsonOutput?.result;
172222
// testing default length
173223
expect(output?.password.length).to.equal(20);
174-
const complexity3Regex = new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])');
175-
expect(complexity3Regex.test(output?.password ?? ''));
224+
const passwordAsCharArray = (output?.password ?? '').split('');
225+
expect(passwordAsCharArray.some((c) => digitArray.includes(c))).to.equal(
226+
true,
227+
'complexity 3 passwords have digits'
228+
);
229+
expect(passwordAsCharArray.some((c) => upperArray.includes(c))).to.equal(
230+
true,
231+
'complexity 3 passwords have uppercase chars'
232+
);
233+
expect(passwordAsCharArray.some((c) => lowerArray.includes(c))).to.equal(
234+
true,
235+
'complexity 3 passwords have lowercase chars'
236+
);
237+
expect(passwordAsCharArray.some((c) => symbolArray.includes(c))).to.equal(
238+
false,
239+
'complexity 3 passwords do not have symbols'
240+
);
176241
});
177242

178243
it('generates new password for secondary user (onbehalfof) with complexity 7 should thrown an error', () => {

test/commands/password/generate.test.ts

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,105 @@ describe('org:generate:password', () => {
8585
expect(queryStub.callCount).to.equal(1);
8686
});
8787

88+
describe('--complexity handling', () => {
89+
const digitArray: string[] = '0123456789'.split('');
90+
const upperArray: string[] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
91+
const lowerArray: string[] = 'abcdefghijklmnopqrstuvwxyz'.split('');
92+
const symbolArray: string[] = '!@#$|%^&*()[]_-'.split('');
93+
94+
it('when no complexity is specified, password should default to complexity 5', async () => {
95+
await prepareStubs(false, false);
96+
const result = (await GenerateUserPasswordCommand.run([
97+
'--target-org',
98+
testOrg.username,
99+
'--json',
100+
])) as PasswordData;
101+
102+
const passwordAsCharArray: string[] = result.password.split('');
103+
104+
expect(passwordAsCharArray.some((c) => digitArray.includes(c))).to.equal(
105+
true,
106+
'complexity 5 passwords have digits'
107+
);
108+
expect(passwordAsCharArray.some((c) => upperArray.includes(c))).to.equal(
109+
true,
110+
'complexity 5 passwords have uppercase chars'
111+
);
112+
expect(passwordAsCharArray.some((c) => lowerArray.includes(c))).to.equal(
113+
true,
114+
'complexity 5 passwords have lowercase chars'
115+
);
116+
expect(passwordAsCharArray.some((c) => symbolArray.includes(c))).to.equal(
117+
true,
118+
'complexity 5 passwords have symbols'
119+
);
120+
});
121+
122+
it('when complexity <3 is specified, logs warn-level message and defaults to 3', async () => {
123+
await prepareStubs(false, false);
124+
const uxStubs = stubSfCommandUx($$.SANDBOX);
125+
const result = (await GenerateUserPasswordCommand.run([
126+
'--target-org',
127+
testOrg.username,
128+
'--complexity',
129+
'2',
130+
'--json',
131+
])) as PasswordData;
132+
133+
const passwordAsCharArray: string[] = result.password.split('');
134+
135+
expect(passwordAsCharArray.some((c) => digitArray.includes(c))).to.equal(
136+
true,
137+
'complexity 3 passwords have digits'
138+
);
139+
expect(passwordAsCharArray.some((c) => upperArray.includes(c))).to.equal(
140+
true,
141+
'complexity 3 passwords have uppercase chars'
142+
);
143+
expect(passwordAsCharArray.some((c) => lowerArray.includes(c))).to.equal(
144+
true,
145+
'complexity 3 passwords have lowercase chars'
146+
);
147+
expect(passwordAsCharArray.some((c) => symbolArray.includes(c))).to.equal(
148+
false,
149+
'complexity 3 passwords do not have symbols'
150+
);
151+
expect(uxStubs.warn.args.flat()).to.include(messages.getMessage('defaultingToComplexity3Password'));
152+
});
153+
154+
it('when complexity >=3 is specified, complexity is used as-is', async () => {
155+
await prepareStubs(false, false);
156+
const uxStubs = stubSfCommandUx($$.SANDBOX);
157+
const result = (await GenerateUserPasswordCommand.run([
158+
'--target-org',
159+
testOrg.username,
160+
'--complexity',
161+
'4',
162+
'--json',
163+
])) as PasswordData;
164+
165+
const passwordAsCharArray: string[] = result.password.split('');
166+
167+
expect(passwordAsCharArray.some((c) => digitArray.includes(c))).to.equal(
168+
false,
169+
'complexity 4 passwords do not have digits'
170+
);
171+
expect(passwordAsCharArray.some((c) => upperArray.includes(c))).to.equal(
172+
true,
173+
'complexity 4 passwords have uppercase chars'
174+
);
175+
expect(passwordAsCharArray.some((c) => lowerArray.includes(c))).to.equal(
176+
true,
177+
'complexity 4 passwords have lowercase chars'
178+
);
179+
expect(passwordAsCharArray.some((c) => symbolArray.includes(c))).to.equal(
180+
true,
181+
'complexity 4 passwords have symbols'
182+
);
183+
expect(uxStubs.warn.args.flat()).to.have.length(0, 'no warnings expected');
184+
});
185+
});
186+
88187
describe('--length handling', () => {
89188
it('when no length is specified, password should default to length 20', async () => {
90189
await prepareStubs(false, false);
@@ -97,7 +196,7 @@ describe('org:generate:password', () => {
97196
expect(result.password.length).to.equal(20);
98197
});
99198

100-
it('when length <20 is specified, logs info-level message and defaults to 20', async () => {
199+
it('when length <20 is specified, logs warn-level message and defaults to 20', async () => {
101200
await prepareStubs(false, false);
102201
const uxStubs = stubSfCommandUx($$.SANDBOX);
103202
const result = (await GenerateUserPasswordCommand.run([

test/forceCommands.nut.ts

Lines changed: 74 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ let session: TestSession;
3030

3131
let mainUserId: string | undefined;
3232

33+
const digitArray: string[] = '0123456789'.split('');
34+
const upperArray: string[] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
35+
const lowerArray: string[] = 'abcdefghijklmnopqrstuvwxyz'.split('');
36+
const symbolArray: string[] = '!@#$|%^&*()[]_-'.split('');
37+
3338
describe('verifies legacy force commands run successfully ', () => {
3439
before(async () => {
3540
session = await TestSession.create({
@@ -136,18 +141,48 @@ describe('verifies legacy force commands run successfully ', () => {
136141
}).jsonOutput?.result;
137142
// testing default length
138143
expect(output?.password?.length).to.equal(20);
139-
const complexity5Regex = new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$|%^&*()[\\]_-])');
140144
// testing default complexity
141-
expect(complexity5Regex.test(output?.password ?? '')).to.be.true;
145+
const passwordAsCharArray = (output?.password ?? '').split('');
146+
expect(passwordAsCharArray.some((c) => digitArray.includes(c))).to.equal(
147+
true,
148+
'complexity 5 passwords have digits'
149+
);
150+
expect(passwordAsCharArray.some((c) => upperArray.includes(c))).to.equal(
151+
true,
152+
'complexity 5 passwords have uppercase chars'
153+
);
154+
expect(passwordAsCharArray.some((c) => lowerArray.includes(c))).to.equal(
155+
true,
156+
'complexity 5 passwords have lowercase chars'
157+
);
158+
expect(passwordAsCharArray.some((c) => symbolArray.includes(c))).to.equal(
159+
true,
160+
'complexity 5 passwords have symbols'
161+
);
142162
});
143163

144-
it('generates new password for main user testing length 11 and complexity 5', () => {
164+
it('generates new password for main user testing length 11 and complexity 3', () => {
145165
const output = execCmd<{ username: string; password: string }>('force:user:password:generate --json -l 11 -c 3', {
146166
ensureExitCode: 0,
147167
}).jsonOutput?.result;
148168
expect(output?.password.length).to.equal(11);
149-
const complexity3Regex = new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])');
150-
expect(complexity3Regex.test(output?.password ?? ''));
169+
const passwordAsCharArray = (output?.password ?? '').split('');
170+
expect(passwordAsCharArray.some((c) => digitArray.includes(c))).to.equal(
171+
true,
172+
'complexity 3 passwords have digits'
173+
);
174+
expect(passwordAsCharArray.some((c) => upperArray.includes(c))).to.equal(
175+
true,
176+
'complexity 3 passwords have uppercase chars'
177+
);
178+
expect(passwordAsCharArray.some((c) => lowerArray.includes(c))).to.equal(
179+
true,
180+
'complexity 3 passwords have lowercase chars'
181+
);
182+
expect(passwordAsCharArray.some((c) => symbolArray.includes(c))).to.equal(
183+
false,
184+
'complexity 3 passwords do not have symbols'
185+
);
151186
});
152187

153188
it('generates new password for secondary user (onbehalfof)', () => {
@@ -164,9 +199,24 @@ describe('verifies legacy force commands run successfully ', () => {
164199
).jsonOutput?.result;
165200

166201
expect(output?.password.length).to.equal(12);
167-
const complexity5Regex = new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$|%^&*()[\\]_-])');
168202
// testing the default complexity
169-
expect(complexity5Regex.test(output?.password ?? ''));
203+
const passwordAsCharArray = (output?.password ?? '').split('');
204+
expect(passwordAsCharArray.some((c) => digitArray.includes(c))).to.equal(
205+
true,
206+
'complexity 5 passwords have digits'
207+
);
208+
expect(passwordAsCharArray.some((c) => upperArray.includes(c))).to.equal(
209+
true,
210+
'complexity 5 passwords have uppercase chars'
211+
);
212+
expect(passwordAsCharArray.some((c) => lowerArray.includes(c))).to.equal(
213+
true,
214+
'complexity 5 passwords have lowercase chars'
215+
);
216+
expect(passwordAsCharArray.some((c) => symbolArray.includes(c))).to.equal(
217+
true,
218+
'complexity 5 passwords have symbols'
219+
);
170220
});
171221

172222
it('generates new password for secondary user (onbehalfof) with complexity 3', () => {
@@ -178,8 +228,23 @@ describe('verifies legacy force commands run successfully ', () => {
178228
).jsonOutput?.result;
179229
// testing default length
180230
expect(output?.password.length).to.equal(20);
181-
const complexity3Regex = new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])');
182-
expect(complexity3Regex.test(output?.password ?? ''));
231+
const passwordAsCharArray = (output?.password ?? '').split('');
232+
expect(passwordAsCharArray.some((c) => digitArray.includes(c))).to.equal(
233+
true,
234+
'complexity 3 passwords have digits'
235+
);
236+
expect(passwordAsCharArray.some((c) => upperArray.includes(c))).to.equal(
237+
true,
238+
'complexity 3 passwords have uppercase chars'
239+
);
240+
expect(passwordAsCharArray.some((c) => lowerArray.includes(c))).to.equal(
241+
true,
242+
'complexity 3 passwords have lowercase chars'
243+
);
244+
expect(passwordAsCharArray.some((c) => symbolArray.includes(c))).to.equal(
245+
false,
246+
'complexity 3 passwords do not have symbols'
247+
);
183248
});
184249

185250
it('generates new password for secondary user (onbehalfof) with complexity 7 should thrown an error', () => {

0 commit comments

Comments
 (0)