-
-
Notifications
You must be signed in to change notification settings - Fork 10.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feature: implement password policies to avoid weak passwords #4008
Conversation
Codecov Report
@@ Coverage Diff @@
## master #4008 +/- ##
============================================
+ Coverage 51.31% 51.46% +0.15%
- Complexity 2504 2524 +20
============================================
Files 482 484 +2
Lines 14772 14798 +26
Branches 1528 1532 +4
============================================
+ Hits 7580 7616 +36
+ Misses 6662 6652 -10
Partials 530 530
Continue to review full report at Codecov.
|
...lo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/UserInfoController.java
Outdated
Show resolved
Hide resolved
apollo-portal/src/main/resources/static/scripts/controller/UserController.js
Outdated
Show resolved
Hide resolved
user.setUsername("username"); | ||
user.setPassword("9sivg8hg3wjz8"); | ||
userInfoController.createOrUpdateUser(user); | ||
Assert.assertTrue(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is pointless
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, this test case has been removed. Thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @WillardHu , I don't mean to remove the case, I mean the assertTrue(true)
is pointless, removing this line should be enough
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, done.
...-portal/src/test/java/com/ctrip/framework/apollo/portal/util/CommonlyUsedPwdCheckerTest.java
Outdated
Show resolved
Hide resolved
70f1d0c
to
edc6de5
Compare
edc6de5
to
cc1cb83
Compare
} | ||
|
||
/** | ||
* @return The passwrod contains code fragment or is blank. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* @return The passwrod contains code fragment or is blank. | |
* @return The password contains code fragment or is blank. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, thanks.
public static boolean notMatchRegex(String password) { | ||
return !PWD_PATTERN.matcher(password).matches(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public static boolean notMatchRegex(String password) { | |
return !PWD_PATTERN.matcher(password).matches(); | |
} | |
public static boolean isWeakPassword(String password) { | |
return !PWD_PATTERN.matcher(password).matches() || isCommonlyUsed(password); | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/util/UserPasswordChecker.java
Outdated
Show resolved
Hide resolved
var alpha_chars =[ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', | ||
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']; | ||
|
||
function randomAlphanumeric(len) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure whether we should provide this functionality, because most modern browsers (like Safari) or password manager (like 1password) has this ability to generate strong random password, this function defined here is rather naive IMO
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with you if it's self-registration, but in this scenario, it is the administrator manage the account. This is just a convenience for administrators to generate default passwords for members if he wanted. And the browser's password generation extension may only work for the password component. If you don't want it, I'll remove it. Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@WillardHu Let's wait for other reviewers' opinions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would remove that functionality since strong password
generation can be done by the browser.
Additionally a custom password generator would be harder to maintain (for example if the password policy changes etc) with little to none benefit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would also suggest we don't provide this functionality as it is not a core function of apollo and there are many plugins or browsers doing this job much better. What we need to do though is to make sure the password form is compatible with those plugins or browsers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, the frontend modifications have been rollback.
cc1cb83
to
f9312ba
Compare
f9312ba
to
a732f6c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The rest looks good to me.
if (UserPasswordChecker.isWeakPassword(user.getPassword())) { | ||
throw new BadRequestException( | ||
"Password needs a number and lowercase letter and between 8~20 characters. " | ||
+ "And cannot be a weak password."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you should adapt the message. Makes it more useable and clear to the user what the real reason is.
It can be not matching to the pattern, weak password
or both.
If it is just one and you display both reasons in the error message the user might not understand it why you get both.
PS: you should also explain what a weak password
is in the exception
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I've split the error message, and wrap it with CheckResult
.
|
||
public class UserPasswordChecker { | ||
|
||
private static final Pattern PWD_PATTERN = Pattern |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be nice if you could write few test cases that check if this pattern really works as intended. Regex is often a reason for bugs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, done.
"1q2w", "2w3e", "3e4r", "5t6y", "abcd", "qwer", "asdf", "zxcv" | ||
); | ||
|
||
public static boolean isWeakPassword(String password) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest we use instance method instead of static method so that we could mock this method easily.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I used Spring IOC to register the bean instance of UserPasswordChecker
interface’s implementation class AuthUserPasswordChecker
.
/** | ||
* @return The password contains code fragment or is blank. | ||
*/ | ||
private static boolean isCommonlyUsed(String password) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest we use instance method instead of static method so that we could mock this method easily.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, done.
try { | ||
userInfoController.createOrUpdateUser(user); | ||
} catch (BadRequestException e) { | ||
Assert.assertEquals( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's better we mock password checker here instead of testing the password logic here, as it is not core logic of UserInfoController.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, done.
import org.junit.Assert; | ||
import org.junit.Test; | ||
|
||
public class CommonlyUsedPwdCheckerTest { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test should be renamed as something like UserPasswordCheckerTest
so that we will test all kinds of weak password scenarios.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, done.
a732f6c
to
465491c
Compare
CheckResult pwdCheckRes; | ||
if (!(pwdCheckRes = passwordChecker.checkWeakPassword(user.getPassword())).isSuccess()) { | ||
throw new BadRequestException(pwdCheckRes.getMessage()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason why you assign pwdCheckRes
inside the if
and not before when you declare the var pwdCheckRes
?
Otherwise to make the code more readable I would suggest to change it to
CheckResult pwdCheckRes = passwordChecker.checkWeakPassword(user.getPassword());
if (!pwdCheckRes.isSuccess()) {
throw new BadRequestException(pwdCheckRes.getMessage());
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, done.
Signed-off-by: WillardHu <wei.hu@daocloud.io>
465491c
to
9b9281e
Compare
Good work! 👍🏽:shipit: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Signed-off-by: WillardHu wei.hu@daocloud.io
What's the purpose of this PR
Closes #3948
Which issue(s) this PR fixes:
UserInfoController.createOrUpdateUser(..)
method;Brief changelog
Implement password policies to avoid weak passwords
Follow this checklist to help us incorporate your contribution quickly and easily:
mvn clean test
to make sure this pull request doesn't break anything.CHANGES
log.