/* dCache - http://www.dcache.org/
*
* Copyright (C) 2014 Deutsches Elektronen-Synchrotron
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.dcache.gridsite;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import org.dcache.delegation.gridsite2.DelegationException;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.dcache.gridsite.Utilities.assertThat;
/**
* An in-memory storage of in-progress delegations. This implementation
* has no persistent backing store so restarting the JVM will loose any
* on-going delegation requests.
*
* To prevent partially delegated credentials where the client has vanished
* from consuming memory indefinitely, delegation requests are expunged after
* a configurable period.
*
* To prevent a denial-of-service attack from triggering an OOM, there is a
* maximum number of concurrent on-going delegations. While this doesn't
* stop a client (or many clients) from denying a client from delegating, it
* does allow the system to survive such attacks.
*
* The class is thread-safe.
*/
public class InMemoryCredentialDelegationStore implements
CredentialDelegationStore
{
private static final Logger LOG =
LoggerFactory.getLogger(InMemoryCredentialDelegationStore.class);
private final RemovalListener<DelegationIdentity,CredentialDelegation>
LOG_REMOVALS = notification -> {
DelegationIdentity identity = notification.getKey();
switch(notification.getCause()) {
case EXPIRED:
LOG.debug("removing delegation from {}: client took" +
" too long to reply", identity.getDn());
break;
case SIZE:
LOG.debug("removing delegation from {}: too many" +
" on-going delegations", identity.getDn());
break;
}
};
private Cache<DelegationIdentity,CredentialDelegation> _storage;
private long _expireAfter;
private long _maxOngoing;
@Required
public void setExpireAfter(long expire)
{
_expireAfter = expire;
}
public long getExpireAfter()
{
return _expireAfter;
}
@Required
public void setMaxOngoing(long value)
{
_maxOngoing = value;
}
public long getMaxOngoing()
{
return _maxOngoing;
}
public void start()
{
_storage = CacheBuilder.newBuilder().
maximumSize(_maxOngoing).
expireAfterWrite(_expireAfter, MILLISECONDS).
removalListener(LOG_REMOVALS).
build();
}
@Override
public synchronized CredentialDelegation get(DelegationIdentity id)
throws DelegationException
{
CredentialDelegation delegation = _storage.getIfPresent(id);
assertThat(delegation != null, "no on-going delegation", id);
return delegation;
}
@Override
public synchronized void add(CredentialDelegation delegation)
throws DelegationException
{
DelegationIdentity id = delegation.getId();
assertThat(_storage.getIfPresent(id) == null,
"already on-going delegation", id);
_storage.put(id, delegation);
}
@Override
public synchronized CredentialDelegation remove(DelegationIdentity id)
throws DelegationException
{
CredentialDelegation delegation = _storage.getIfPresent(id);
assertThat(delegation != null, "no on-going delegation", id);
_storage.invalidate(id);
return delegation;
}
@Override
public synchronized void removeIfPresent(DelegationIdentity id)
{
_storage.invalidate(id);
}
@Override
public synchronized boolean has(DelegationIdentity id)
{
return _storage.getIfPresent(id) != null;
}
}