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>
This commit is contained in:
Shawn O. Pearce 2011-05-15 13:33:15 -07:00
parent 4a11a38b0b
commit 7929d874a0
5 changed files with 122 additions and 7 deletions

View File

@ -1945,6 +1945,9 @@ Sample `etc/secure.config`:
[sendemail]
smtpPass = sp@m
[remote "bar"]
password = s3kr3t
----
File `etc/replication.config`

View File

@ -194,6 +194,24 @@ By default, replicates without group control, i.e replicates
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`
----------------------------------

View File

@ -19,7 +19,6 @@ import static com.google.inject.Scopes.SINGLETON;
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.AccountInfoCacheFactory;
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.PushAllProjectsOp;
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.tools.ToolsCatalog;
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 @@ public class GerritGlobalModule extends FactoryModule {
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);

View File

@ -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.Constants;
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 @@ class PushOp implements ProjectRunnable {
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 @@ class PushOp implements ProjectRunnable {
@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 @@ class PushOp implements ProjectRunnable {
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()) {

View File

@ -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;
}
}