diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java index 5a29044e18..def3aed887 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java @@ -34,6 +34,7 @@ import com.google.gerrit.server.config.CanonicalWebUrlModule; import com.google.gerrit.server.config.CanonicalWebUrlProvider; import com.google.gerrit.server.config.GerritGlobalModule; import com.google.gerrit.server.config.MasterNodeStartup; +import com.google.gerrit.server.contact.HttpContactStoreConnection; import com.google.gerrit.server.git.PushReplication; import com.google.gerrit.server.git.WorkQueue; import com.google.gerrit.server.mail.SmtpEmailSender; @@ -253,6 +254,7 @@ public class Daemon extends SiteProgram { modules.add(sysInjector.getInstance(GitOverHttpModule.class)); modules.add(sshInjector.getInstance(WebSshGlueModule.class)); modules.add(CacheBasedWebSession.module()); + modules.add(HttpContactStoreConnection.module()); if (sshd) { modules.add(sshInjector.getInstance(ProjectQoSFilter.Module.class)); } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreConnection.java b/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreConnection.java new file mode 100644 index 0000000000..24c9e66c1d --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreConnection.java @@ -0,0 +1,42 @@ +// Copyright (C) 2011 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gerrit.server.contact; + +import java.io.IOException; +import java.net.URL; + +/** Single connection to a {@link ContactStore}. */ +public interface ContactStoreConnection { + public static interface Factory { + /** + * Open a new connection to a {@link ContactStore}. + * + * @param url contact store URL. + * @return a new connection to the store. + * + * @throws IOException the URL couldn't be opened. + */ + ContactStoreConnection open(URL url) throws IOException; + } + + /** + * Store a blob of contact data in the store. + * + * @param body protocol-specific body data. + * + * @throws IOException an error occurred storing the contact data. + */ + public void store(byte[] body) throws IOException; +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreProvider.java index da17c08581..e3b8ea7028 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreProvider.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/contact/ContactStoreProvider.java @@ -22,25 +22,30 @@ import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.ProvisionException; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openpgp.PGPPublicKey; import org.eclipse.jgit.lib.Config; import java.io.File; import java.net.MalformedURLException; import java.net.URL; +import java.security.Security; /** Creates the {@link ContactStore} based on the configuration. */ public class ContactStoreProvider implements Provider { private final Config config; private final SitePaths site; private final SchemaFactory schema; + private final ContactStoreConnection.Factory connFactory; @Inject ContactStoreProvider(@GerritServerConfig final Config config, - final SitePaths site, final SchemaFactory schema) { + final SitePaths site, final SchemaFactory schema, + final ContactStoreConnection.Factory connFactory) { this.config = config; this.site = site; this.schema = schema; + this.connFactory = connFactory; } @Override @@ -68,12 +73,14 @@ public class ContactStoreProvider implements Provider { throw new ProvisionException("PGP public key file \"" + pubkey.getAbsolutePath() + "\" not found"); } - return new EncryptedContactStore(storeUrl, storeAPPSEC, pubkey, schema); + return new EncryptedContactStore(storeUrl, storeAPPSEC, pubkey, schema, + connFactory); } private static boolean havePGP() { try { Class.forName(PGPPublicKey.class.getName()); + Security.addProvider(new BouncyCastleProvider()); return true; } catch (NoClassDefFoundError noBouncyCastle) { return false; diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java b/gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java index 05d4e7a783..d39169443b 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/contact/EncryptedContactStore.java @@ -37,7 +37,6 @@ import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; import org.bouncycastle.openpgp.PGPUtil; -import org.eclipse.jgit.util.IO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,7 +46,6 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.HttpURLConnection; import java.net.URL; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; @@ -70,13 +68,16 @@ class EncryptedContactStore implements ContactStore { private final SecureRandom prng; private final URL storeUrl; private final String storeAPPSEC; + private final ContactStoreConnection.Factory connFactory; EncryptedContactStore(final URL storeUrl, final String storeAPPSEC, - final File pubKey, final SchemaFactory schema) { + final File pubKey, final SchemaFactory schema, + final ContactStoreConnection.Factory connFactory) { this.storeUrl = storeUrl; this.storeAPPSEC = storeAPPSEC; this.schema = schema; this.dest = selectKey(readPubRing(pubKey)); + this.connFactory = connFactory; final String prngName = "SHA1PRNG"; try { @@ -157,33 +158,7 @@ class EncryptedContactStore implements ContactStore { } u.put("account_id", String.valueOf(account.getId().get())); u.put("data", encStr); - final byte[] body = u.toString().getBytes("UTF-8"); - - final HttpURLConnection c = (HttpURLConnection) storeUrl.openConnection(); - c.setRequestMethod("POST"); - c.setRequestProperty("Content-Type", - "application/x-www-form-urlencoded; charset=UTF-8"); - c.setDoOutput(true); - c.setFixedLengthStreamingMode(body.length); - final OutputStream out = c.getOutputStream(); - out.write(body); - out.close(); - - if (c.getResponseCode() == 200) { - final byte[] dst = new byte[2]; - final InputStream in = c.getInputStream(); - try { - IO.readFully(in, dst, 0, 2); - } finally { - in.close(); - } - if (dst[0] != 'O' || dst[1] != 'K') { - throw new IOException("Store failed: " + c.getResponseCode()); - } - } else { - throw new IOException("Store failed: " + c.getResponseCode()); - } - + connFactory.open(storeUrl).store(u.toString().getBytes("UTF-8")); } catch (IOException e) { log.error("Cannot store encrypted contact information", e); throw new ContactInformationStoreException(e); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/contact/HttpContactStoreConnection.java b/gerrit-server/src/main/java/com/google/gerrit/server/contact/HttpContactStoreConnection.java new file mode 100644 index 0000000000..781f401f26 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/contact/HttpContactStoreConnection.java @@ -0,0 +1,74 @@ +// Copyright 2011 Google Inc. All Rights Reserved. + +package com.google.gerrit.server.contact; + +import static com.google.inject.Scopes.SINGLETON; + +import com.google.gerrit.server.contact.ContactStoreConnection; +import com.google.gerrit.server.contact.HttpContactStoreConnection; +import com.google.inject.AbstractModule; +import com.google.inject.Inject; +import com.google.inject.Module; +import com.google.inject.assistedinject.Assisted; +import com.google.inject.assistedinject.FactoryProvider; + +import org.eclipse.jgit.util.IO; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; + +/** {@link ContactStoreConnection} with an underlying {@HttpURLConnection}. */ +public class HttpContactStoreConnection implements ContactStoreConnection { + public static Module module() { + return new AbstractModule() { + @Override + protected void configure() { + bind(ContactStoreConnection.Factory.class) + .toProvider(FactoryProvider.newFactory( + ContactStoreConnection.Factory.class, + HttpContactStoreConnection.class)) + .in(SINGLETON); + } + }; + } + + private final HttpURLConnection conn; + + @Inject + HttpContactStoreConnection(@Assisted final URL url) throws IOException { + final URLConnection urlConn = url.openConnection(); + if (!(urlConn instanceof HttpURLConnection)) { + throw new IllegalArgumentException("Non-HTTP URL not supported: " + urlConn); + } + conn = (HttpURLConnection) urlConn; + } + + @Override + public void store(final byte[] body) throws IOException { + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", + "application/x-www-form-urlencoded; charset=UTF-8"); + conn.setDoOutput(true); + conn.setFixedLengthStreamingMode(body.length); + final OutputStream out = conn.getOutputStream(); + out.write(body); + out.close(); + if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) { + throw new IOException("Connection failed: " + conn.getResponseCode()); + } + final byte[] dst = new byte[2]; + final InputStream in = conn.getInputStream(); + try { + IO.readFully(in, dst, 0, 2); + } finally { + in.close(); + } + if (dst[0] != 'O' || dst[1] != 'K') { + throw new IOException("Store failed: " + dst[0] + dst[1]); + } + } +} diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java index 89581a1750..5c3a7d982f 100644 --- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java +++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java @@ -25,6 +25,7 @@ import com.google.gerrit.server.config.GerritGlobalModule; import com.google.gerrit.server.config.GerritServerConfigModule; import com.google.gerrit.server.config.MasterNodeStartup; import com.google.gerrit.server.config.SitePath; +import com.google.gerrit.server.contact.HttpContactStoreConnection; import com.google.gerrit.server.git.LocalDiskRepositoryManager; import com.google.gerrit.server.git.PushReplication; import com.google.gerrit.server.git.WorkQueue; @@ -208,6 +209,7 @@ public class WebAppInitializer extends GuiceServletContextListener { modules.add(sysInjector.getInstance(GitOverHttpModule.class)); modules.add(sshInjector.getInstance(WebSshGlueModule.class)); modules.add(CacheBasedWebSession.module()); + modules.add(HttpContactStoreConnection.module()); return sysInjector.createChildInjector(modules); }