/* * JBoss, Home of Professional Open Source. * * See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing. * * See the AUTHORS.txt file distributed with this work for a full listing of individual contributors. */ package org.teiid.designer.runtime; import static org.teiid.designer.runtime.DqpPlugin.PLUGIN_ID; import static org.teiid.designer.runtime.DqpPlugin.Util; import java.net.MalformedURLException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.teiid.core.designer.HashCodeUtil; import org.teiid.core.designer.util.StringUtilities; import org.teiid.datatools.connectivity.ConnectivityUtil; import org.teiid.datatools.connectivity.spi.ISecureStorageProvider; import org.teiid.designer.runtime.spi.ITeiidConnectionInfo; /** * * * @since 8.0 */ public abstract class TeiidConnectionInfo implements ITeiidConnectionInfo { protected static final int DEFAULT_PORT_NUMBER = 0; private ISecureStorageProvider secureStorageProvider; /* * A hash of the url and password. Generated when the password is initially set * then provides a unique reference to the password in the secure storage */ private String passToken; private String passwordStorageKey; protected String url; private String host; private String port; private int portNumber = DEFAULT_PORT_NUMBER; private boolean secure; private String username; boolean settingAllInfo = false; /** * @param port the connection port (can be <code>null</code> or empty) * @param username the connection user name (can be <code>null</code> or empty) * @param secureStorageProvider provider for storage of the password * @param password the connection password (can be <code>null</code> or empty) * @param secure <code>true</code> if a secure connection should be used * @see #validate() */ protected TeiidConnectionInfo( String host, String port, String username, ISecureStorageProvider secureStorageProvider, String password, boolean secure) { this.host = host; this.port = port; try { this.portNumber = Integer.parseInt(port); } catch (NumberFormatException ex) { this.portNumber = DEFAULT_PORT_NUMBER; } this.username = username; this.secureStorageProvider = secureStorageProvider; this.secure = secure; } /** * Used for initialising the password from constructors. Must be called by any * sub classes that add extra information to their urls since the password and * generated passToken are dependent on the url. * * Should password be null then no need to secure or set it. * * @param password */ protected void initPassword(String password) { if (password == null) return; setPassword(password); } /** * Key that the password is stored against under secure storage * * @return */ protected abstract String getPasswordKey(); /** * {@inheritDoc} * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals( Object object ) { if (this == object) { return true; } if (object == null) { return false; } if (getClass() != object.getClass()) { return false; } TeiidConnectionInfo thatInfo = (TeiidConnectionInfo) object; // host String thisHost = getHost(); String thatHost = thatInfo.getHost(); if (thisHost == null) { if (thatHost != null) { return false; } } else { if (thatHost == null) { return false; } if (!thisHost.equals(thatHost)) { return false; } } // port if (getPort() == null) { if (thatInfo.getPort() != null) { return false; } } else { if (thatInfo.getPort() == null) { return false; } if (!getPort().equals(thatInfo.getPort())) { return false; } } // secure if (isSecure() != thatInfo.isSecure()) { return false; } // user if (getUsername() == null) { if (thatInfo.getUsername() != null) { return false; } } else { if (thatInfo.getUsername() == null) { return false; } if (!getUsername().equals(thatInfo.getUsername())) { return false; } } // password if (getPassword() == null) { if (thatInfo.getPassword() != null) { return false; } } else { if (thatInfo.getPassword() == null) { return false; } if (!getPassword().equals(thatInfo.getPassword())) { return false; } } return true; } @Override public ISecureStorageProvider getSecureStorageProvider() { return this.secureStorageProvider; } /** * Get the key to be used for storing properties against for this connection. * * @return provider key used as reference to secure storage * @throws Exception */ private void generateProviderKey() throws Exception { if( this.passwordStorageKey == null ) { if (passToken == null) { throw new Exception("password token is NULL"); } else { this.passwordStorageKey = ConnectivityUtil.buildSecureStorageKey(getClass(), getUrl(), passToken); } } } private boolean passwordExists() { try { boolean exists = secureStorageProvider.existsInSecureStorage(this.passwordStorageKey, getPasswordKey()); return exists; } catch (Exception ex) { DqpPlugin.Util.log(ex); return false; } } private String retrievePassword() { if( this.passwordStorageKey == null ) return null; try { return secureStorageProvider.getFromSecureStorage(this.passwordStorageKey, getPasswordKey()); } catch (Exception ex) { DqpPlugin.Util.log(ex); return null; } } private void generatePasswordToken(String password) { try { this.passToken = ConnectivityUtil.generateHashToken(getUrl(), password); } catch (Exception e) { DqpPlugin.Util.log(e); } } private void storePassword(String password) { boolean restoring = false; try { if (passToken == null ) { generateUrl(); if( ConnectivityUtil.isPasswordToken(password)) { passToken = password; restoring = true; } else { generatePasswordToken(password); } generateProviderKey(); } if( !restoring && !passwordExists() ) { secureStorageProvider.storeInSecureStorage(this.passwordStorageKey, getPasswordKey(), password); } } catch (Exception e) { DqpPlugin.Util.log(e); } finally { restoring = false; } } private void resetPassword() { if( settingAllInfo ) return; // If HOST or PORT changes, this method needs to be called to do the following // 1) get the current password from storage with current passwordStorageKey String currentPassword = retrievePassword(); // 2) remove the node from storage try { if( this.passwordStorageKey != null ) { // if passToken != null, need to remove old stored password so new one can be stored instead? secureStorageProvider.removeFromSecureStorage(passwordStorageKey); } } catch (Exception e) { DqpPlugin.Util.log(e); } // 3) regenerate passToken // 4) regenerate passwordStorageKey // 5) restore password if( currentPassword != null ) { setPassword(currentPassword); } } /** * @return the password (can be <code>null</code> or empty) */ @Override public String getPassword() { String password = retrievePassword(); return password; } /** * @return the host (can be <code>null</code> or empty) */ @Override public String getHost() { return this.host; } /** * @return the port (can be <code>null</code> or empty) */ @Override public String getPort() { return this.port; } /** * @return the port number */ @Override public int getPortNumber() { return portNumber; } /** * @return the connection type (never <code>null</code>) */ @Override public abstract String getType(); protected void generateUrl() { // mm<s>://host:port StringBuilder sb = new StringBuilder(); sb.append(isSecure() ? MMS : MM); sb.append(getHost()); sb.append(':'); sb.append(getPort()); this.url = sb.toString(); } /** * @return the URL (never <code>null</code>) */ @Override public String getUrl() { return this.url; } /** * @return the user name (can be <code>null</code> or empty) */ @Override public String getUsername() { return this.username; } /** * {@inheritDoc} * * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return HashCodeUtil.hashCode(0, getHost(), getPort(), isSecure(), getUsername()); } /** * @return <code>true</code> if a secure connection protocol is being used */ @Override public boolean isSecure() { return this.secure; } /** * The port, password, user name, persisting password, secure protocol, and host provider are set. * * @param info the connection properties whose values are being used to update state */ @Override public void setAll( ITeiidConnectionInfo info ) { settingAllInfo = true; setHost(info.getHost()); setPort(info.getPort()); setUsername(info.getUsername()); setSecure(info.isSecure()); settingAllInfo = false; setPassword(info.getPassword()); } /** * The port, password, user name, persisting password, secure protocol, and host provider are set. * * @param info the connection properties whose values are being used to update state */ @Override public void setAll(String host, String port, String username, String password, boolean isSecure ) { settingAllInfo = true; setHost(host); setPort(port); setUsername(username); setSecure(isSecure); settingAllInfo = false; setPassword(password); } /** * @return the passToken */ @Override public String getPassToken() { return this.passToken; } /** * Note. Password can be set to null and this will be stored as the value in secure storage * * @param password the new value for password (can be empty or <code>null</code>) */ @Override public void setPassword( String password ) { if( settingAllInfo ) return; // setAll() will be setting host/port/username/isSecure. Don't want to setPassword it's complete /* * Real password being passed into this method so generate a token * and use it to store the real password in secure storage */ this.url = null; this.passToken = null; this.passwordStorageKey = null; if( password != null ) { storePassword(password); } } /** * @param port the new value for host (never empty or <code>null</code>) * @see #validate() */ @Override public void setHost( String host ) { this.host = host; resetPassword(); } /** * @param port the new value for port (never empty or <code>null</code>) * @see #validate() */ @Override public void setPort( String port ) { this.port = port; try { this.portNumber = Integer.parseInt(port); } catch (NumberFormatException ex) { this.portNumber = DEFAULT_PORT_NUMBER; } resetPassword(); } /** * @param secure the new value for if a secure connection protocol should be used */ @Override public void setSecure( boolean secure ) { this.secure = secure; resetPassword(); } /** * @param username the new value for user name * @see #validate() */ @Override public void setUsername( String username ) { this.username = username; } /** * {@inheritDoc} * * @see java.lang.Object#toString() */ @Override public String toString() { return DqpPlugin.Util.getString("teiidConnectionInfoProperties", //$NON-NLS-1$ getType(), getHost(), getPort(), getUsername(), getPassword(), isSecure()); } /** * @return a status indicating if the connection info is in a validate state (never <code>null</code>) */ @Override public IStatus validate() { IStatus status = validateUrl(); if (!status.isOK()) { return status; } return validateUsername(); } /** * @return a status indicating if the URL is valid (never <code>null</code>) */ protected IStatus validateUrl() { // validate URL (protocol, host, port) try { TeiidServerUtils.validateServerUrl(getUrl()); } catch (MalformedURLException e) { return new Status(IStatus.ERROR, PLUGIN_ID, Util.getString("invalidServerUrl", getType(), e.getMessage()), e); //$NON-NLS-1$ } return Status.OK_STATUS; } /** * @return a status indicating if the user name is valida (never <code>null</code>) */ protected IStatus validateUsername() { // must have a user name if (StringUtilities.isEmpty(this.username)) { return new Status(IStatus.ERROR, PLUGIN_ID, Util.getString("connectionUsernameIsEmpty", getType())); //$NON-NLS-1$ } return Status.OK_STATUS; } }