From 09bdfc3da8728f23442ecc091494a603452a4d1e Mon Sep 17 00:00:00 2001 From: uboness Date: Wed, 6 Aug 2014 03:23:10 +0200 Subject: [PATCH] Added support class for caching username/password realm Original commit: elastic/x-pack-elasticsearch@493234a0a56ae37ffe13dad01580253727896692 --- .../support/CachingUsernamePasswordRealm.java | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 src/main/java/org/elasticsearch/shield/authc/support/CachingUsernamePasswordRealm.java diff --git a/src/main/java/org/elasticsearch/shield/authc/support/CachingUsernamePasswordRealm.java b/src/main/java/org/elasticsearch/shield/authc/support/CachingUsernamePasswordRealm.java new file mode 100644 index 00000000000..b981e4ec524 --- /dev/null +++ b/src/main/java/org/elasticsearch/shield/authc/support/CachingUsernamePasswordRealm.java @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.shield.authc.support; + +import org.elasticsearch.common.cache.Cache; +import org.elasticsearch.common.cache.CacheBuilder; +import org.elasticsearch.common.component.AbstractComponent; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.TimeValue; +import org.elasticsearch.shield.User; +import org.elasticsearch.shield.authc.AuthenticationException; +import org.elasticsearch.shield.authc.Realm; +import org.elasticsearch.transport.TransportRequest; + +import java.util.Arrays; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +public abstract class CachingUsernamePasswordRealm extends AbstractComponent implements Realm { + + private static final TimeValue DEFAULT_TTL = TimeValue.timeValueHours(1); + + private final Cache cache; + + protected CachingUsernamePasswordRealm(Settings settings) { + super(settings); + TimeValue ttl = componentSettings.getAsTime("cache.ttl", DEFAULT_TTL); + if (ttl.millis() > 0) { + cache = CacheBuilder.newBuilder() + .expireAfterWrite(ttl.getMillis(), TimeUnit.MILLISECONDS) + .maximumSize(settings.getAsInt("cache.max_users", -1)) + .build(); + } else { + cache = null; + } + } + + @Override + public UsernamePasswordToken token(TransportRequest request) { + return UsernamePasswordToken.extractToken(request, null); + } + + protected final void expire(String username) { + if (cache != null) { + cache.invalidate(username); + } + } + + protected final void expireAll() { + if (cache != null) { + cache.invalidateAll(); + } + } + + @Override + public User authenticate(final UsernamePasswordToken token) { + if (cache == null) { + return doAuthenticate(token); + } + + try { + return cache.get(new CacheKey(token), new Callable() { + @Override + public User call() throws Exception { + return doAuthenticate(token); + } + }); + } catch (ExecutionException ee) { + throw new AuthenticationException("Could not authenticate ['" + token.principal() + "]", ee); + } + } + + protected abstract User doAuthenticate(UsernamePasswordToken token); + + static class CacheKey { + + private final String username; + private final char[] passwdHash; + + CacheKey(UsernamePasswordToken token) { + this.username = token.principal(); + this.passwdHash = Hasher.HTPASSWD.hash(token.credentials()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + CacheKey cacheKey = (CacheKey) o; + + if (!Arrays.equals(passwdHash, cacheKey.passwdHash)) return false; + if (!username.equals(cacheKey.username)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = username.hashCode(); + result = 31 * result + Arrays.hashCode(passwdHash); + return result; + } + } +}