Skip to content

Commit

Permalink
Merge pull request #110 from arenadata/feature/ADH-5033-password-enco…
Browse files Browse the repository at this point in the history
…ding

[ADH-5033] Add password encoding support
  • Loading branch information
iamlapa authored Oct 28, 2024
2 parents 6983c3e + 4c3dbe7 commit ae7560d
Show file tree
Hide file tree
Showing 12 changed files with 292 additions and 7 deletions.
20 changes: 20 additions & 0 deletions conf/smart-default.xml
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,26 @@
</description>
</property>

<property>
<name>smart.rest.server.auth.predefined.password.encoder</name>
<value>noop</value>
<description>
Default encoder for predefined users passwords in case if password doesn't have {%encoderId%} prefix.
Possible values:
noop (for plaintext passwords), bcrypt, ldap, md4, md5, pbkdf2, scrypt, sha-1, sha-256.
</description>
</property>

<property>
<name>smart.rest.server.auth.ldap.password.encoder</name>
<value>noop</value>
<description>
Default encoder for LDAP users passwords in case if password doesn't have {%encoderId%} prefix.
Possible values:
noop (for plaintext passwords), bcrypt, ldap, md4, md5, pbkdf2, scrypt, sha-1, sha-256.
</description>
</property>

<property>
<name>smart.rest.server.auth.failures.logging.enabled</name>
<value>true</value>
Expand Down
7 changes: 6 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
<scala.binary.version>2.11</scala.binary.version>
<mockito.version>2.28.2</mockito.version>
<testcontainers.version>1.19.1</testcontainers.version>
<bouncycastle.version>1.46</bouncycastle.version>
<bouncycastle.version>1.68</bouncycastle.version>
<protobuf.version>3.7.1</protobuf.version>
<dbunitVersion>2.5.3</dbunitVersion>
<akka.version>2.5.32</akka.version>
Expand Down Expand Up @@ -192,6 +192,11 @@
<artifactId>tape</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_2.11</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.junit.BeforeClass;
import org.junit.runners.Parameterized.Parameters;
import org.smartdata.conf.SmartConf;
import org.smartdata.server.config.PasswordEncoderFactory.EncoderType;
import org.smartdata.server.config.ldap.search.LdapSearchScope;

