package org.dcache.auth; import com.google.common.base.Strings; import org.springframework.beans.factory.annotation.Required; import javax.security.auth.Subject; import java.io.File; import java.security.Principal; import java.util.Collections; import java.util.Map; import java.util.Objects; import java.util.Properties; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; import diskCacheV111.namespace.NameSpaceProvider; import diskCacheV111.util.CacheException; import diskCacheV111.util.FsPath; import diskCacheV111.util.PermissionDeniedCacheException; import dmg.cells.nucleus.CellCommandListener; import dmg.cells.nucleus.EnvironmentAware; import dmg.util.Formats; import dmg.util.Replaceable; import org.dcache.auth.attributes.LoginAttribute; import org.dcache.auth.attributes.PrefixRestriction; import org.dcache.auth.attributes.RootDirectory; import org.dcache.gplazma.AuthenticationException; import org.dcache.gplazma.GPlazma; import org.dcache.gplazma.NoSuchPrincipalException; import org.dcache.gplazma.configuration.ConfigurationLoadingStrategy; import org.dcache.gplazma.configuration.FromFileConfigurationLoadingStrategy; import org.dcache.gplazma.loader.DcacheAwarePluginFactory; import org.dcache.gplazma.loader.PluginFactory; import org.dcache.gplazma.monitor.LoginResult; import org.dcache.gplazma.monitor.LoginResultPrinter; import org.dcache.gplazma.monitor.RecordingLoginMonitor; import org.dcache.util.Args; /** * A LoginStrategy that wraps a org.dcache.gplazma.GPlazma * */ public class Gplazma2LoginStrategy implements LoginStrategy, EnvironmentAware, CellCommandListener { private String _configurationFile; private GPlazma _gplazma; private Map<String,Object> _environment = Collections.emptyMap(); private PluginFactory _factory; private Function<FsPath, PrefixRestriction> _createPrefixRestriction; @Required public void setConfigurationFile(String configurationFile) { if ((configurationFile == null) || (configurationFile.length() == 0)) { throw new IllegalArgumentException( "configuration file argument wasn't specified correctly"); } else if (!new File(configurationFile).exists()) { throw new IllegalArgumentException( "configuration file does not exists at " + configurationFile); } _configurationFile = configurationFile; } @Required public void setNameSpace(NameSpaceProvider namespace) { _factory = new DcacheAwarePluginFactory(namespace); } public String getConfigurationFile() { return _configurationFile; } @Override public void setEnvironment(Map<String,Object> environment) { _environment = environment; } public Map<String,Object> getEnvironment() { return _environment; } public Properties getEnvironmentAsProperties() { Replaceable replaceable = name -> Objects.toString(_environment.get(name), null); Properties properties = new Properties(); for (Map.Entry<String,Object> e: _environment.entrySet()) { String key = e.getKey(); String value = String.valueOf(e.getValue()); properties.put(key, Formats.replaceKeywords(value, replaceable)); } return properties; } public void init() { ConfigurationLoadingStrategy configuration = new FromFileConfigurationLoadingStrategy(_configurationFile); _gplazma = new GPlazma(configuration, getEnvironmentAsProperties(), _factory); } public void shutdown() { if (_gplazma != null) { _gplazma.shutdown(); } } private LoginReply convertLoginReply(org.dcache.gplazma.LoginReply gPlazmaLoginReply) { Set<Object> sessionAttributes = gPlazmaLoginReply.getSessionAttributes(); Set<LoginAttribute> loginAttributes = sessionAttributes.stream() .filter(LoginAttribute.class::isInstance) .map(LoginAttribute.class::cast) .collect(Collectors.toSet()); sessionAttributes.stream() .filter(RootDirectory.class::isInstance) .map(RootDirectory.class::cast) .filter(att -> !att.getRoot().equals("/")) .map(att -> FsPath.create(att.getRoot())) .map(_createPrefixRestriction) .forEach(loginAttributes::add); // Filter IGTFStatusPrincipal and IGTFPolicyPrincipal until no longer // need backwards compatibility with dCache v3.0 pools Subject replyUser = new Subject(); Subject loggedInUser = gPlazmaLoginReply.getSubject(); replyUser.getPublicCredentials().addAll(loggedInUser.getPublicCredentials()); replyUser.getPrivateCredentials().addAll(loggedInUser.getPrivateCredentials()); Set<Principal> replyPrincipals = replyUser.getPrincipals(); loggedInUser.getPrincipals().stream() .filter(p -> !(p instanceof IGTFStatusPrincipal)) .filter(p -> !(p instanceof IGTFPolicyPrincipal)) .forEach(replyPrincipals::add); return new LoginReply(replyUser, loginAttributes); } @Override public LoginReply login(Subject subject) throws CacheException { try { return convertLoginReply(_gplazma.login(subject)); } catch (AuthenticationException e) { // We deliberately hide the reason why the login failed from the // rest of dCache. This is to prevent a brute-force attack // discovering whether certain user accounts exist. throw new PermissionDeniedCacheException("login failed"); } } @Override public Principal map(Principal principal) throws CacheException { try { return _gplazma.map(principal); } catch (NoSuchPrincipalException e) { return null; } } @Override public Set<Principal> reverseMap(Principal principal) throws CacheException { try { return _gplazma.reverseMap(principal); } catch (NoSuchPrincipalException e) { return Collections.emptySet(); } } public static final String fh_explain_login = "This command runs a test login with the supplied principals\n" + "The result is tracked and an explanation is provided of how \n" + "the result was obtained.\n\n" + "Examples:\n" + " explain login \"dn:/C=DE/O=GermanGrid/OU=DESY/CN=testUser\" fqan:/test\n" + " explain login user:testuser\n"; public static final String hh_explain_login = "<principal> [<principal> ...] # explain the result of login"; public String ac_explain_login_$_1_99(Args args) { Subject subject = Subjects.subjectFromArgs(args.getArguments()); RecordingLoginMonitor monitor = new RecordingLoginMonitor(); try { _gplazma.login(subject, monitor); } catch (AuthenticationException e) { // ignore exception: we'll show this in the explanation. } LoginResult result = monitor.getResult(); LoginResultPrinter printer = new LoginResultPrinter(result); return printer.print(); } public void setUploadPath(String s) { if (Strings.isNullOrEmpty(s) || !s.startsWith("/")) { _createPrefixRestriction = path -> new PrefixRestriction(path); } else { FsPath uploadPath = FsPath.create(s); _createPrefixRestriction = path -> new PrefixRestriction(path, uploadPath); } } }