Skip to content

Commit a13c1a8

Browse files
committed
Merge branch 'password-validation' into 'main'
Adding user password validation support See merge request weblogic-cloud/weblogic-deploy-tooling!1537
2 parents d908144 + e4d16f2 commit a13c1a8

File tree

11 files changed

+600
-11
lines changed

11 files changed

+600
-11
lines changed
Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
/*
2+
* Copyright (c) 2023, Oracle Corporation and/or its affiliates.
3+
* Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
4+
*/
5+
package oracle.weblogic.deploy.validate;
6+
7+
import java.util.HashMap;
8+
import java.util.Map;
9+
10+
import oracle.weblogic.deploy.logging.PlatformLogger;
11+
import oracle.weblogic.deploy.logging.WLSDeployLogFactory;
12+
import oracle.weblogic.deploy.util.StringUtils;
13+
14+
/**
15+
* The PasswordValidator class that works similar to the SystemPasswordValidator provider in WebLogic Server
16+
*/
17+
public class PasswordValidator {
18+
private static final String CLASS = PasswordValidator.class.getName();
19+
private static final PlatformLogger LOGGER = WLSDeployLogFactory.getLogger("wlsdeploy.validate");
20+
21+
public static final String MAX_CONSECUTIVE_CHARACTERS = "MaxConsecutiveCharacters";
22+
public static final String MAX_INSTANCES_OF_ANY_CHARACTER = "MaxInstancesOfAnyCharacter";
23+
public static final String MAX_PASSWORD_LENGTH = "MaxPasswordLength";
24+
public static final String MIN_ALPHABETIC_CHARACTERS = "MinAlphabeticCharacters";
25+
public static final String MIN_LOWERCASE_CHARACTERS = "MinLowercaseCharacters";
26+
public static final String MIN_NON_ALPHANUMERIC_CHARACTERS = "MinNonAlphanumericCharacters";
27+
public static final String MIN_NUMERIC_CHARACTERS = "MinNumericCharacters";
28+
public static final String MIN_NUMERIC_OR_SPECIAL_CHARACTERS = "MinNumericOrSpecialCharacters";
29+
public static final String MIN_PASSWORD_LENGTH = "MinPasswordLength";
30+
public static final String MIN_UPPERCASE_CHARACTERS = "MinUppercaseCharacters";
31+
public static final String REJECT_EQUAL_OR_CONTAIN_REVERSE_USERNAME = "RejectEqualOrContainReverseUsername";
32+
public static final String REJECT_EQUAL_OR_CONTAIN_USERNAME = "RejectEqualOrContainUsername";
33+
34+
private static final int NO_RESTRICTION = 0;
35+
private static final boolean NOT_SET = false;
36+
37+
private static final char[] DISALLOWED_PASSWORD_START_CHARACTERS = { '{' };
38+
39+
private final Map<String, Object> config;
40+
41+
/**
42+
* The constructor.
43+
*
44+
* @param config a map of the configuration values
45+
*/
46+
public PasswordValidator(Map<String, Object> config) {
47+
this.config = config;
48+
}
49+
50+
/**
51+
* Validate the password using the configured rules.
52+
*
53+
* @param username the username of the user whose password is being validated
54+
* @param pass the password
55+
* @return true, if the password is valid; false otherwise
56+
* @throws ValidateException if the username (after trimming whitespace) or password is empty
57+
*/
58+
public boolean validate(String username, String pass) throws ValidateException {
59+
final String METHOD = "validate";
60+
61+
LOGGER.entering(CLASS, METHOD, username);
62+
String user = username.trim();
63+
64+
if (StringUtils.isEmpty(user)) {
65+
ValidateException ex = new ValidateException("WLSDPLY-05400");
66+
LOGGER.throwing(CLASS, METHOD, ex);
67+
throw ex;
68+
} else if (StringUtils.isEmpty(pass)) {
69+
ValidateException ex = new ValidateException("WLSDPLY-05401", user);
70+
LOGGER.throwing(CLASS, METHOD, ex);
71+
throw ex;
72+
}
73+
74+
boolean isValid = !validatePasswordLength(user, pass);
75+
if (validatePasswordDoesNotContainUsername(user, pass)) {
76+
isValid = false;
77+
}
78+
if (validatePasswordDoesNotContainReverseUsername(user, pass)) {
79+
isValid = false;
80+
}
81+
if (validatePasswordMaxInstancesOfCharacter(user, pass)) {
82+
isValid = false;
83+
}
84+
if (validatePasswordMaxConsecutiveCharacter(user, pass)) {
85+
isValid = false;
86+
}
87+
if (validatePasswordMinCharacterTypeRequirements(user, pass)) {
88+
isValid = false;
89+
}
90+
if (validatePasswordDisallowedStartCharacters(user, pass)) {
91+
isValid = false;
92+
}
93+
94+
LOGGER.exiting(CLASS, METHOD, isValid);
95+
return isValid;
96+
}
97+
98+
private boolean validatePasswordLength(String user, String pass) {
99+
final String METHOD = "validatePasswordLength";
100+
LOGGER.entering(CLASS, METHOD, user);
101+
102+
boolean foundErrors = false;
103+
final int minLength = getIntegerFieldConfiguration(MIN_PASSWORD_LENGTH);
104+
final int maxLength = getIntegerFieldConfiguration(MAX_PASSWORD_LENGTH);
105+
if (minLength > NO_RESTRICTION && pass.length() < minLength) {
106+
foundErrors = true;
107+
LOGGER.severe("WLSDPLY-05402", user, pass.length(), minLength);
108+
}
109+
if (maxLength > NO_RESTRICTION && pass.length() > maxLength) {
110+
foundErrors = true;
111+
LOGGER.severe("WLSDPLY-05403", user, pass.length(), maxLength);
112+
}
113+
114+
LOGGER.exiting(CLASS, METHOD, foundErrors);
115+
return foundErrors;
116+
}
117+
118+
private boolean validatePasswordDoesNotContainUsername(String user, String pass) {
119+
final String METHOD = "validatePasswordDoesNotContainUsername";
120+
LOGGER.entering(CLASS, METHOD, user);
121+
122+
boolean foundErrors = false;
123+
final boolean checkForUserName = getBooleanFieldConfiguration(REJECT_EQUAL_OR_CONTAIN_USERNAME);
124+
if (checkForUserName) {
125+
int index = pass.toLowerCase().indexOf(user.toLowerCase());
126+
if (index >= 0) {
127+
foundErrors = true;
128+
LOGGER.severe("WLSDPLY-05404", user);
129+
}
130+
}
131+
132+
LOGGER.exiting(CLASS, METHOD, foundErrors);
133+
return foundErrors;
134+
}
135+
136+
private boolean validatePasswordDoesNotContainReverseUsername(String user, String pass) {
137+
final String METHOD = "validatePasswordDoesNotContainReverseUsername";
138+
LOGGER.entering(CLASS, METHOD, user);
139+
140+
boolean foundErrors = false;
141+
final boolean checkForUserName = getBooleanFieldConfiguration(REJECT_EQUAL_OR_CONTAIN_REVERSE_USERNAME);
142+
if (checkForUserName) {
143+
String reversedUser = new StringBuffer(user).reverse().toString();
144+
int index = pass.toLowerCase().indexOf(reversedUser.toLowerCase());
145+
if (index >= 0) {
146+
foundErrors = true;
147+
LOGGER.severe("WLSDPLY-05405", user, reversedUser);
148+
}
149+
}
150+
151+
LOGGER.exiting(CLASS, METHOD, foundErrors);
152+
return foundErrors;
153+
}
154+
155+
private boolean validatePasswordMaxInstancesOfCharacter(String user, String pass) {
156+
final String METHOD = "validatePasswordMaxInstancesOfCharacter";
157+
LOGGER.entering(CLASS, METHOD, user);
158+
159+
boolean foundErrors = false;
160+
final int maxInstancesOfChar = getIntegerFieldConfiguration(MAX_INSTANCES_OF_ANY_CHARACTER);
161+
if (maxInstancesOfChar > NO_RESTRICTION) {
162+
Map<Character, Integer> characterCountMap = new HashMap<>();
163+
for (int i = 0; i < pass.length(); i++) {
164+
char c = pass.charAt(i);
165+
incrementCharacterCount(characterCountMap, c);
166+
}
167+
168+
// Look for errors.
169+
for (Map.Entry<Character, Integer> entry : characterCountMap.entrySet()) {
170+
char c = entry.getKey();
171+
int count = entry.getValue();
172+
if (count > maxInstancesOfChar) {
173+
foundErrors = true;
174+
LOGGER.severe("WLSDPLY-05406", user, maxInstancesOfChar, count, String.valueOf(c));
175+
}
176+
}
177+
}
178+
179+
LOGGER.exiting(CLASS, METHOD, foundErrors);
180+
return foundErrors;
181+
}
182+
183+
private boolean validatePasswordMaxConsecutiveCharacter(String user, String pass) {
184+
final String METHOD = "validatePasswordMaxConsecutiveCharacter";
185+
LOGGER.entering(CLASS, METHOD, user);
186+
187+
boolean foundErrors = false;
188+
int maxConsecutiveChars = getIntegerFieldConfiguration(MAX_CONSECUTIVE_CHARACTERS);
189+
if (maxConsecutiveChars > NO_RESTRICTION) {
190+
char lastChar = 0;
191+
int count = 0;
192+
for (int i = 0; i < pass.length(); i++) {
193+
char c = pass.charAt(i);
194+
if (c == lastChar) {
195+
count++;
196+
} else {
197+
if (count > maxConsecutiveChars) {
198+
foundErrors = true;
199+
LOGGER.severe("WLSDPLY-05407", user, maxConsecutiveChars, count, lastChar);
200+
}
201+
lastChar = c;
202+
count = 1;
203+
}
204+
}
205+
}
206+
207+
LOGGER.exiting(CLASS, METHOD, foundErrors);
208+
return foundErrors;
209+
}
210+
211+
private boolean validatePasswordMinCharacterTypeRequirements(String user, String pass) {
212+
final String METHOD = "validatePasswordMinCharacterTypeRequirements";
213+
LOGGER.entering(CLASS, METHOD, user);
214+
215+
int minAlphabeticChars = getIntegerFieldConfiguration(MIN_ALPHABETIC_CHARACTERS);
216+
int minNumericChars = getIntegerFieldConfiguration(MIN_NUMERIC_CHARACTERS);
217+
int minNonAlphanumericChars = getIntegerFieldConfiguration(MIN_NON_ALPHANUMERIC_CHARACTERS);
218+
int minLowercaseChars = getIntegerFieldConfiguration(MIN_LOWERCASE_CHARACTERS);
219+
int minUppercaseChars = getIntegerFieldConfiguration(MIN_UPPERCASE_CHARACTERS);
220+
int minNumericOrSpecialChars = getIntegerFieldConfiguration(MIN_NUMERIC_OR_SPECIAL_CHARACTERS);
221+
222+
int alphabeticCount = 0;
223+
int numericCount = 0;
224+
int nonAlphanumericCount = 0;
225+
int lowercaseCount = 0;
226+
int uppercaseCount = 0;
227+
int numericOrSpecialCount = 0;
228+
for (int i = 0; i < pass.length(); i++) {
229+
char c = pass.charAt(i);
230+
if (Character.isLowerCase(c)) {
231+
alphabeticCount++;
232+
lowercaseCount++;
233+
} else if (Character.isUpperCase(c)) {
234+
alphabeticCount++;
235+
uppercaseCount++;
236+
} else if (Character.isDigit(c)) {
237+
numericCount++;
238+
numericOrSpecialCount++;
239+
} else {
240+
nonAlphanumericCount++;
241+
numericOrSpecialCount++;
242+
}
243+
}
244+
245+
boolean foundErrors = false;
246+
if (minAlphabeticChars > NO_RESTRICTION && alphabeticCount < minAlphabeticChars) {
247+
foundErrors = true;
248+
LOGGER.severe("WLSDPLY-05408", user, alphabeticCount, minAlphabeticChars);
249+
}
250+
if (minNumericChars > NO_RESTRICTION && numericCount < minNumericChars) {
251+
foundErrors = true;
252+
LOGGER.severe("WLSDPLY-05409", user, numericCount, minNumericChars);
253+
}
254+
if (minLowercaseChars > NO_RESTRICTION && lowercaseCount < minLowercaseChars) {
255+
foundErrors = true;
256+
LOGGER.severe("WLSDPLY-05410", user, lowercaseCount, minLowercaseChars);
257+
}
258+
if (minUppercaseChars > NO_RESTRICTION && uppercaseCount < minUppercaseChars) {
259+
foundErrors = true;
260+
LOGGER.severe("WLSDPLY-05411", user, uppercaseCount, minUppercaseChars);
261+
}
262+
if (minNonAlphanumericChars > NO_RESTRICTION && nonAlphanumericCount < minNonAlphanumericChars) {
263+
foundErrors = true;
264+
LOGGER.severe("WLSDPLY-05412", user, uppercaseCount, minNonAlphanumericChars);
265+
}
266+
if (minNumericOrSpecialChars > NO_RESTRICTION && numericOrSpecialCount < minNumericOrSpecialChars) {
267+
foundErrors = true;
268+
LOGGER.severe("WLSDPLY-05413", user, numericOrSpecialCount, minNumericOrSpecialChars);
269+
}
270+
271+
LOGGER.exiting(CLASS, METHOD, foundErrors);
272+
return foundErrors;
273+
}
274+
275+
private boolean validatePasswordDisallowedStartCharacters(String user, String pass) {
276+
final String METHOD = "validatePasswordDisallowedStartCharacters";
277+
LOGGER.entering(CLASS, METHOD, user);
278+
279+
boolean foundErrors = false;
280+
char startChar = pass.charAt(0);
281+
for (char c : DISALLOWED_PASSWORD_START_CHARACTERS) {
282+
if (startChar == c) {
283+
foundErrors = true;
284+
LOGGER.severe("WLSDPLY-05414", user, String.valueOf(c));
285+
}
286+
}
287+
288+
LOGGER.exiting(CLASS, METHOD, foundErrors);
289+
return foundErrors;
290+
}
291+
292+
private int getIntegerFieldConfiguration(String fieldName) {
293+
int result = NO_RESTRICTION;
294+
if (config.containsKey(fieldName)) {
295+
Object value = config.get(fieldName);
296+
if (Integer.class.isAssignableFrom(value.getClass())) {
297+
result = (int) value;
298+
}
299+
}
300+
return result;
301+
}
302+
303+
private boolean getBooleanFieldConfiguration(String fieldName) {
304+
boolean result = NOT_SET;
305+
if (config.containsKey(fieldName)) {
306+
Object value = config.get(fieldName);
307+
if (Boolean.class.isAssignableFrom(value.getClass())) {
308+
result = (boolean) value;
309+
}
310+
}
311+
return result;
312+
}
313+
314+
private void incrementCharacterCount(Map<Character, Integer> charCountMap, Character c) {
315+
int count = 0;
316+
if (charCountMap.containsKey(c)) {
317+
count = charCountMap.get(c);
318+
}
319+
count++;
320+
charCountMap.put(c, count);
321+
}
322+
}