import static org.smartdata.integration.auth.TestWebServerAuth.TestParams.ExpectedResult.FAIL;
Expand All @@ -35,6 +36,7 @@
import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_GROUP_MEMBER_ATTR;
import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_GROUP_NAME_ATTR;
import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_GROUP_OBJECT_DEFAULT;
import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_PASSWORD_ENCODER;
import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_SEARCH_ADDITIONAL_FILTER;
import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_SEARCH_BASE;
import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_URL;
Expand Down Expand Up @@ -104,6 +106,12 @@ public static TestParams[] parameters() {
searchWithAdditionalSearch(BIND)),
new TestParams("ben", "bens_password",
searchWithAdditionalSearch(BIND), FAIL),
new TestParams("hashed_bob", "b0bs_p4ssw0rd",
passwordCompareWithPasswordEncoding(EncoderType.NOOP)),
new TestParams("hashed_july", "kitty_cat",
passwordCompareWithPasswordEncoding(EncoderType.NOOP), FAIL),
new TestParams("hashed_july", "kitty_cat",
passwordCompareWithPasswordEncoding(EncoderType.PBKDF2)),
new TestParams("july", "kitty_cat",
searchByCustomSearchSeveralUsers(BIND), FAIL),
new TestParams("july", "kitty_cat",
Expand All @@ -120,6 +128,16 @@ private static SmartConf searchByName(AuthType authType) {
return conf;
}

private static SmartConf passwordCompareWithPasswordEncoding(EncoderType defaultEncoder) {
SmartConf conf = baseConf();
conf.set(SMART_REST_SERVER_LDAP_AUTH_TYPE, PASSWORD_COMPARE.toString());
conf.setEnum(SMART_REST_SERVER_LDAP_USER_SEARCH_SCOPE, LdapSearchScope.SUBTREE);
conf.set(SMART_REST_SERVER_LDAP_PASSWORD_ENCODER, defaultEncoder.getId());

conf.set(TEST_PARAM_NAME_OPTION, "passwordCompareWithPasswordEncoding");
return conf;
}

private static SmartConf searchByCustomSearch(AuthType authType) {
SmartConf conf = baseConf();
conf.set(SMART_REST_SERVER_LDAP_AUTH_TYPE, authType.toString());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.smartdata.integration.auth;

import org.junit.runners.Parameterized.Parameters;
import org.smartdata.conf.SmartConf;

import static org.smartdata.integration.auth.TestWebServerAuth.TestParams.ExpectedResult.FAIL;
import static org.smartdata.server.config.ConfigKeys.PREDEFINED_BASIC_AUTH_ENABLED;
import static org.smartdata.server.config.ConfigKeys.PREDEFINED_USERS;
import static org.smartdata.server.config.ConfigKeys.PREDEFINED_USERS_PASSWORD_ENCODER;

public class TestWebServerPredefinedUsersAuth extends TestWebServerAuth {

@Parameters(name = "{0}")
public static TestParams[] parameters() {
return new TestParams[]{
new TestParams("user1", "pass1", plainTextCredentials()),
new TestParams("user2", "pass2", plainTextCredentials()),
new TestParams("unknown", "pass", plainTextCredentials(), FAIL),
new TestParams("user1", "pass1", bcryptCredentials()),
new TestParams("unknown2", "unknown", bcryptCredentials(), FAIL),
new TestParams("user2", "pass2", pbkdf2Credentials()),
new TestParams("unknown3", "unknown2", pbkdf2Credentials(), FAIL),
new TestParams("user1", "pass1", hashCredentialsWithDefaultEncoder()),
new TestParams("user2", "pass2", hashCredentialsWithDefaultEncoder(), FAIL)
};
}

private static SmartConf plainTextCredentials() {
SmartConf conf = baseConf();
conf.set(PREDEFINED_USERS, "user1:pass1,user2:pass2");

conf.set(TEST_PARAM_NAME_OPTION, "plainTextCredentials");
return conf;
}

private static SmartConf bcryptCredentials() {
SmartConf conf = baseConf();
conf.set(PREDEFINED_USERS,
// user1:pass1
"user1:{bcrypt}$2a$10$.E/VDhdRt85NyN7/kzrE8uCVu6Ey3tbGR/efyOOIgw.e4BeYcb8.G,"
// user2:pass2
+ "user2:{bcrypt}$2a$10$kEZuKAa9sL3C2YlTwSo7meNfvjkZMHGUkf6T/E6.eSO6.OIt.Q9qa");

conf.set(TEST_PARAM_NAME_OPTION, "bcryptCredentials");
return conf;
}

private static SmartConf pbkdf2Credentials() {
SmartConf conf = baseConf();
// user1:pass1
conf.set(PREDEFINED_USERS, "user1:{pbkdf2}"
+ "8d831f4f23dc4f0f43bdabd573372c6ea091f8fb55cb7d0242534d35f7fe14bb932da695a20c3c23,"
// user2:pass2
+ "user2:{pbkdf2}"
+ "b4f2a5e4a827e83fae4346e2ffc806ee61a79ff40aeea3afea7acac2d6651fe17869968c55902618");

conf.set(TEST_PARAM_NAME_OPTION, "pbkdf2Credentials");
return conf;
}

private static SmartConf hashCredentialsWithDefaultEncoder() {
SmartConf conf = baseConf();
// user1:pass1
conf.set(PREDEFINED_USERS, "user1:$e0801$LBsB5RY9rXDw343mFrViv74uFhi6SRqE5R8KirZjIcDd966x"
+ "TJbzrDVgtgnFAX8h+zU+6+raIsPnckqF4D7Smg=="
+ "$l72uGTcby92qvT6vd+gekAjUL/WJSDvujf///s/l+sQ=");
conf.set(PREDEFINED_USERS_PASSWORD_ENCODER, "scrypt");

conf.set(TEST_PARAM_NAME_OPTION, "hashCredentialsWithDefaultEncoder");
return conf;
}

private static SmartConf baseConf() {
SmartConf conf = new SmartConf();
conf.setBoolean(PREDEFINED_BASIC_AUTH_ENABLED, true);
return conf;
}
}
21 changes: 21 additions & 0 deletions smart-integration/src/test/resources/ldap-server.ldif
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,24 @@ cn: Bob Hamilton
sn: Hamilton
uid: bob
userPassword: b0bs_p4ssw0rd

dn: uid=hashed_bob,ou=people,dc=ssm,dc=test
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Bob Hashed
sn: Hashed
uid: hashed_bob
userPassword: {bcrypt}$2a$10$.R5BfExkMoBcgd2TXWgz0efIsL8NvZr3dcFfeuZpTNjvTmUPtQjZ6

dn: uid=hashed_july,ou=people,dc=ssm,dc=test
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: July Hashed
sn: Hashed
uid: hashed_july
userPassword: 6adbc4b34934648df697c5e5d6c422e89ca5e67239621d24961a1404c94c716e52a8d0bf63d9c994
memberOf: cn=developers,ou=groups,dc=ssm,dc=test
5 changes: 5 additions & 0 deletions smart-web-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@
<version>${lombok-mapstruct-binding.version}</version>
</dependency>

<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ public class ConfigKeys {

public static final String PREDEFINED_USERS = "smart.rest.server.auth.predefined.users";

public static final String PREDEFINED_USERS_PASSWORD_ENCODER =
"smart.rest.server.auth.predefined.password.encoder";

public static final String PREDEFINED_USERS_PASSWORD_ENCODER_DEFAULT =
"noop";

public static final String SMART_REST_SERVER_KEYTAB_FILE_KEY =
"smart.rest.server.auth.spnego.keytab";

Expand Down Expand Up @@ -143,4 +149,11 @@ public class ConfigKeys {

public static final String SMART_REST_SERVER_AUTH_ERRORS_LOGGING_ENABLED =
"smart.rest.server.auth.failures.logging.enabled";

public static final String SMART_REST_SERVER_LDAP_PASSWORD_ENCODER =
"smart.rest.server.auth.ldap.password.encoder";

public static final String SMART_REST_SERVER_LDAP_PASSWORD_ENCODER_DEFAULT =
"noop";

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@
package org.smartdata.server.config;

import org.smartdata.conf.SmartConf;
import org.smartdata.server.config.PasswordEncoderFactory.EncoderType;
import org.smartdata.server.config.ldap.search.LdapUserSearchFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.authentication.BindAuthenticator;
import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
Expand All @@ -41,6 +42,8 @@
import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_AUTH_TYPE;
import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_BIND_PASSWORD;
import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_BIND_USER_DN;
import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_PASSWORD_ENCODER;
import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_PASSWORD_ENCODER_DEFAULT;
import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_SEARCH_BASE;
import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_SEARCH_BASE_DEFAULT;
import static org.smartdata.server.config.ConfigKeys.SMART_REST_SERVER_LDAP_URL;
Expand Down Expand Up @@ -113,8 +116,7 @@ public LdapAuthenticator passswordCompareLdapAuthenticator(
PasswordComparisonAuthenticator authenticator = new PasswordComparisonAuthenticator(contextSource);

authenticator.setPasswordAttributeName(passwordAttribute);
// todo support password encoding for LDAP and predefined users auth provider
authenticator.setPasswordEncoder(NoOpPasswordEncoder.getInstance());
authenticator.setPasswordEncoder(ldapPasswordEncoder(conf));
authenticator.setUserSearch(ldapUserSearch);
return authenticator;
}
Expand All @@ -123,4 +125,12 @@ public LdapAuthenticator passswordCompareLdapAuthenticator(
public AuthenticationProvider ldapAuthenticationProvider(LdapAuthenticator authenticator) {
return new LdapAuthenticationProvider(authenticator);
}

private PasswordEncoder ldapPasswordEncoder(SmartConf smartConf) {
String defaultEncoder = smartConf.get(
SMART_REST_SERVER_LDAP_PASSWORD_ENCODER,
SMART_REST_SERVER_LDAP_PASSWORD_ENCODER_DEFAULT);

return PasswordEncoderFactory.build(EncoderType.fromId(defaultEncoder));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.smartdata.server.config;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.LdapShaPasswordEncoder;
import org.springframework.security.crypto.password.Md4PasswordEncoder;
import org.springframework.security.crypto.password.MessageDigestPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;

import java.util.HashMap;
import java.util.Map;

/**
* Enhanced version of Spring's
* {@link PasswordEncoderFactories#createDelegatingPasswordEncoder}.
*/
public interface PasswordEncoderFactory {

@RequiredArgsConstructor
@Getter
enum EncoderType {
NOOP("noop"),
BCRYPT("bcrypt"),
LDAP("ldap"),
MD4("md4"),
MD5("md5"),
PBKDF2("pbkdf2"),
SCRYPT("scrypt"),
SHA_1("sha-1"),
SHA_256("sha-256");

private final String id;

public static EncoderType fromId(String id) {
for (EncoderType encoderType : EncoderType.values()) {
if (encoderType.id.equals(id)) {
return encoderType;
}
}
throw new IllegalArgumentException("Wrong encoder id: " + id);
}
}

@SuppressWarnings("deprecation")
static PasswordEncoder build(EncoderType defaultEncoderType) {
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(EncoderType.BCRYPT.id, new BCryptPasswordEncoder());
encoders.put(EncoderType.LDAP.id, new LdapShaPasswordEncoder());
encoders.put(EncoderType.MD4.id, new Md4PasswordEncoder());
encoders.put(EncoderType.MD5.id, new MessageDigestPasswordEncoder("MD5"));
encoders.put(EncoderType.NOOP.id, NoOpPasswordEncoder.getInstance());
encoders.put(EncoderType.PBKDF2.id, new Pbkdf2PasswordEncoder());
encoders.put(EncoderType.SCRYPT.id, new SCryptPasswordEncoder());
encoders.put(EncoderType.SHA_1.id, new MessageDigestPasswordEncoder("SHA-1"));
encoders.put(EncoderType.SHA_256.id, new MessageDigestPasswordEncoder("SHA-256"));

DelegatingPasswordEncoder passwordEncoder =
new DelegatingPasswordEncoder(defaultEncoderType.id, encoders);
passwordEncoder.setDefaultPasswordEncoderForMatches(encoders.get(defaultEncoderType.id));
return passwordEncoder;
}
}
Loading

0 comments on commit ae7560d

Please sign in to comment.