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; } }