diff --git a/src/main/java/org/elasticsearch/shield/User.java b/src/main/java/org/elasticsearch/shield/User.java index ea5749adcaf..49329562747 100644 --- a/src/main/java/org/elasticsearch/shield/User.java +++ b/src/main/java/org/elasticsearch/shield/User.java @@ -7,6 +7,8 @@ package org.elasticsearch.shield; import org.elasticsearch.shield.authz.SystemRole; +import java.util.Arrays; + /** * An authenticated user */ @@ -48,6 +50,26 @@ public abstract class User { public String[] roles() { return roles; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Simple simple = (Simple) o; + + if (!Arrays.equals(roles, simple.roles)) return false; + if (!username.equals(simple.username)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = username.hashCode(); + result = 31 * result + Arrays.hashCode(roles); + return result; + } } private static class System extends User { @@ -67,6 +89,7 @@ public abstract class User { public String[] roles() { return ROLES; } + } } diff --git a/src/main/java/org/elasticsearch/shield/authc/esusers/ESUsersModule.java b/src/main/java/org/elasticsearch/shield/authc/esusers/ESUsersModule.java index bfa2316ee66..3a3a58bc495 100644 --- a/src/main/java/org/elasticsearch/shield/authc/esusers/ESUsersModule.java +++ b/src/main/java/org/elasticsearch/shield/authc/esusers/ESUsersModule.java @@ -29,7 +29,7 @@ public class ESUsersModule extends AbstractShieldModule.Node { @Override protected void configureNode() { if (enabled) { - bind(Realm.class).annotatedWith(named(ESUsersRealm.TYPE)).to(ESUsersRealm.class).asEagerSingleton(); + bind(ESUsersRealm.class).asEagerSingleton(); bind(UserPasswdStore.class).annotatedWith(named("file")).to(FileUserPasswdStore.class).asEagerSingleton(); bind(UserRolesStore.class).annotatedWith(named("file")).to(FileUserRolesStore.class).asEagerSingleton(); } else { diff --git a/src/main/java/org/elasticsearch/shield/authc/esusers/ESUsersRealm.java b/src/main/java/org/elasticsearch/shield/authc/esusers/ESUsersRealm.java index 151db907706..a1fd481cf59 100644 --- a/src/main/java/org/elasticsearch/shield/authc/esusers/ESUsersRealm.java +++ b/src/main/java/org/elasticsearch/shield/authc/esusers/ESUsersRealm.java @@ -6,7 +6,6 @@ package org.elasticsearch.shield.authc.esusers; import org.elasticsearch.common.Strings; -import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.name.Named; import org.elasticsearch.common.settings.Settings; @@ -14,7 +13,7 @@ import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestRequest; import org.elasticsearch.shield.User; import org.elasticsearch.shield.authc.AuthenticationToken; -import org.elasticsearch.shield.authc.Realm; +import org.elasticsearch.shield.authc.support.CachingUsernamePasswordRealm; import org.elasticsearch.shield.authc.support.UserPasswdStore; import org.elasticsearch.shield.authc.support.UserRolesStore; import org.elasticsearch.shield.authc.support.UsernamePasswordToken; @@ -23,7 +22,7 @@ import org.elasticsearch.transport.TransportMessage; /** * */ -public class ESUsersRealm extends AbstractComponent implements Realm { +public class ESUsersRealm extends CachingUsernamePasswordRealm { public static final String TYPE = "esusers"; @@ -60,7 +59,7 @@ public class ESUsersRealm extends AbstractComponent implements Realm cache; + private final Hasher hasher; + protected CachingUsernamePasswordRealm(Settings settings) { super(settings); + hasher = Hasher.resolve(componentSettings.get("cache.hash_algo", null), Hasher.SHA2); TimeValue ttl = componentSettings.getAsTime(CACHE_TTL, DEFAULT_TTL); if (ttl.millis() > 0) { cache = CacheBuilder.newBuilder() @@ -91,7 +94,7 @@ public abstract class CachingUsernamePasswordRealm extends AbstractComponent imp if (user == null) { throw new AuthenticationException("Could not authenticate ['" + token.principal() + "]"); } - return new UserWithHash(user, token.credentials()); + return new UserWithHash(user, token.credentials(), hasher); } }; @@ -116,13 +119,15 @@ public abstract class CachingUsernamePasswordRealm extends AbstractComponent imp public static class UserWithHash { User user; char[] hash; - public UserWithHash(User user, SecuredString password){ + Hasher hasher; + public UserWithHash(User user, SecuredString password, Hasher hasher){ this.user = user; - this.hash = Hasher.HTPASSWD.hash(password); + this.hash = hasher.hash(password); + this.hasher = hasher; } public boolean verify(SecuredString password){ - return Hasher.HTPASSWD.verify(password, hash); + return hasher.verify(password, hash); } } } diff --git a/src/main/java/org/elasticsearch/shield/authc/support/Hasher.java b/src/main/java/org/elasticsearch/shield/authc/support/Hasher.java index 19b112cda18..f1dfbda4852 100644 --- a/src/main/java/org/elasticsearch/shield/authc/support/Hasher.java +++ b/src/main/java/org/elasticsearch/shield/authc/support/Hasher.java @@ -12,6 +12,7 @@ import org.apache.commons.codec.digest.Md5Crypt; import org.apache.commons.codec.digest.Sha2Crypt; import org.elasticsearch.ElasticsearchIllegalArgumentException; import org.elasticsearch.common.os.OsUtils; +import org.elasticsearch.shield.ShieldSettingsException; import java.util.Locale; @@ -147,6 +148,10 @@ public enum Hasher { } switch (name.toLowerCase(Locale.ROOT)) { case "htpasswd" : return HTPASSWD; + case "bcrypt" : return BCRYPT; + case "sha1" : return SHA1; + case "sha2" : return SHA2; + case "md5" : return MD5; default: return defaultHasher; } @@ -155,7 +160,7 @@ public enum Hasher { public static Hasher resolve(String name) { Hasher hasher = resolve(name, null); if (hasher == null) { - throw new ElasticsearchIllegalArgumentException("Unknown hash function [" + name + "]"); + throw new ShieldSettingsException("Unknown hash function [" + name + "]"); } return hasher; } diff --git a/src/test/java/org/elasticsearch/shield/authc/esusers/ESUsersRealmTests.java b/src/test/java/org/elasticsearch/shield/authc/esusers/ESUsersRealmTests.java index 30caa896bd3..263bca1f495 100644 --- a/src/test/java/org/elasticsearch/shield/authc/esusers/ESUsersRealmTests.java +++ b/src/test/java/org/elasticsearch/shield/authc/esusers/ESUsersRealmTests.java @@ -5,6 +5,7 @@ */ package org.elasticsearch.shield.authc.esusers; +import com.carrotsearch.randomizedtesting.annotations.Repeat; import org.elasticsearch.action.Action; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionRequest; @@ -28,9 +29,9 @@ import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Test; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.Matchers.arrayContaining; +import java.util.Locale; + +import static org.hamcrest.Matchers.*; import static org.mockito.Mockito.*; /** @@ -70,6 +71,19 @@ public class ESUsersRealmTests extends ElasticsearchTestCase { assertThat(user.roles(), arrayContaining("role1", "role2")); } + @Test @Repeat(iterations = 20) + public void testAuthenticate_Caching() throws Exception { + Settings settings = ImmutableSettings.builder() + .put("shield.authc.esusers.cache.hash_algo", Hasher.values()[randomIntBetween(0, Hasher.values().length - 1)].name().toLowerCase(Locale.ROOT)) + .build(); + MockUserPasswdStore userPasswdStore = new MockUserPasswdStore("user1", "test123"); + MockUserRolesStore userRolesStore = new MockUserRolesStore("user1", "role1", "role2"); + ESUsersRealm realm = new ESUsersRealm(settings, userPasswdStore, userRolesStore, restController); + User user1 = realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123"))); + User user2 = realm.authenticate(new UsernamePasswordToken("user1", SecuredStringTests.build("test123"))); + assertThat(user1, sameInstance(user2)); + } + @Test public void testToken() throws Exception { Settings settings = ImmutableSettings.builder().build();