package grith.jgrith.cred; import grisu.jcommons.configuration.CommonGridProperties; import grisu.jcommons.constants.Constants; import grisu.jcommons.constants.Enums.LoginType; import grisu.jcommons.exceptions.CredentialException; import grisu.model.info.dto.VO; import grith.jgrith.cred.callbacks.AbstractCallback; import grith.jgrith.cred.callbacks.CliCallback; import grith.jgrith.cred.callbacks.NoCallback; import grith.jgrith.cred.details.CredDetail; import grith.jgrith.cred.details.LoginTypeDetail; import grith.jgrith.myProxy.MyProxy_light; import grith.jgrith.utils.CertHelpers; import grith.jgrith.utils.CredentialHelpers; import grith.jgrith.voms.VOManagement.VOManager; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.File; import java.lang.reflect.Field; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.globus.common.CoGProperties; import org.globus.myproxy.InitParams; import org.globus.myproxy.MyProxy; import org.globus.myproxy.MyProxyException; import org.globus.util.Util; import org.ietf.jgss.GSSCredential; import org.ietf.jgss.GSSException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Maps; import com.google.common.collect.Sets; public abstract class AbstractCred extends BaseCred implements Cred { class CredentialInvalid extends TimerTask { @Override public void run() { myLogger.debug("Credential invalid now"); pcs.firePropertyChange("valid", true, false); } } class CredentialMinThreshold extends TimerTask { @Override public void run() { myLogger.debug("Min threshold reached"); pcs.firePropertyChange("belowMinLifetime", false, true); } } class CredentialRenewTask extends TimerTask { @Override public void run() { myLogger.debug("Auto renew task started."); refresh(); } } public enum PROPERTY { LoginType(LoginType.class), Username(String.class), Password( char[].class), MyProxyHost(String.class), MyProxyPort( Integer.class), VO(VO.class), FQAN(String.class), md5sum( String.class), SlcsUrl(String.class), SlcsResponse(String.class), IdP(String.class), CertFile( String.class), KeyFile(String.class), MyProxyPassword( char[].class), MyProxyUsername(String.class), LifetimeInSeconds( Integer.class), LocalPath(String.class), Uploaded(Boolean.class), StorePasswordInMemory( Boolean.class); private Class valueClass; private PROPERTY(Class valueClass) { this.valueClass = valueClass; } public Class getValueClass() { return valueClass; } } static final Logger myLogger = LoggerFactory.getLogger(AbstractCred.class .getName()); public static VOManager DEFAULT_VO_MANAGER = new VOManager(); public static AbstractCred create(AbstractCallback callback) { return loadFromConfig(null, callback); } public static AbstractCred getExistingCredential() { ProxyCred pc = null; try { pc = new ProxyCred(); if ( pc.isValid() ) { return pc; } else { return null; } } catch (CredentialException c) { return null; } } public static AbstractCred getExistingOrCliCredential() { ProxyCred pc = new ProxyCred(); if ( pc.isValid() ) { return pc; } AbstractCred c = AbstractCred.loadFromConfig(null, new CliCallback()); return c; } public static AbstractCred loadFromConfig(Map<PROPERTY, Object> config) { return loadFromConfig(config, null); } public static AbstractCred loadFromConfig(Map<PROPERTY, Object> config, AbstractCallback callback) { if (config == null) { config = Maps.newHashMap(); } myLogger.debug("Loading credential from config map..."); try { LoginType type = null; try { type = (LoginType) config.get(PROPERTY.LoginType); } catch (Exception e) { myLogger.debug("Can't get login type..."); } if ((type == null) && (callback != null)) { CredDetail<String> lt = new LoginTypeDetail(); callback.fill(lt); String input = lt.getValue(); if (StringUtils.isBlank(input)) { throw new CredentialException("No login type specified."); } else if (input.contains("Institution login")) { if (input.contains("using:")) { type = LoginType.SHIBBOLETH_LAST_IDP; } else { type = LoginType.SHIBBOLETH; } } else if (input.equals("Certificate login")) { type = LoginType.X509_CERTIFICATE; } else if (input.equals("MyProxy login")) { type = LoginType.MYPROXY; } } if (type == null) { throw new CredentialException("No credential type specified."); } AbstractCred c = null; switch (type) { case MYPROXY: c = new MyProxyCred(); break; case SHIBBOLETH_LAST_IDP: String idp = CommonGridProperties.getDefault().getLastShibIdp(); config.put(PROPERTY.IdP, idp); case SHIBBOLETH: c = new SLCSCred(); break; case X509_CERTIFICATE: c = new X509Cred(); break; default: throw new CredentialException("Login type " + type.toString() + " not supported."); } if (callback != null) { c.setCallback(callback); } c.init(config); return c; } catch (CredentialException ce) { throw ce; } catch (Exception e) { e.printStackTrace(); throw new CredentialException("Can't create credential: " + e.getLocalizedMessage(), e); } } public static void main(String[] args) throws Exception { SLCSCred x = new SLCSCred(); x.setCallback(new CliCallback()); x.init(); System.out.println(x.getGSSCredential().getName().toString()); x.uploadMyProxy(false); x.saveMyProxy(); MyProxyCred mp = new MyProxyCred(); mp.initFromFile(); System.out.println(mp.getRemainingLifetimeMyProxy()); } private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); private long minTimeBetweenAutoRefreshes = 100; protected boolean isUploaded = false; protected boolean isPopulated = false; protected int proxyLifetimeInSeconds = 864000; protected AbstractCallback credCallback = new NoCallback(); private GSSCredential cachedCredential = null; protected String localPath; private Map<String, AbstractCred> groupCache = Maps.newHashMap(); private Map<String, String> groupPathCache = Maps.newHashMap(); private Map<String, VO> fqans; private int minProxyLifetime = DEFAULT_MIN_LIFETIME_IN_SECONDS; private CredentialInvalid invalidTask = null; private CredentialMinThreshold minThresholdTask = null; private CredentialRenewTask renewTask = null; private final Timer timer = new Timer("CredentialUpdate timer", true); private boolean saveProxyOnCreation = true; private volatile Date lastCredentialAutoRefresh = new Date(); private VOManager vom = null; public AbstractCred() { super(); } public AbstractCred(AbstractCallback callback) { super(); setCallback(callback); } public AbstractCred(AbstractCallback callback, Map<PROPERTY, Object> config) { super(config); init(callback, config); } /** * Constructor to create a credential out of a provided config map. * * All credential properties need to be set, otherwise an error will be * thrown. No Callback is set. * * @param config * the credential properties */ public AbstractCred(Map<PROPERTY, Object> config) { super(config); init(config); } public AbstractCred(String mpUsername, char[] mpPassword, String mpHost, int mpPort) { super(mpUsername, mpPassword, mpHost, mpPort); } public void addPropertyChangeListener(PropertyChangeListener l) { pcs.addPropertyChangeListener(l); } public synchronized void createGSSCredential() { cachedCredential = createGSSCredentialInstance(); lastCredentialAutoRefresh = new Date(); groupCache.clear(); Thread t1 = new Thread() { @Override public void run() { if (isUploaded) { uploadMyProxy(true); } } }; t1.setName("MyProxyUpdateThread"); t1.start(); if (getSaveProxyOnCreation()) { Thread t2 = new Thread() { @Override public void run() { if (StringUtils.isNotBlank(localPath) && ! (AbstractCred.this instanceof GroupCred )) { myLogger.debug("Saving proxy as part of gss cred creation. Path: " + localPath); saveProxy(localPath); } for (String group : groupPathCache.keySet()) { String path = groupPathCache.get(group); saveGroupProxy(group, path); } } }; t2.setName("Proxy save thread"); t2.start(); } Thread t3 = new Thread() { @Override public void run() { for (String group : groupCache.keySet()) { AbstractCred cred = groupCache.get(group); if (cred instanceof GroupCred) { myLogger.debug("not updating myproxy for group " + group); return; } myLogger.debug("updating group cred: " + group); GroupCred gc = (GroupCred) cred; gc.setBaseCred(AbstractCred.this); } } }; t3.setName("MyProxyGroupUpdateThread"); t3.start(); int remaining = -1; try { remaining = cachedCredential.getRemainingLifetime(); } catch (GSSException e) { throw new CredentialException("Can't get remaining lifetime.", e); } if (invalidTask != null) { invalidTask.cancel(); } if (minThresholdTask != null) { minThresholdTask.cancel(); } if (renewTask != null) { renewTask.cancel(); } invalidTask = new CredentialInvalid(); timer.schedule(invalidTask, remaining * 1000); minThresholdTask = new CredentialMinThreshold(); int delay = remaining - getMinimumLifetime(); if (delay < 0) { delay = 0; } if (delay > 0) { timer.schedule(minThresholdTask, delay * 1000); } // try to renew before minThreshold is there renewTask = new CredentialRenewTask(); delay = delay - 20; if (delay > 0) { timer.schedule(renewTask, delay * 1000); } } abstract public GSSCredential createGSSCredentialInstance(); /* * (non-Javadoc) * * @see grith.jgrith.cred.Cred#destroy() */ @Override public void destroy() { if (cachedCredential != null) { new Thread() { @Override public void run() { if (cachedCredential != null) { if (isUploaded) { try { myLogger.debug("Destrying uploaded proxy from host: " + getMyProxyHost()); MyProxy mp = new MyProxy(getMyProxyHost(), getMyProxyPort()); try { mp.destroy(cachedCredential, getMyProxyUsername(), new String( getMyProxyPassword())); } catch (MyProxyException e) { myLogger.error( "Can't destroy myproxy credential.", e); } } catch (Exception e) { myLogger.debug( "Error when trying to destroy myproxy cred.", e); } } try { cachedCredential.dispose(); } catch (GSSException e) { myLogger.debug( "Error when disposing gsscredential.", e); } } } }.start(); } for (AbstractCred cred : groupCache.values()) { try { // for non-vo cred this would result in a loop otherwise if (cred instanceof GroupCred) { cred.destroy(); } } catch (Exception e) { myLogger.debug("Error when disposing group gss credential."); } } for (String path : groupPathCache.values()) { Util.destroy(path); } if (StringUtils.isNotBlank(this.localPath)) { if (new File(localPath).exists()) { myLogger.debug("Deleting proxy file " + localPath); Util.destroy(localPath); } } destroyMyProxy(); } /** * Get a map of all Fqans (and VOs) the user has access to. * * @return the Fqans of the user */ public synchronized Map<String, VO> getAvailableFqans() { if (fqans == null) { fqans = getVOManager().getAllFqans(getGSSCredential()); } return fqans; } public Set<String> getAvailableGroups() { return getAvailableFqans().keySet(); } private AbstractCallback getCallback() { if (credCallback == null) { throw new CredentialException( "No callback configured for credential."); } return credCallback; } private Set<CredDetail> getDetails() { Set<CredDetail> details = Sets.newLinkedHashSet(); for (Field f : this.getClass().getDeclaredFields()) { myLogger.debug("populating field: {}", f); try { Class c = f.get(this).getClass().getSuperclass(); if (CredDetail.class.equals(c)) { CredDetail d = (CredDetail) f.get(this); details.add(d); } } catch (Exception e) { // myLogger.debug("Error when trying to get field: {}, {}", f, // e.getLocalizedMessage()); } } return details; } /* * (non-Javadoc) * * @see grith.jgrith.cred.Cred#getDN() */ @Override public String getDN() { return CertHelpers.getDnInProperFormat(getGSSCredential()); } public String getFqan() { if (this instanceof GroupCred) { return ((GroupCred) this).getGroup(); } else { return Constants.NON_VO_FQAN; } } public AbstractCred getGroupCredential(String fqan) { synchronized (fqan) { if (StringUtils.isBlank(fqan)) { fqan = Constants.NON_VO_FQAN; } AbstractCred temp = groupCache.get(fqan); if (temp == null) { myLogger.debug("creating GroupCred for: " + fqan); if (StringUtils.isBlank(fqan) || Constants.NON_VO_FQAN.equals(fqan)) { AbstractCred groupCred = this; groupCache.put(fqan, groupCred); } else { VO vo = getAvailableFqans().get(fqan); if (vo == null) { throw new CredentialException( "Can't find VO for fqan: " + fqan); } else { GroupCred groupCred = getGroupCredential(vo, fqan); groupCache.put(fqan, groupCred); } } } } return groupCache.get(fqan); } /** * Creates a new, voms-enabled Credential object from an arbitrary VO. * * @param vo * the VO * @param fqan * the fqan * @param upload * whether to upload the voms proxy to myproxy * @return the Credential * @throws CredentialException * if the Credential can't be created (e.g. voms error). */ private GroupCred getGroupCredential(VO vo, String fqan) throws CredentialException { if (VO.NON_VO.equals(vo)) { throw new CredentialException("No valid VO specified."); } try { GroupCred gc = new GroupCred(this, vo, fqan); return gc; } catch (Exception e) { throw new CredentialException("Can't create VOMS proxy: " + e.getLocalizedMessage(), e); } } public String getGroupProxyPath(String group) { return groupPathCache.get(group); } public Set<String> getGroups() { return getAvailableFqans().keySet(); } public GSSCredential getGSSCredential() { try { if ((cachedCredential == null)) { if (!isPopulated) { throw new CredentialException( "Credential not populated (yet)."); } createGSSCredential(); return cachedCredential; } else if ((cachedCredential.getRemainingLifetime() < minProxyLifetime) && isRenewable()) { myLogger.debug( "Credential ({}) below min lifetime ({}). Trying to refresh...", cachedCredential.getRemainingLifetime(), minProxyLifetime); if (!isPopulated) { throw new CredentialException( "Credential not populated (yet)."); } Date now = new Date(); long diff = (now.getTime() - lastCredentialAutoRefresh .getTime()) / 1000; if ((diff > minTimeBetweenAutoRefreshes)) { refresh(); lastCredentialAutoRefresh = new Date(); } else { myLogger.debug( "Not refreshing credential since only {} secs since last refresh.", diff); } return cachedCredential; } else { return cachedCredential; } } catch (GSSException e) { throw new CredentialException( "Could not get remaining lifetime of credential.", e); } } public int getMinimumLifetime() { return this.minProxyLifetime; } protected int getProxyLifetimeInSeconds() { return proxyLifetimeInSeconds; } public String getProxyPath() { if (StringUtils.isNotBlank(this.localPath) && new File(this.localPath).exists()) { return this.localPath; } return null; } public synchronized VOManager getVOManager() { if ( vom == null ) { if ( DEFAULT_VO_MANAGER != null ) { vom = DEFAULT_VO_MANAGER; } else { vom = new VOManager(); } } return vom; } public void setVOManager(VOManager vom) { this.vom = vom; } /* * (non-Javadoc) * * @see grith.jgrith.cred.Cred#getRemainingLifetime() */ @Override public int getRemainingLifetime() { try { return getGSSCredential().getRemainingLifetime(); } catch (GSSException e) { throw new CredentialException("Can't get remaining lifetime.", e); } catch (CredentialException ce) { myLogger.debug("Can't get gsscredential.", ce); return 0; } } public boolean getSaveProxyOnCreation() { return saveProxyOnCreation; } public synchronized void init() { init(new HashMap<PROPERTY, Object>()); } public synchronized void init(AbstractCallback callback) { setCallback(callback); init(); } public synchronized void init(AbstractCallback callback, Map<PROPERTY, Object> config) { setCallback(callback); init(config); } @Override public synchronized void init(Map<PROPERTY, Object> config) { isPopulated = false; isUploaded = false; cachedCredential = null; groupCache.clear(); groupPathCache.clear(); fqans = null; if (StringUtils.isBlank(localPath)) { if (!StringUtils.isBlank(localMPPath)) { localPath = localMPPath.substring(0, localMPPath.length() - BaseCred.DEFAULT_MYPROXY_FILE_EXTENSION.length()); } else { localPath = null; } } if (invalidTask != null) { invalidTask.cancel(); } if (minThresholdTask != null) { minThresholdTask.cancel(); } if (renewTask != null) { renewTask.cancel(); } // just so that doesn't need to be configured for every Cred that // inherits this Object mph = config.get(PROPERTY.MyProxyHost); if ((mph != null) && StringUtils.isNotBlank((String) mph)) { setMyProxyHost((String) mph); } initCred(config); populate(); } protected abstract void initCred(Map<PROPERTY, Object> config); @Override abstract public boolean isRenewable(); public boolean isUploaded() { return isUploaded; } /* * (non-Javadoc) * * @see grith.jgrith.cred.Cred#isValid() */ @Override public boolean isValid() { return (getRemainingLifetime() > 0); } private void populate() { if (!isPopulated) { for (CredDetail d : getDetails()) { if (d.isSet()) { myLogger.debug("detail {} already set", d.getName()); } else { myLogger.debug("detail {} not set, calling callback...", d.getName()); getCallback().fill(d); if (!d.isSet()) { myLogger.debug( "detail {} still not set, callback failed...", d.getName()); throw new CredentialException(d.getName() + " not filled"); } } } createGSSCredential(); isPopulated = true; } return; } @Override public synchronized boolean refresh() { if (!isRenewable()) { return false; } int lt = Integer.MIN_VALUE; if ( cachedCredential != null ) { try { lt = cachedCredential.getRemainingLifetime(); } catch (GSSException e) { e.printStackTrace(); } } createGSSCredential(); int newLt = Integer.MIN_VALUE; try { newLt = cachedCredential.getRemainingLifetime(); } catch (GSSException e) { e.printStackTrace(); } return (newLt > lt); } public void removePropertyChangeListener(PropertyChangeListener l) { pcs.addPropertyChangeListener(l); } public void saveGroupProxy(String group) { saveGroupProxy(group, null); } public void saveGroupProxy(String group, String path) { synchronized (group) { if (StringUtils.isBlank(path)) { String temp = getProxyPath(); if (StringUtils.isBlank(getProxyPath())) { temp = CoGProperties.getDefault().getProxyFile(); } path = temp + group.replaceAll("/", "_"); } if (StringUtils.equals(path, getProxyPath())) { this.localPath = null; } AbstractCred temp = getGroupCredential(group); CredentialHelpers.writeToDisk(temp.getGSSCredential(), new File( path)); groupPathCache.put(group, path); } } @Override public String saveProxy() { return saveProxy(null); } @Override public String saveProxy(String path) { if (( this instanceof GroupCred) && (StringUtils.isBlank(path)) ) { return null; } if (StringUtils.isBlank(path) && StringUtils.isNotBlank(this.localPath)) { path = this.localPath; } else if (StringUtils.isBlank(path)) { path = CoGProperties.getDefault().getProxyFile(); } synchronized (path) { this.localPath = path; File file = new File(localPath); CredentialHelpers.writeToDisk(getGSSCredential(), file); if (isUploaded() || this instanceof MyProxyCred) { saveMyProxy(path); } else { FileUtils.deleteQuietly(new File(path + DEFAULT_MYPROXY_FILE_EXTENSION)); } } return localPath; } public void setCallback(AbstractCallback callback) { this.credCallback = callback; } @Override public void setMinimumLifetime(int m) { this.minProxyLifetime = m; if (minThresholdTask != null) { minThresholdTask.cancel(); } if (renewTask != null) { renewTask.cancel(); } minThresholdTask = new CredentialMinThreshold(); int delay = getRemainingLifetime() - this.minProxyLifetime; if (delay < 0) { delay = 0; } timer.schedule(minThresholdTask, delay * 1000); // try to renew before minThreshold is there renewTask = new CredentialRenewTask(); delay = delay - 20; if (delay > 0) { timer.schedule(renewTask, delay * 1000); } } @Override public void setMyProxyHost(String mph) { if (!StringUtils.equals(getMyProxyHost(), mph)) { super.setMyProxyHost(mph); isUploaded = false; } } @Override public void setMyProxyPassword(char[] pw) { if (!Arrays.equals(getMyProxyPassword(), pw)) { super.setMyProxyPassword(pw); isUploaded = false; } } @Override public void setMyProxyPort(int port) { if (port != getMyProxyPort()) { super.setMyProxyPort(port); isUploaded = false; } } @Override public void setMyProxyUsername(String username) { String tmpHost = extractMyProxyServerFromUsername(username); String tmpUsername = null; if (StringUtils.isNotBlank(tmpHost)) { tmpUsername = extractUsernameFromUsername(username); setMyProxyHost(tmpHost); setMyProxyUsername(tmpUsername); } if (!StringUtils.equals(getMyProxyUsername(), username)) { super.setMyProxyUsername(username); isUploaded = false; } } public void setProxyLifetimeInSeconds(int p) { this.proxyLifetimeInSeconds = p; } public void setSaveDetails(boolean save) { for (CredDetail d : getDetails()) { d.setSaveToPropertiesFile(save); } } public void setSaveProxyOnCreation(boolean save) { this.saveProxyOnCreation = save; } @Override public void uploadMyProxy() { uploadMyProxy(false); } /* * (non-Javadoc) * * @see grith.jgrith.cred.Cred#uploadMyProxy(boolean) */ public synchronized void uploadMyProxy(boolean force) { if (!isPopulated) { init(); } if ((cachedCredential == null)) { throw new CredentialException("Credential not populated (yet)."); } myLogger.debug("Credential " + super.getMyProxyUsername() + ": uploading to my proxy host '" + super.getMyProxyHost() + "'..."); if (force) { isUploaded = false; } // try { // if (cachedCredential.getRemainingLifetime() < minProxyLifetime) { // createGSSCredential(); // groupCache.clear(); // groupPathCache.clear(); // isUploaded = false; // } // } catch (GSSException e1) { // throw new CredentialException( // "Can't get lifetime when trying to upload proxy.", e1); // } if (isUploaded == true) { myLogger.debug("Credential " + super.getMyProxyUsername() + ": already uploaded and no force."); return; } MyProxy mp = MyProxy_light.getMyProxy(super.getMyProxyHost(), super.getMyProxyPort()); InitParams params = null; try { params = MyProxy_light.prepareProxyParameters(getMyProxyUsername(), null, null, null, null, cachedCredential.getRemainingLifetime()); } catch (MyProxyException e) { throw new CredentialException("Can't prepare myproxy parameters", e); } catch (GSSException e) { throw new CredentialException("Can't set proxy lifetime", e); } try { MyProxy_light.init(mp, cachedCredential, params, getMyProxyPassword()); isUploaded = true; // invalidateCachedCredential(); } catch (Exception e) { throw new CredentialException("Can't upload MyProxy: " + e.getLocalizedMessage(), e); } if (StringUtils.isNotBlank(this.localPath)) { myLogger.debug( "Saving myproxy credential as part of myproxy upload. Path: {}", this.localPath); saveProxy(); saveMyProxy(); } } }