http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/CertificateStore.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/CertificateStore.java b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/CertificateStore.java new file mode 100755 index 0000000..f06eaf5 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/CertificateStore.java @@ -0,0 +1,127 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2010, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.client.deprecated; + +import java.io.*; +import java.security.*; +import java.security.cert.*; +import java.security.cert.Certificate; +import java.util.*; + +/** + * Specialized certificate storage based on {@link KeyStore} for managing trusted certificates. + */ +@Deprecated // Use SimpleX509TrustManager +public class CertificateStore { + + private final KeyStore keyStore; + + /** + * Get the underlying KeyStore. + */ + KeyStore getKeyStore() { + return keyStore; + } + + /** + * Helper method that creates a {@link KeyStore} by reading it from a file. + */ + static KeyStore load(File file, String password) throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException { + KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); + try { + InputStream input = new FileInputStream(file); + try { + ks.load(input, password == null ? null : password.toCharArray()); + } finally { + input.close(); + } + } catch (IOException e) { + // Return an empty initialized KeyStore + ks.load(null, null); + } + return ks; + } + + /** + * Helper method that writes a {@link KeyStore} to a file. + */ + static void store(KeyStore ks, File file, String password) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException { + OutputStream output = new FileOutputStream(file); + try { + ks.store(output, password == null ? null : password.toCharArray()); + } finally { + output.close(); + } + } + + /** + * Helper to compute a unique alias within the trust store for a specified certificate. + * @param cert The certificate to compute an alias for. + */ + static String computeAlias(Certificate cert) { + // There appears to be no standard way to construct certificate aliases, + // but this class never depends on looking up a certificate by its + // computed alias, so just create an alias that's unique and be done. + return UUID.randomUUID().toString(); + } + + /** + * Construct a new TrustStore initially containing no certificates. + */ + public CertificateStore() throws NoSuchAlgorithmException, CertificateException, IOException { + try { + keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + } catch (KeyStoreException e) { + // If the code above caused a KeyStoreException, then the JVM classpath is probably messed up. + throw new RuntimeException("KeyStoreException: ["+e.getLocalizedMessage()+"]. " + + "Likely cause is that the Java Cryptography Extension libraries are missing from the JRE classpath. " + + "Make sure %JAVA_HOME%/lib/ext is specified in your JVM's java.ext.dirs system property."); + } + keyStore.load(null, null); + } + + /** + * Does the trust store contain the specified certificate? + */ + public boolean containsCertificate(Certificate cert) throws KeyStoreException { + return (keyStore.getCertificateAlias(cert) != null); + } + + /** + * Enter the specified certificate into the trust store. + */ + public void enterCertificate(Certificate cert) throws KeyStoreException { + if (! containsCertificate(cert)) + keyStore.setCertificateEntry(computeAlias(cert), cert); + } + + /* + * Helper to copy all the certificate entries, and none of the other + * entries, from a {@link KeyStore} into the trust store. + */ + private void enterCertificates(KeyStore ks) throws KeyStoreException { + for (Enumeration<String> e = ks.aliases(); e.hasMoreElements();) { + String alias = e.nextElement(); + if (ks.isCertificateEntry(alias)) { + Certificate cert = ks.getCertificate(alias); + enterCertificate(cert); + } + } + } + + /** + * Load the specified {@link KeyStore} file and copy all of the certificates + * it contains into the trust store. Only certificates, and not any other + * entries, are loaded. + */ + public void loadCertificates(File file, String password) throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException { + KeyStore ks = load(file, password); + enterCertificates(ks); + } +}
http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ICertificateValidator$Trust.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ICertificateValidator$Trust.class b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ICertificateValidator$Trust.class new file mode 100755 index 0000000..3051b31 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ICertificateValidator$Trust.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ICertificateValidator.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ICertificateValidator.class b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ICertificateValidator.class new file mode 100755 index 0000000..d470011 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ICertificateValidator.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ICertificateValidator.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ICertificateValidator.java b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ICertificateValidator.java new file mode 100755 index 0000000..0ee07e8 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ICertificateValidator.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2010, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.client.deprecated; + +import java.security.cert.*; + +/** + * Validator of certificates presented by a server when establishing an SSL + * connection. + */ +@Deprecated // Use SimpleX509TrustManager +public interface ICertificateValidator { + + /** Action to take for a server-supplied certificate. */ + public enum Trust { + + /** Do not accept the certificate. */ + REJECT, + + /** Accept the certificate temporarily for the current connection. */ + ACCEPT_CONNECTION, + + /** Accept the certificate temporarily for the current session. */ + ACCEPT_SESSION, + + /** Accept the certificate permanently, by saving it in the user's trust store.*/ + ACCEPT_PERMANENT + } + + /** + * There is a problem accepting the server-supplied certificate. What should + * be done? + * + * @param cert The problematic certificate presented by the server + * @param problem The {@link CertificateException} that may indicate the specific + * problem with the certificate, e.g. {@link CertificateExpiredException}. + * @return The disposition on the certificate. + */ + Trust validate(X509Certificate cert, CertificateException problem); +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ITrustStoreProvider.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ITrustStoreProvider.class b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ITrustStoreProvider.class new file mode 100755 index 0000000..923672e Binary files /dev/null and b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ITrustStoreProvider.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ITrustStoreProvider.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ITrustStoreProvider.java b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ITrustStoreProvider.java new file mode 100755 index 0000000..47256a9 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ITrustStoreProvider.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2010, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.client.deprecated; + +import java.io.*; +import java.security.*; +import java.security.cert.*; +import java.security.cert.Certificate; + +/** + * Utility class for handling certificate stores. + */ +@Deprecated // Use SimpleX509TrustManager +public interface ITrustStoreProvider { + + /** + * Returns the store of all certificates trusted for the lifetime + * of this trust provider + */ + CertificateStore getSessionTrustStore(); + + /** + * Returns the store of all permanently trusted certificates. + */ + CertificateStore getRuntimeTrustStore(); + + /** + * Install a certificate in the user's application-specific on-disk key + * store, if possible. + */ + public void installCertificate(Certificate cert) throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException; + +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/LenientCertificateValidator.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/LenientCertificateValidator.class b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/LenientCertificateValidator.class new file mode 100755 index 0000000..ad456a0 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/LenientCertificateValidator.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/LenientCertificateValidator.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/LenientCertificateValidator.java b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/LenientCertificateValidator.java new file mode 100755 index 0000000..a12ca43 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/LenientCertificateValidator.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2010, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.client.deprecated; + +import java.security.cert.*; + +/** + * Lenient certificate validator that always accepts invalid certificates. + */ +@Deprecated // Use SimpleX509TrustManager +public final class LenientCertificateValidator implements ICertificateValidator { + + /** Singleton */ + public static final ICertificateValidator INSTANCE = new LenientCertificateValidator(); + + @Override /* ICertificateValidator */ + public Trust validate(X509Certificate certificate, CertificateException problem) { + return Trust.ACCEPT_CONNECTION; + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/SharedTrustStoreProvider.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/SharedTrustStoreProvider.class b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/SharedTrustStoreProvider.class new file mode 100755 index 0000000..e68e4c7 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/SharedTrustStoreProvider.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/SharedTrustStoreProvider.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/SharedTrustStoreProvider.java b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/SharedTrustStoreProvider.java new file mode 100755 index 0000000..149302d --- /dev/null +++ b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/SharedTrustStoreProvider.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2010, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.client.deprecated; + +import java.io.*; +import java.security.*; +import java.security.cert.*; +import java.security.cert.Certificate; + +/** + * Trust store provider with shared static certificate stores. + */ +@Deprecated // Use SimpleX509TrustManager +public final class SharedTrustStoreProvider implements ITrustStoreProvider { + + // In-memory trust store of all certificates explicitly accepted by the + // certificate validator during this session. The validator will not be + // called again during this session for any of these certificates. These may + // include expired, not yet valid, or otherwise untrusted certificates. + // These are kept distinctly, rather than merged into the runtime trust + // store, because the base trust manager will never accept expired, etc. + // certificates, even if from a trusted source. + private static CertificateStore sessionCerts; + + // In-memory trust store of all permanently trusted certificates, assembled + // from a number of key store files. These are provided to the base trust + // manager as the basis for its decision making. + private static CertificateStore runtimeCerts; + + // Location and password of the user's private trust store for this application. + private static String userTrustStoreLocation; + private static String userTrustStorePassword; + + static { + init(); + } + + private static final void init() { + try { + String userHome = System.getProperty("user.home"); + String javaHome = System.getProperty("java.home"); + + userTrustStoreLocation = userHome + "/.jazzcerts"; + userTrustStorePassword = "ibmrationaljazz"; + + sessionCerts = new CertificateStore(); + + runtimeCerts = new CertificateStore(); + + // JRE keystore override + String file = System.getProperty("javax.net.ssl.trustStore"); + String password = System.getProperty("javax.net.ssl.trustStorePassword"); + addCertificatesFromStore(runtimeCerts, file, password); + + // JRE Signer CA keystore + file = javaHome + "/lib/security/cacerts"; + addCertificatesFromStore(runtimeCerts, file, null); + + // JRE Secure Site CA keystore + file = (javaHome + "/lib/security/jssecacerts"); + addCertificatesFromStore(runtimeCerts, file, null); + + // Application-specific keystore for the current user + addCertificatesFromStore(runtimeCerts, userTrustStoreLocation, userTrustStorePassword); + + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static void addCertificatesFromStore(CertificateStore store, String file, String password) { + try { + File f = new File(file); + if (f.canRead()) + store.loadCertificates(f, password); + } catch (Exception e) { + // Discard errors + } + } + + @Override /* ITrustStoreProvider */ + public CertificateStore getRuntimeTrustStore() { + return runtimeCerts; + } + + @Override /* ITrustStoreProvider */ + public CertificateStore getSessionTrustStore() { + return sessionCerts; + } + + @Override /* ITrustStoreProvider */ + public void installCertificate(Certificate cert) throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException { + File f = new File(userTrustStoreLocation); + KeyStore ks = CertificateStore.load(f, userTrustStorePassword); + ks.setCertificateEntry(CertificateStore.computeAlias(cert), cert); + CertificateStore.store(ks, f, userTrustStorePassword); + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ValidatingX509TrustManager$1.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ValidatingX509TrustManager$1.class b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ValidatingX509TrustManager$1.class new file mode 100755 index 0000000..1673c74 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ValidatingX509TrustManager$1.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ValidatingX509TrustManager.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ValidatingX509TrustManager.class b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ValidatingX509TrustManager.class new file mode 100755 index 0000000..b06f337 Binary files /dev/null and b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ValidatingX509TrustManager.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ValidatingX509TrustManager.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ValidatingX509TrustManager.java b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ValidatingX509TrustManager.java new file mode 100755 index 0000000..a7539fe --- /dev/null +++ b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/deprecated/ValidatingX509TrustManager.java @@ -0,0 +1,113 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2010, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.client.deprecated; + +import java.io.*; +import java.security.*; +import java.security.cert.*; + +import javax.net.ssl.*; + +/** + * A trust manager that will call a registered {@link ICertificateValidator} in + * the event that a problematic (e.g. expired, not yet valid) or untrusted + * certificate is presented by a server, and react appropriately. This trust + * manager will rely on multiple key stores, and manage one of its own. The + * managed key store and the session-accepted key store are shared by all trust + * manager instances. + */ +@Deprecated // Use SimpleX509TrustManager +public final class ValidatingX509TrustManager implements X509TrustManager { + + // The JRE-provided trust manager used to validate certificates presented by a server. + private X509TrustManager baseTrustManager; + + // The registered certificate validator, may be null, called when the base + // trust manager rejects a certificate presented by a server. + private ICertificateValidator validator; + + private ITrustStoreProvider trustStoreProvider; + + /** + * Construct a new ValidatingX509TrustManager. + * + * @param validator Certificate validator to consult regarding problematic + * certificates, or <code>null</code> to always reject them. + */ + public ValidatingX509TrustManager(ICertificateValidator validator) throws KeyStoreException, NoSuchAlgorithmException { + this.validator = validator; + this.trustStoreProvider = new SharedTrustStoreProvider(); + + // Initialize the base X509 trust manager that will be used to evaluate + // certificates presented by the server against the runtime trust store. + TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + factory.init(trustStoreProvider.getRuntimeTrustStore().getKeyStore()); + TrustManager[] managers = factory.getTrustManagers(); + for (TrustManager manager : managers) { + if (manager instanceof X509TrustManager) { + baseTrustManager = (X509TrustManager) manager; // Take the first X509TrustManager we find + return; + } + } + throw new IllegalStateException("Couldn't find JRE's X509TrustManager"); //$NON-NLS-1$ + } + + @Override /* X509TrustManager */ + public X509Certificate[] getAcceptedIssuers() { + return baseTrustManager.getAcceptedIssuers(); + } + + @Override /* X509TrustManager */ + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + baseTrustManager.checkClientTrusted(chain, authType); + } + + @Override /* X509TrustManager */ + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + X509Certificate cert = chain[0]; + + // Has the certificate been OK'd for the session? + try { + if (trustStoreProvider.getSessionTrustStore().containsCertificate(cert)) + return; + } catch (KeyStoreException e) { + // Ignore; proceed to try base trust manager + } + + try { + // Rely on the base trust manager to check the certificate against the assembled runtime key store + baseTrustManager.checkServerTrusted(chain, authType); + } catch (CertificateException certEx) { + + // Done if there isn't a validator to consult + if (validator == null) + throw certEx; // Rejected! + + // Ask the registered certificate validator to rule on the certificate + ICertificateValidator.Trust disposition = validator.validate(cert, certEx); + switch (disposition) { + case REJECT: throw certEx; + case ACCEPT_CONNECTION: break; + case ACCEPT_SESSION: enterCertificate(cert, false); break; + case ACCEPT_PERMANENT: enterCertificate(cert, true); break; + } + } + } + + private void enterCertificate(X509Certificate cert, boolean permanent) throws CertificateException { + try { + trustStoreProvider.getSessionTrustStore().enterCertificate(cert); + if (permanent) + trustStoreProvider.installCertificate(cert); + } catch (KeyStoreException e) { + } catch (NoSuchAlgorithmException e) { + } catch (IOException e) { + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/jazz/JazzRestClient.class ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/jazz/JazzRestClient.class b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/jazz/JazzRestClient.class new file mode 100755 index 0000000..5fc0b7e Binary files /dev/null and b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/jazz/JazzRestClient.class differ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/jazz/JazzRestClient.java ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/jazz/JazzRestClient.java b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/jazz/JazzRestClient.java new file mode 100755 index 0000000..58f3017 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/jazz/JazzRestClient.java @@ -0,0 +1,390 @@ +/******************************************************************************* + * Licensed Materials - Property of IBM + * (c) Copyright IBM Corporation 2014, 2015. All Rights Reserved. + * + * The source code for this program is not published or otherwise + * divested of its trade secrets, irrespective of what has been + * deposited with the U.S. Copyright Office. + *******************************************************************************/ +package com.ibm.juno.client.jazz; + +import static org.apache.http.HttpStatus.*; + +import java.io.*; +import java.net.*; +import java.util.*; + +import org.apache.http.*; +import org.apache.http.auth.*; +import org.apache.http.client.*; +import org.apache.http.client.config.*; +import org.apache.http.client.entity.*; +import org.apache.http.client.methods.*; +import org.apache.http.impl.client.*; +import org.apache.http.message.*; +import org.apache.http.util.*; + +import com.ibm.juno.client.*; +import com.ibm.juno.core.parser.*; +import com.ibm.juno.core.serializer.*; +import com.ibm.juno.core.utils.*; + +/** + * Specialized {@link RestClient} for working with Jazz servers. + * <p> + * Provides support for BASIC, FORM, and OIDC authentication against Jazz servers and simple SSL certificate validation. + * + * <h6 class='topic'>Additional Information</h6> + * <ul> + * <li><a class='doclink' href='package-summary.html#RestClient'>com.ibm.juno.client.jazz > Jazz REST client API</a> for more information and code examples. + * </ul> + * @author James Bognar ([email protected]) + */ +public class JazzRestClient extends RestClient { + + private String user, pw; + private URI jazzUri; + private SSLOpts sslOpts; + private String cookie = null; + + /** + * Create a new client with no serializer or parser. + * + * @param jazzUrl The URL of the Jazz server being connected to (e.g. <js>"https://localhost:9443/jazz"</js>) + * @param sslOpts SSL options. + * @param user The Jazz username. + * @param pw The Jazz password. + * @throws IOException If a problem occurred trying to authenticate against the Jazz server. + */ + public JazzRestClient(String jazzUrl, SSLOpts sslOpts, String user, String pw) throws IOException { + super(); + this.user = user; + this.pw = pw; + if (! jazzUrl.endsWith("/")) + jazzUrl = jazzUrl + "/"; + this.sslOpts = sslOpts; + jazzUri = URI.create(jazzUrl); + } + + /** + * Create a new client with no serializer or parser, and LAX SSL support. + * + * @param jazzUrl The URL of the Jazz server being connected to (e.g. <js>"https://localhost:9443/jazz"</js>) + * @param user The Jazz username. + * @param pw The Jazz password. + * @throws IOException + */ + public JazzRestClient(String jazzUrl, String user, String pw) throws IOException { + this(jazzUrl, SSLOpts.LAX, user, pw); + } + + /** + * Create a new client with the specified serializer and parser instances. + * + * @param jazzUri The URI of the Jazz server being connected to (e.g. <js>"https://localhost:9443/jazz"</js>) + * @param sslOpts SSL options. + * @param user The Jazz username. + * @param pw The Jazz password. + * @param s The serializer for converting POJOs to HTTP request message body text. + * @param p The parser for converting HTTP response message body text to POJOs. + * @throws IOException If a problem occurred trying to authenticate against the Jazz server. + */ + public JazzRestClient(String jazzUri, SSLOpts sslOpts, String user, String pw, Serializer<?> s, Parser<?> p) throws IOException { + this(jazzUri, sslOpts, user, pw); + setParser(p); + setSerializer(s); + } + + /** + * Create a new client with the specified serializer and parser instances and LAX SSL support. + * + * @param jazzUri The URI of the Jazz server being connected to (e.g. <js>"https://localhost:9443/jazz"</js>) + * @param user The Jazz username. + * @param pw The Jazz password. + * @param s The serializer for converting POJOs to HTTP request message body text. + * @param p The parser for converting HTTP response message body text to POJOs. + * @throws IOException If a problem occurred trying to authenticate against the Jazz server. + */ + public JazzRestClient(String jazzUri, String user, String pw, Serializer<?> s, Parser<?> p) throws IOException { + this(jazzUri, SSLOpts.LAX, user, pw); + setParser(p); + setSerializer(s); + } + + /** + * Create a new client with the specified serializer and parser classes. + * + * @param jazzUri The URI of the Jazz server being connected to (e.g. <js>"https://localhost:9443/jazz"</js>) + * @param sslOpts SSL options. + * @param user The Jazz username. + * @param pw The Jazz password. + * @param s The serializer for converting POJOs to HTTP request message body text. + * @param p The parser for converting HTTP response message body text to POJOs. + * @throws IOException If a problem occurred trying to authenticate against the Jazz server. + * @throws InstantiationException If serializer or parser could not be instantiated. + */ + public JazzRestClient(String jazzUri, SSLOpts sslOpts, String user, String pw, Class<? extends Serializer<?>> s, Class<? extends Parser<?>> p) throws InstantiationException, IOException { + this(jazzUri, sslOpts, user, pw); + setParser(p); + setSerializer(s); + } + + /** + * Create a new client with the specified serializer and parser classes and LAX SSL support. + * + * @param jazzUri The URI of the Jazz server being connected to (e.g. <js>"https://localhost:9443/jazz"</js>) + * @param user The Jazz username. + * @param pw The Jazz password. + * @param s The serializer for converting POJOs to HTTP request message body text. + * @param p The parser for converting HTTP response message body text to POJOs. + * @throws IOException If a problem occurred trying to authenticate against the Jazz server. + * @throws InstantiationException If serializer or parser could not be instantiated. + */ + public JazzRestClient(String jazzUri, String user, String pw, Class<? extends Serializer<?>> s, Class<? extends Parser<?>> p) throws InstantiationException, IOException { + this(jazzUri, SSLOpts.LAX, user, pw); + setParser(p); + setSerializer(s); + } + + @Override /* RestClient */ + protected CloseableHttpClient createHttpClient() throws Exception { + try { + if (jazzUri.getScheme().equals("https")) + enableSSL(sslOpts); + + setRedirectStrategy(new AllowAllRedirects()); + + // See wi 368181. The PublicSuffixDomainFilter uses a default PublicSuffixMatcher + // that rejects hostnames lacking a dot, such as "ccmserver", so needed + // cookies don't get put on outgoing requests. + // Here, we create a cookie spec registry with handlers that don't have a PublicSuffixMatcher. + if (! Boolean.getBoolean("com.ibm.team.repository.transport.client.useDefaultPublicSuffixMatcher")) { //$NON-NLS-1$ + // use a lenient PublicSuffixDomainFilter + setDefaultCookieSpecRegistry(CookieSpecRegistries.createDefault(null)); + } + + // We want to use a fresh HttpClientBuilder since the default implementation + // uses an unshared PoolingConnectionManager, and if you close the client + // and create a new one, can cause a "java.lang.IllegalStateException: Connection pool shut down" + CloseableHttpClient client = createHttpClientBuilder().build(); + + // Tomcat will respond with SC_BAD_REQUEST (or SC_REQUEST_TIMEOUT?) when the + // j_security_check URL is visited before an authenticated URL has been visited. + visitAuthenticatedURL(client); + + // Authenticate against the server. + String authMethod = determineAuthMethod(client); + if (authMethod.equals("FORM")) { + formBasedAuthenticate(client); + visitAuthenticatedURL(client); + } else if (authMethod.equals("BASIC")) { + AuthScope scope = new AuthScope(jazzUri.getHost(), jazzUri.getPort()); + Credentials up = new UsernamePasswordCredentials(user, pw); + CredentialsProvider p = new BasicCredentialsProvider(); + p.setCredentials(scope, up); + setDefaultCredentialsProvider(p); + client.close(); + client = getHttpClientBuilder().build(); + } else if (authMethod.equals("OIDC")) { + oidcAuthenticate(client); + client.close(); + client = getHttpClientBuilder().build(); + } + + return client; + } catch (Exception e) { + throw e; + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + @Override /* RestClient */ + protected HttpClientBuilder createHttpClientBuilder() { + HttpClientBuilder b = super.createHttpClientBuilder(); + + // See wi 368181. The PublicSuffixDomainFilter uses a default PublicSuffixMatcher + // that rejects hostnames lacking a dot, such as "ccmserver", so needed + // cookies don't get put on outgoing requests. + // Here, we create a cookie spec registry with handlers that don't have a PublicSuffixMatcher. + if (! Boolean.getBoolean("com.ibm.team.repository.transport.client.useDefaultPublicSuffixMatcher")) + b.setDefaultCookieSpecRegistry(CookieSpecRegistries.createDefault(null)); + + return b; + } + + + /** + * Performs form-based authentication against the Jazz server. + */ + private void formBasedAuthenticate(HttpClient client) throws IOException { + + URI uri2 = jazzUri.resolve("j_security_check"); + HttpPost request = new HttpPost(uri2); + request.setConfig(RequestConfig.custom().setRedirectsEnabled(false).build()); + // Charset must explicitly be set to UTF-8 to handle user/pw with non-ascii characters. + request.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8"); + + NameValuePairs params = new NameValuePairs() + .append(new BasicNameValuePair("j_username", user)) + .append(new BasicNameValuePair("j_password", pw)); + request.setEntity(new UrlEncodedFormEntity(params)); + + HttpResponse response = client.execute(request); + try { + int rc = response.getStatusLine().getStatusCode(); + + Header authMsg = response.getFirstHeader("X-com-ibm-team-repository-web-auth-msg"); + if (authMsg != null) + throw new IOException(authMsg.getValue()); + + // The form auth request should always respond with a 200 ok or 302 redirect code + if (rc == SC_MOVED_TEMPORARILY) { + if (response.getFirstHeader("Location").getValue().matches("^.*/auth/authfailed.*$")) + throw new IOException("Invalid credentials."); + } else if (rc != SC_OK) { + throw new IOException("Unexpected HTTP status: " + rc); + } + } finally { + EntityUtils.consume(response.getEntity()); + } + } + + private void oidcAuthenticate(HttpClient client) throws IOException { + + HttpGet request = new HttpGet(jazzUri); + request.setConfig(RequestConfig.custom().setRedirectsEnabled(false).build()); + + // Charset must explicitly be set to UTF-8 to handle user/pw with non-ascii characters. + request.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8"); + + HttpResponse response = client.execute(request); + try { + int code = response.getStatusLine().getStatusCode(); + + // Already authenticated + if (code == SC_OK) + return; + + if (code != SC_UNAUTHORIZED) + throw new RestCallException("Unexpected response during OIDC authentication: " + response.getStatusLine()); + + //'x-jsa-authorization-redirect' + String redirectUri = getHeader(response, "X-JSA-AUTHORIZATION-REDIRECT"); + + if (redirectUri == null) + throw new RestCallException("Excpected a redirect URI during OIDC authentication: " + response.getStatusLine()); + + // Handle Bearer Challenge + HttpGet method = new HttpGet(redirectUri + "&prompt=none"); + addDefaultOidcHeaders(method); + + response = client.execute(method); + + code = response.getStatusLine().getStatusCode(); + + if (code != SC_OK) + throw new RestCallException("Unexpected response during OIDC authentication phase 2: " + response.getStatusLine()); + + String loginRequired = getHeader(response, "X-JSA-LOGIN-REQUIRED"); + + if (! "true".equals(loginRequired)) + throw new RestCallException("X-JSA-LOGIN-REQUIRED header not found on response during OIDC authentication phase 2: " + response.getStatusLine()); + + method = new HttpGet(redirectUri + "&prompt=none"); + + addDefaultOidcHeaders(method); + response = client.execute(method); + + code = response.getStatusLine().getStatusCode(); + + if (code != SC_OK) + throw new RestCallException("Unexpected response during OIDC authentication phase 3: " + response.getStatusLine()); + + // Handle JAS Challenge + method = new HttpGet(redirectUri); + addDefaultOidcHeaders(method); + + response = client.execute(method); + + code = response.getStatusLine().getStatusCode(); + + if (code != SC_OK) + throw new RestCallException("Unexpected response during OIDC authentication phase 4: " + response.getStatusLine()); + + cookie = getHeader(response, "Set-Cookie"); + + Header[] defaultHeaders = new Header[] { + new BasicHeader("User-Agent", "Jazz Native Client"), + new BasicHeader("X-com-ibm-team-configuration-versions", "com.ibm.team.rtc=6.0.0,com.ibm.team.jazz.foundation=6.0"), + new BasicHeader("Accept", "text/json"), + new BasicHeader("Authorization", "Basic " + StringUtils.base64EncodeToString(this.user + ":" + this.pw)), + new BasicHeader("Cookie", cookie) + }; + + setDefaultHeaders(Arrays.asList(defaultHeaders)); + + } finally { + EntityUtils.consume(response.getEntity()); + } + } + + /* + * This is needed for Tomcat because it responds with SC_BAD_REQUEST when the j_security_check URL is visited before an + * authenticated URL has been visited. This same URL must also be visited after authenticating with j_security_check + * otherwise tomcat will not consider the session authenticated + */ + private int visitAuthenticatedURL(HttpClient httpClient) throws IOException { + HttpGet authenticatedURL = new HttpGet(jazzUri.resolve("authenticated/identity")); + HttpResponse response = httpClient.execute(authenticatedURL); + try { + return response.getStatusLine().getStatusCode(); + } finally { + EntityUtils.consume(response.getEntity()); + } + } + + /* + * @return Returns "FORM" for form-based authenication, "BASIC" for basic auth, "OIDC" for OIDC. Never <code>null</code>. + */ + private String determineAuthMethod(HttpClient client) throws IOException { + + HttpGet request = new HttpGet(jazzUri.resolve("authenticated/identity")); + request.setConfig(RequestConfig.custom().setRedirectsEnabled(false).build()); + + // if the FORM_AUTH_URI path exists, then we know we are using FORM auth + HttpResponse response = client.execute(request); + try { //'x-jsa-authorization-redirect' + Header redirectUri = response.getFirstHeader("X-JSA-AUTHORIZATION-REDIRECT"); + if (redirectUri != null) + return "OIDC"; + + int rc = response.getStatusLine().getStatusCode(); + // Tomcat and Jetty return a status code 200 if the server is using FORM auth + if (rc == SC_OK) + return "FORM"; + else if (rc == SC_MOVED_TEMPORARILY && response.getFirstHeader("Location").getValue().matches("^.*(/auth/authrequired|/authenticated/identity).*$")) + return "FORM"; + return "BASIC"; + + } finally { + EntityUtils.consume(response.getEntity()); + } + } + + private String getHeader(HttpResponse response, String key) { + Header h = response.getFirstHeader(key); + return (h == null ? null : h.getValue()); + } + + private void addDefaultOidcHeaders(HttpRequestBase method) { + method.addHeader("User-Agent", "Jazz Native Client"); + method.addHeader("X-com-ibm-team-configuration-versions", "com.ibm.team.rtc=6.0.0,com.ibm.team.jazz.foundation=6.0"); + method.addHeader("Accept", "text/json"); + + if (cookie != null) { + method.addHeader("Authorization", "Basic " + StringUtils.base64EncodeToString(user + ":" + pw)); + method.addHeader("Cookie", cookie); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/7e4f63e6/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/jazz/package.html ---------------------------------------------------------------------- diff --git a/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/jazz/package.html b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/jazz/package.html new file mode 100755 index 0000000..fce9248 --- /dev/null +++ b/com.ibm.team.juno.releng/bin/client/com/ibm/juno/client/jazz/package.html @@ -0,0 +1,187 @@ +<!DOCTYPE HTML> +<!-- + Licensed Materials - Property of IBM + (c) Copyright IBM Corporation 2014. All Rights Reserved. + + Note to U.S. Government Users Restricted Rights: + Use, duplication or disclosure restricted by GSA ADP Schedule + Contract with IBM Corp. + --> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <style type="text/css"> + /* For viewing in Page Designer */ + @IMPORT url("../../../../../../javadoc.css"); + + /* For viewing in REST interface */ + @IMPORT url("../htdocs/javadoc.css"); + body { + margin: 20px; + } + </style> + <script> + /* Replace all @code and @link tags. */ + window.onload = function() { + document.body.innerHTML = document.body.innerHTML.replace(/\{\@code ([^\}]+)\}/g, '<code>$1</code>'); + document.body.innerHTML = document.body.innerHTML.replace(/\{\@link (([^\}]+)\.)?([^\.\}]+)\}/g, '<code>$3</code>'); + } + </script> +</head> +<body> +<p>Jazz REST client API</p> + +<script> + function toggle(x) { + var div = x.nextSibling; + while (div != null && div.nodeType != 1) + div = div.nextSibling; + if (div != null) { + var d = div.style.display; + if (d == 'block' || d == '') { + div.style.display = 'none'; + x.className += " closed"; + } else { + div.style.display = 'block'; + x.className = x.className.replace(/(?:^|\s)closed(?!\S)/g , '' ); + } + } + } +</script> + +<a id='TOC'></a><h5 class='toc'>Table of Contents</h5> +<ol class='toc'> + <li><p><a class='doclink' href='#RestClient'>Jazz REST client API</a></p> +</ol> + +<!-- ======================================================================================================== --> +<a id="RestClient"></a> +<h2 class='topic' onclick='toggle(this)'>1 - Jazz REST client API</h2> +<div class='topic'> + <p> + Juno provides a default REST client implementation for working with Jazz servers. + The client automatically detects and handles BASIC and FORM authentication and basic certificate authentication. + </p> + <p> + The following code shows the Jazz REST client being used for querying and creating server messages on + a Jazz server. The <code>ServerMessage</code> and <code>CreateServerMessage</code> classes + are nothing more than simple beans that get serialized over the connection and reconstituted on + the server. + </p> + <p class='bcode'> + System.<jsf>out</jsf>.println(<js>"Adding sample messages"</js>); + + DateFormat df = <jk>new</jk> SimpleDateFormat(<js>"yyyy-MM-dd'T'HH:mm:ssZ"</js>); + String url = <js>"https://localhost:9443/jazz"</js>; + String sms = url + <js>"/serverMessages"</js>; + CreateServerMessage m; + ServerMessage m2; + String s1; + ServerMessage[] messages; + + <jc>// Serializer for debug messages.</jc> + WriterSerializer serializer = JsonSerializer.<jsf>DEFAULT</jsf>; + + <jc>// Create clients to handle JSON and XML requests and responses.</jc> + RestClient jsonClient = <jk>new</jk> JazzRestClient(url, <js>"ADMIN"</js>, <js>"ADMIN"</js>) + .setSerializer(JsonSerializer.<jk>class</jk>) + .setParser(<jk>new</jk> JsonParser().addFilters(DateFilter.<jsf>ISO8601DTZ</jsf>.<jk>class</jk>)); + + RestClient xmlClient = <jk>new</jk> JazzRestClient(url, <js>"ADMIN"</js>, <js>"ADMIN"</js>, XmlSerializer.<jk>class</jk>, XmlParser.<jk>class</jk>); + + <jc>// Delete any existing messages.</jc> + messages = jsonClient + .doGet(sms) + .getResponse(ServerMessage[].<jk>class</jk>); + + <jk>for</jk> (ServerMessage message : messages) { + <jk>int</jk> rc = jsonClient + .doDelete(message.getUri()) + .execute(); + System.<jsf>out</jsf>.println(rc); <jc>// Prints 200.</jc> + } + + <jc>// Create an active server message.</jc> + m = <jk>new</jk> CreateServerMessage( + <jsf>INFO</jsf>, + <js>"Test message #1"</js>, + <js>"subTypeFoo"</js>, + df.parse(<js>"2012-01-01T12:34:56EST"</js>), + df.parse(<js>"2013-01-01T12:34:56EST"</js>)); + + <jc>// POST the message, get response as string.</jc> + s1 = jsonClient + .doPost(sms, m) + .getResponseAsString(); + System.<jsf>out</jsf>.println(<js>"TEST1: response="</js> + s1); + + <jc>// POST another message, get response as ServerMessage</jc> + m = <jk>new</jk> CreateServerMessage( + <jsf>INFO</jsf>, + <js>"Test message #2"</js>, + <js>"subTypeFoo"</js>, + df.parse(<js>"2012-01-01T12:34:56EST"</js>), + df.parse(<js>"2013-01-01T12:34:56EST"</js>)); + + m2 = jsonClient + .doPost(sms, m) + .getResponse(ServerMessage.<jk>class</jk>); + System.<jsf>out</jsf>.println(<js>"TEST2: response="</js> + serializer.serialize(m2)); + + <jc>// Create a future server message.</jc> + m = <jk>new</jk> CreateServerMessage( + <jsf>INFO</jsf>, + <js>"Test message #3"</js>, + <js>"subTypeFoo"</js>, + df.parse(<js>"2013-01-01T12:34:56EST"</js>), + df.parse(<js>"2014-01-01T12:34:56EST"</js>)); + + m2 = jsonClient + .doPost(sms, m) + .getResponse(ServerMessage.<jk>class</jk>); + System.<jsf>out</jsf>.println(<js>"TEST3: response="</js> + serializer.serialize(m2)); + System.<jsf>out</jsf>.println(<js>"TEST3: id="</js> + m2.getItemId().getUuidValue()); + + <jc>// Create a future server message using XML on both request and response.</jc> + m = <jk>new</jk> CreateServerMessage( + <jsf>INFO</jsf>, + <js>"Test message #4"</js>, + <js>"subTypeFoo"</js>, + df.parse(<js>"2013-01-01T12:34:56EST"</js>), + df.parse(<js>"2014-01-01T12:34:56EST"</js>)); + + s1 = xmlClient + .doPost(sms, m) + .getResponseAsString(); + System.<jsf>out</jsf>.println(<js>"TEST4: response="</js> + s1); + + <jc>// Get all the messages</jc> + messages = jsonClient + .doGet(sms) + .getResponse(ServerMessage[].<jk>class</jk>); + System.<jsf>out</jsf>.println(<js>"TEST5: response="</js> + serializer.serialize(messages)); + + <jc>// Get the first ID</jc> + URI firstMessageUrl = messages[0].getUri(); + + System.<jsf>out</jsf>.println(<js>"firstMessageUrl=["</js>+firstMessageUrl+<js>"]"</js>); + + <jc>// Get the Date of the first ID.</jc> + Date startDate = jsonClient + .doGet(firstMessageUrl + <js>"/startDate"</js>) + .getResponse(Date.<jk>class</jk>); + System.<jsf>out</jsf>.println(<js>"TEST5: response.startDate="</js>+startDate); + + <jc>// Change the start and end dates on first message</jc> + m = <jk>new</jk> CreateServerMessage( + <jsf>INFO</jsf>, + <js>"Test message #3 overwritten"</js>, + <js>"subTypeFooBar"</js>, + df.parse(<js>"2023-01-01T12:34:56EST"</js>), + df.parse(<js>"2024-01-01T12:34:56EST"</js>)); + s1 = jsonClient.doPut(firstMessageUrl, m).getResponseAsString(); + System.<jsf>out</jsf>.println(<js>"TEST6: response="</js>+s1); + </p> +</div> +</body> +</html> \ No newline at end of file
