diff --git a/build.gradle b/build.gradle index 9391b26017..a22a044ba0 100644 --- a/build.gradle +++ b/build.gradle @@ -506,6 +506,9 @@ dependencies { implementation 'com.flipkart.zjsonpatch:zjsonpatch:0.4.14' implementation 'org.apache.commons:commons-collections4:4.4' + //Password generation + implementation 'org.passay:passay:1.6.3' + //JSON path implementation 'com.jayway.jsonpath:json-path:2.8.0' implementation 'net.minidev:json-smart:2.4.11' diff --git a/src/main/java/org/opensearch/security/user/UserService.java b/src/main/java/org/opensearch/security/user/UserService.java index 0653948a38..bf2e3e0273 100644 --- a/src/main/java/org/opensearch/security/user/UserService.java +++ b/src/main/java/org/opensearch/security/user/UserService.java @@ -13,10 +13,12 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.Base64; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.Random; import java.util.stream.Collectors; import com.fasterxml.jackson.core.JsonProcessingException; @@ -31,6 +33,7 @@ import org.opensearch.action.support.WriteRequest; import org.opensearch.client.Client; import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.Randomness; import org.opensearch.common.inject.Inject; import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentHelper; @@ -43,6 +46,9 @@ import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.support.SecurityJsonNode; +import org.passay.CharacterRule; +import org.passay.EnglishCharacterData; +import org.passay.PasswordGenerator; import static org.opensearch.security.dlic.rest.support.Utils.hash; @@ -204,13 +210,28 @@ private void verifyServiceAccount(SecurityJsonNode securityJsonNode, String acco } /** - * This will be swapped in for a real solution once one is decided on. + * Use Passay to generate an 8 - 16 character password with 1+ lowercase, 1+ uppercase, 1+ digit, 1+ special character * * @return A password for a service account. */ - private String generatePassword() { - String generatedPassword = "superSecurePassword"; - return generatedPassword; + public static String generatePassword() { + + CharacterRule lowercaseCharacterRule = new CharacterRule(EnglishCharacterData.LowerCase, 1); + CharacterRule uppercaseCharacterRule = new CharacterRule(EnglishCharacterData.UpperCase, 1); + CharacterRule numericCharacterRule = new CharacterRule(EnglishCharacterData.Digit, 1); + CharacterRule specialCharacterRule = new CharacterRule(EnglishCharacterData.Special, 1); + + List rules = Arrays.asList( + lowercaseCharacterRule, + uppercaseCharacterRule, + numericCharacterRule, + specialCharacterRule + ); + PasswordGenerator passwordGenerator = new PasswordGenerator(); + + Random random = Randomness.get(); + + return passwordGenerator.generatePassword(random.nextInt(8) + 8, rules); } /** diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java index 707dbe614e..659074f216 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java @@ -29,10 +29,17 @@ import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.test.helper.file.FileHelper; import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; +import org.opensearch.security.user.UserService; +import org.passay.CharacterCharacteristicsRule; +import org.passay.CharacterRule; +import org.passay.EnglishCharacterData; +import org.passay.LengthRule; +import org.passay.PasswordData; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertNotEquals; import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX; import static org.opensearch.security.dlic.rest.api.InternalUsersApiAction.RESTRICTED_FROM_USERNAME; import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED; @@ -1004,4 +1011,30 @@ public void checkNullElementsInArray() throws Exception { Assert.assertEquals(RequestContentValidator.ValidationError.NULL_ARRAY_ELEMENT.message(), settings.get("reason")); } + @Test + public void testGeneratedPasswordContents() { + String password = UserService.generatePassword(); + PasswordData data = new PasswordData(password); + + LengthRule lengthRule = new LengthRule(8, 16); + + CharacterCharacteristicsRule characteristicsRule = new CharacterCharacteristicsRule(); + + // Define M (3 in this case) + characteristicsRule.setNumberOfCharacteristics(3); + + // Define elements of N (upper, lower, digit, symbol) + characteristicsRule.getRules().add(new CharacterRule(EnglishCharacterData.UpperCase, 1)); + characteristicsRule.getRules().add(new CharacterRule(EnglishCharacterData.LowerCase, 1)); + characteristicsRule.getRules().add(new CharacterRule(EnglishCharacterData.Digit, 1)); + characteristicsRule.getRules().add(new CharacterRule(EnglishCharacterData.Special, 1)); + + org.passay.PasswordValidator validator = new org.passay.PasswordValidator(lengthRule, characteristicsRule); + validator.validate(data); + + String password2 = UserService.generatePassword(); + PasswordData data2 = new PasswordData(password2); + assertNotEquals(password, password2); + assertNotEquals(data, data2); + } }