package org.dcache.auth;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.security.auth.Subject;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import diskCacheV111.util.CacheException;
import diskCacheV111.util.PermissionDeniedCacheException;
import org.dcache.auth.attributes.Restrictions;
/**
* LoginStrategy which forms the union of allowed logins of several
* access strategies. Login will be granted by the first of a list of
* LoginStrategies which grants login. If no LoginStrategy grants
* login then the behaviour depends on whether the user supplied any credentials.
* If no credentials were presented then the login attempt is treated as
* anonymous and the correct level of access is granted. If credentials were
* presented then fallback to anonymous only if that is allowed.
*/
public class UnionLoginStrategy implements LoginStrategy
{
private static final Logger _log =
LoggerFactory.getLogger(UnionLoginStrategy.class);
/**
* Describes various levels of access.
*/
public enum AccessLevel
{
NONE, READONLY, FULL
}
private List<LoginStrategy> _loginStrategies = Collections.emptyList();
private AccessLevel _anonymousAccess = AccessLevel.NONE;
private boolean _shouldFallback = true;
public void setLoginStrategies(List<LoginStrategy> list)
{
_loginStrategies = new ArrayList<>(list);
}
public List<LoginStrategy> getLoginStrategies()
{
return Collections.unmodifiableList(_loginStrategies);
}
public void setAnonymousAccess(AccessLevel level)
{
_log.debug( "Setting anonymous access to {}", level);
_anonymousAccess = level;
}
public AccessLevel getAnonymousAccess()
{
return _anonymousAccess;
}
public void setFallbackToAnonymous(boolean fallback)
{
_shouldFallback = fallback;
}
public boolean hasFallbackToAnonymous()
{
return _shouldFallback;
}
@Override
public LoginReply login(Subject subject) throws CacheException
{
Optional<Principal> origin = subject.getPrincipals().stream()
.filter(Origin.class::isInstance)
.findFirst();
boolean areCredentialsSupplied = !subject.getPrivateCredentials().isEmpty()
|| !subject.getPublicCredentials().isEmpty()
|| !subject.getPrincipals().stream().allMatch(Origin.class::isInstance);
for (LoginStrategy strategy: _loginStrategies) {
_log.debug( "Attempting login strategy: {}", strategy.getClass().getName());
try {
LoginReply login = strategy.login(subject);
_log.debug( "Login strategy returned {}", login.getSubject());
if (!Subjects.isNobody(login.getSubject())) {
return login;
}
} catch (IllegalArgumentException e) {
/* LoginStrategies throw IllegalArgumentException when
* provided with a Subject they cannot handle.
*/
_log.debug("Login failed with IllegalArgumentException for {}: {}", subject,
e.getMessage());
} catch (PermissionDeniedCacheException e) {
/* As we form the union of all allowed logins of all
* strategies, we ignore the failure and try the next
* strategy.
*/
_log.debug("Permission denied for {}: {}", subject,
e.getMessage());
}
}
if (areCredentialsSupplied && !_shouldFallback) {
throw new PermissionDeniedCacheException("Access denied");
}
_log.debug( "Strategies failed, trying for anonymous access");
LoginReply reply = new LoginReply();
switch (_anonymousAccess) {
case READONLY:
_log.debug( "Allowing read-only access as an anonymous user");
reply.getLoginAttributes().add(Restrictions.readOnly());
if (origin.isPresent()) {
reply.getSubject().getPrincipals().add(origin.get());
}
break;
case FULL:
_log.debug( "Allowing full access as an anonymous user");
if (origin.isPresent()) {
reply.getSubject().getPrincipals().add(origin.get());
}
break;
default:
_log.debug( "Login failed");
throw new PermissionDeniedCacheException("Access denied");
}
return reply;
}
@Override
public Principal map(Principal principal) throws CacheException
{
for (LoginStrategy strategy: _loginStrategies) {
Principal result = strategy.map(principal);
if (result != null) {
return result;
}
}
return null;
}
@Override
public Set<Principal> reverseMap(Principal principal) throws CacheException
{
Set<Principal> result = new HashSet<>();
for (LoginStrategy strategy: _loginStrategies) {
result.addAll(strategy.reverseMap(principal));
}
return result;
}
}