Support HTTP authentication for replication
Gerrit now supports HTTP Basic and Digest authentication when
a remote http:// URL is configured for replication. The password
can be supplied by secure.config.
Change-Id: Id1277ba8d9a8653f1b2f94d8b5fe94323623c5c5
Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 84a4310..35fc3da 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1945,6 +1945,9 @@
[sendemail]
smtpPass = sp@m
+
+[remote "bar"]
+ password = s3kr3t
----
File `etc/replication.config`
diff --git a/Documentation/config-replication.txt b/Documentation/config-replication.txt
index 81fda98..53c69a5 100644
--- a/Documentation/config-replication.txt
+++ b/Documentation/config-replication.txt
@@ -194,6 +194,24 @@
everything to all remotes.
+[[secure_config]]File `secure.config`
+-----------------------------------------------
+
+The optional file `'$site_path'/secure.config` is a Git-style config
+file that provides secure values that should not be world-readable,
+such as passwords. Passwords for HTTP remotes can be obtained from
+this file.
+
+[[remote.name.username]]remote.<name>.username::
++
+Username to use for HTTP authentication on this remote, if not given
+in the URL.
+
+[[remote.name.password]]remote.<name>.password::
++
+Password to use for HTTP authentication on this remote.
+
+
[[ssh_config]]File `~/.ssh/config`
----------------------------------
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index df10c27..5426f34 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -19,7 +19,6 @@
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.lifecycle.LifecycleListener;
import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AuthType;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.server.AnonymousUser;
@@ -35,12 +34,11 @@
import com.google.gerrit.server.account.DefaultRealm;
import com.google.gerrit.server.account.EmailExpander;
import com.google.gerrit.server.account.GroupCacheImpl;
-import com.google.gerrit.server.account.GroupInfoCacheFactory;
import com.google.gerrit.server.account.GroupIncludeCacheImpl;
+import com.google.gerrit.server.account.GroupInfoCacheFactory;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.auth.ldap.LdapModule;
import com.google.gerrit.server.cache.CachePool;
-import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.events.EventFactory;
import com.google.gerrit.server.git.ChangeMergeQueue;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -50,6 +48,7 @@
import com.google.gerrit.server.git.PushReplication;
import com.google.gerrit.server.git.ReloadSubmitQueueOp;
import com.google.gerrit.server.git.ReplicationQueue;
+import com.google.gerrit.server.git.SecureCredentialsProvider;
import com.google.gerrit.server.git.TransferConfig;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.mail.EmailSender;
@@ -68,16 +67,13 @@
import com.google.gerrit.server.util.IdGenerator;
import com.google.gerrit.server.workflow.FunctionState;
import com.google.inject.Inject;
-import com.google.inject.TypeLiteral;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.runtime.RuntimeConstants;
-
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.PersonIdent;
import java.util.Properties;
-import java.util.Set;
/** Starts global state with standard dependencies. */
@@ -173,6 +169,7 @@
bind(TransferConfig.class);
bind(ReplicationQueue.class).to(PushReplication.class).in(SINGLETON);
+ factory(SecureCredentialsProvider.Factory.class);
factory(PushAllProjectsOp.Factory.class);
bind(MergeQueue.class).to(ChangeMergeQueue.class).in(SINGLETON);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java
index 049e0e0..a24d416 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java
@@ -15,8 +15,8 @@
package com.google.gerrit.server.git;
import com.google.gerrit.reviewdb.Project;
-import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.Project.NameKey;
+import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gwtorm.client.OrmException;
@@ -34,6 +34,7 @@
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.FetchConnection;
import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RefSpec;
@@ -70,6 +71,7 @@
private final SchemaFactory<ReviewDb> schema;
private final PushReplication.ReplicationConfig pool;
private final RemoteConfig config;
+ private final CredentialsProvider credentialsProvider;
private final Set<String> delta = new HashSet<String>();
private final Project.NameKey projectName;
@@ -88,11 +90,13 @@
@Inject
PushOp(final GitRepositoryManager grm, final SchemaFactory<ReviewDb> s,
final PushReplication.ReplicationConfig p, final RemoteConfig c,
+ final SecureCredentialsProvider.Factory cpFactory,
@Assisted final Project.NameKey d, @Assisted final URIish u) {
repoManager = grm;
schema = s;
pool = p;
config = c;
+ credentialsProvider = cpFactory.create(c.getName());
projectName = d;
uri = u;
}
@@ -249,6 +253,7 @@
private PushResult pushVia(final Transport tn) throws IOException,
NotSupportedException, TransportException {
tn.applyConfig(config);
+ tn.setCredentialsProvider(credentialsProvider);
final List<RemoteRefUpdate> todo = generateUpdates(tn);
if (todo.isEmpty()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SecureCredentialsProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SecureCredentialsProvider.java
new file mode 100644
index 0000000..d51936a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SecureCredentialsProvider.java
@@ -0,0 +1,92 @@
+// 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.git;
+
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.jgit.errors.UnsupportedCredentialItem;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.transport.CredentialItem;
+import org.eclipse.jgit.transport.CredentialsProvider;
+import org.eclipse.jgit.transport.URIish;
+
+/** Looks up a remote's password in secure.config. */
+public class SecureCredentialsProvider extends CredentialsProvider {
+ public interface Factory {
+ SecureCredentialsProvider create(String remoteName);
+ }
+
+ private final String cfgUser;
+ private final String cfgPass;
+
+ @Inject
+ SecureCredentialsProvider(@GerritServerConfig Config cfg,
+ @Assisted String remoteName) {
+ cfgUser = cfg.getString("remote", remoteName, "username");
+ cfgPass = cfg.getString("remote", remoteName, "password");
+ }
+
+ @Override
+ public boolean isInteractive() {
+ return false;
+ }
+
+ @Override
+ public boolean supports(CredentialItem... items) {
+ for (CredentialItem i : items) {
+ if (i instanceof CredentialItem.Username) {
+ continue;
+ } else if (i instanceof CredentialItem.Password) {
+ continue;
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean get(URIish uri, CredentialItem... items)
+ throws UnsupportedCredentialItem {
+ String username = uri.getUser();
+ if (username == null) {
+ username = cfgUser;
+ }
+ if (username == null) {
+ return false;
+ }
+
+ String password = uri.getPass();
+ if (password == null) {
+ password = cfgPass;
+ }
+ if (password == null) {
+ return false;
+ }
+
+ for (CredentialItem i : items) {
+ if (i instanceof CredentialItem.Username) {
+ ((CredentialItem.Username) i).setValue(username);
+ } else if (i instanceof CredentialItem.Password) {
+ ((CredentialItem.Password) i).setValue(password.toCharArray());
+ } else {
+ throw new UnsupportedCredentialItem(uri, i.getPromptText());
+ }
+ }
+ return true;
+ }
+}