core/src/main/python/create.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
from oracle.weblogic.deploy.create import CreateException
2020
from oracle.weblogic.deploy.deploy import DeployException
21+
from oracle.weblogic.deploy.validate import ValidateException
2122
from oracle.weblogic.deploy.util import FileUtils
2223
from oracle.weblogic.deploy.util import WLSDeployArchiveIOException
2324

@@ -369,7 +370,9 @@ def main(model_context):
369370
validate_crd_sections=False)
370371

371372
# check for any content problems in the merged, substituted model
372-
content_validator = ContentValidator(model_context)
373+
content_validator = ContentValidator(model_context, aliases)
374+
# password validation errors are fatal and will raise a CreateException if any are found.
375+
content_validator.validate_user_passwords(model_dictionary)
373376
content_validator.validate_model(model_dictionary)
374377

375378
archive_helper = None
@@ -382,7 +385,7 @@ def main(model_context):
382385
os.mkdir(os.path.abspath(domain_path))
383386

384387
archive_helper.extract_all_database_wallets()
385-
archive_helper.extract_custom_archive()
388+
archive_helper.extract_custom_directory()
386389

387390
has_atp, has_ssl = validate_rcu_args_and_model(model_context, model_dictionary, archive_helper, aliases)
388391

@@ -405,6 +408,10 @@ def main(model_context):
405408
_exit_code = ExitCode.ERROR
406409
__logger.severe('WLSDPLY-12409', _program_name, ex.getLocalizedMessage(), error=ex,
407410
class_name=_class_name, method_name=_method_name)
411+
except ValidateException, ex:
412+
_exit_code = ExitCode.ERROR
413+
__logger.severe('WLSDPLY-12409', _program_name, ex.getLocalizedMessage(), error=ex,
414+
class_name=_class_name, method_name=_method_name)
408415
except CreateException, ex:
409416
_exit_code = ExitCode.ERROR
410417
__logger.severe('WLSDPLY-12409', _program_name, ex.getLocalizedMessage(), error=ex,

core/src/main/python/update.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ def main(model_context):
302302
archive_helper = ArchiveHelper(archive_file_name, domain_path, __logger, ExceptionType.CREATE)
303303
if archive_helper:
304304
archive_helper.extract_all_database_wallets()
305-
archive_helper.extract_custom_archive()
305+
archive_helper.extract_custom_directory()
306306

307307
_exit_code = __update(model, model_context, aliases)
308308
except DeployException, ex:

core/src/main/python/wlsdeploy/aliases/model_constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
DEFAULT_CREDENTIAL_MAPPER = 'DefaultCredentialMapper'
8686
DEFAULT_DELIVERY_PARAMS = 'DefaultDeliveryParams'
8787
DEFAULT_IDENTITY_ASSERTER = 'DefaultIdentityAsserter'
88+
DEFAULT_REALM = 'DefaultRealm'
8889
DEFAULT_ROLE_MAPPER = 'DefaultRoleMapper'
8990
DEFAULT_WLS_DOMAIN_NAME = 'base_domain'
9091
DELIVERY_FAILURE_PARAMS = 'DeliveryFailureParams'

core/src/main/python/wlsdeploy/exception/exception_helper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
Copyright (c) 2017, 2022, Oracle and/or its affiliates.
2+
Copyright (c) 2017, 2023, Oracle and/or its affiliates.
33
Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
44
"""
55
import sys

core/src/main/python/wlsdeploy/tool/prepare/model_preparer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ def prepare_models(self):
397397
self._apply_final_filters(merged_model_dictionary)
398398

399399
# check for any content problems in the merged, substituted model
400-
content_validator = ContentValidator(self.model_context)
400+
content_validator = ContentValidator(self.model_context, self._aliases)
401401
content_validator.validate_model(merged_model_dictionary)
402402

403403
target_configuration_helper.generate_all_output_files(Model(merged_model_dictionary), self._aliases,

0 commit comments

Comments
 (0)