Enable case insensitive authentication for git operations

A new configuration parameter is introduced that converts the username
that is received to authenticate a git operation to lower case for
looking up the user account in Gerrit.

By setting this parameter a case insensitive authentication for the
git operations can be achieved, if it is ensured that the usernames in
Gerrit (scheme 'username') are stored in lower case (e.g. if the
parameter 'ldap.accountSshUserName' is set to
'${sAMAccountName.toLowerCase}').

Change-Id: Icee61ab71b18b71885c20a67cf0152ee45b228f0
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
Edwin Kempin
2011-09-22 15:06:14 +02:00
parent a0c801e142
commit 4b9e5e7fb9
4 changed files with 59 additions and 10 deletions

View File

@@ -298,6 +298,25 @@ then Gerrit will do the authentication (using DIGEST authentication).
+ +
By default this is set to false. By default this is set to false.
[[auth.userNameToLowerCase]]auth.userNameToLowerCase::
+
If set the username that is received to authenticate a git operation
is converted to lower case for looking up the user account in Gerrit.
+
By setting this parameter a case insensitive authentication for the
git operations can be achieved, if it is ensured that the usernames in
Gerrit (scheme `username`) are stored in lower case (e.g. if the
parameter link:#ldap.accountSshUserName[ldap.accountSshUserName] is
set to `${sAMAccountName.toLowerCase}`). It is important that for all
existing accounts this username is already in lower case. It is not
possible to convert the usernames of the existing accounts to lower
case because this would break the access to existing per-user
branches.
+
This parameter only affects git over http and git over SSH traffic.
+
By default this is set to false.
[[cache]]Section cache [[cache]]Section cache
~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -20,12 +20,16 @@ import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState; import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.config.AuthConfig; import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gwtjsonrpc.server.XsrfException; import com.google.gwtjsonrpc.server.XsrfException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import org.eclipse.jgit.lib.Config;
import java.io.IOException; import java.io.IOException;
import java.util.Locale;
import javax.servlet.Filter; import javax.servlet.Filter;
import javax.servlet.FilterChain; import javax.servlet.FilterChain;
@@ -54,12 +58,14 @@ class ContainerAuthFilter implements Filter {
private final Provider<WebSession> session; private final Provider<WebSession> session;
private final AccountCache accountCache; private final AccountCache accountCache;
private final Config config;
@Inject @Inject
ContainerAuthFilter(Provider<WebSession> session, AccountCache accountCache) ContainerAuthFilter(Provider<WebSession> session, AccountCache accountCache,
throws XsrfException { @GerritServerConfig Config config) throws XsrfException {
this.session = session; this.session = session;
this.accountCache = accountCache; this.accountCache = accountCache;
this.config = config;
} }
@Override @Override
@@ -84,11 +90,14 @@ class ContainerAuthFilter implements Filter {
private boolean verify(HttpServletRequest req, HttpServletResponseWrapper rsp) private boolean verify(HttpServletRequest req, HttpServletResponseWrapper rsp)
throws IOException { throws IOException {
final String username = req.getRemoteUser(); String username = req.getRemoteUser();
if (username == null) { if (username == null) {
rsp.sendError(SC_FORBIDDEN); rsp.sendError(SC_FORBIDDEN);
return false; return false;
} }
if (config.getBoolean("auth", "userNameToLowerCase", false)) {
username = username.toLowerCase(Locale.US);
}
final AccountState who = accountCache.getByUsername(username); final AccountState who = accountCache.getByUsername(username);
if (who == null || !who.getAccount().isActive()) { if (who == null || !who.getAccount().isActive()) {
rsp.sendError(SC_UNAUTHORIZED); rsp.sendError(SC_UNAUTHORIZED);

View File

@@ -23,18 +23,22 @@ import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import com.google.gerrit.server.account.AccountCache; import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState; import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.config.CanonicalWebUrl; import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gwtjsonrpc.server.SignedToken; import com.google.gwtjsonrpc.server.SignedToken;
import com.google.gwtjsonrpc.server.XsrfException; import com.google.gwtjsonrpc.server.XsrfException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import org.eclipse.jgit.lib.Config;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@@ -67,16 +71,18 @@ class ProjectDigestFilter implements Filter {
private final Provider<String> urlProvider; private final Provider<String> urlProvider;
private final Provider<WebSession> session; private final Provider<WebSession> session;
private final AccountCache accountCache; private final AccountCache accountCache;
private final Config config;
private final SignedToken tokens; private final SignedToken tokens;
private ServletContext context; private ServletContext context;
@Inject @Inject
ProjectDigestFilter(@CanonicalWebUrl @Nullable Provider<String> urlProvider, ProjectDigestFilter(@CanonicalWebUrl @Nullable Provider<String> urlProvider,
Provider<WebSession> session, AccountCache accountCache) Provider<WebSession> session, AccountCache accountCache,
throws XsrfException { @GerritServerConfig Config config) throws XsrfException {
this.urlProvider = urlProvider; this.urlProvider = urlProvider;
this.session = session; this.session = session;
this.accountCache = accountCache; this.accountCache = accountCache;
this.config = config;
this.tokens = new SignedToken((int) SECONDS.convert(1, HOURS)); this.tokens = new SignedToken((int) SECONDS.convert(1, HOURS));
} }
@@ -111,7 +117,7 @@ class ProjectDigestFilter implements Filter {
} }
final Map<String, String> p = parseAuthorization(hdr); final Map<String, String> p = parseAuthorization(hdr);
final String username = p.get("username"); final String user = p.get("username");
final String realm = p.get("realm"); final String realm = p.get("realm");
final String nonce = p.get("nonce"); final String nonce = p.get("nonce");
final String uri = p.get("uri"); final String uri = p.get("uri");
@@ -121,7 +127,7 @@ class ProjectDigestFilter implements Filter {
final String cnonce = p.get("cnonce"); final String cnonce = p.get("cnonce");
final String method = req.getMethod(); final String method = req.getMethod();
if (username == null // if (user == null //
|| realm == null // || realm == null //
|| nonce == null // || nonce == null //
|| uri == null // || uri == null //
@@ -133,6 +139,11 @@ class ProjectDigestFilter implements Filter {
return false; return false;
} }
String username = user;
if (config.getBoolean("auth", "userNameToLowerCase", false)) {
username = username.toLowerCase(Locale.US);
}
final AccountState who = accountCache.getByUsername(username); final AccountState who = accountCache.getByUsername(username);
if (who == null || ! who.getAccount().isActive()) { if (who == null || ! who.getAccount().isActive()) {
rsp.sendError(SC_UNAUTHORIZED); rsp.sendError(SC_UNAUTHORIZED);
@@ -145,7 +156,7 @@ class ProjectDigestFilter implements Filter {
return false; return false;
} }
final String A1 = username + ":" + realm + ":" + passwd; final String A1 = user + ":" + realm + ":" + passwd;
final String A2 = method + ":" + uri; final String A2 = method + ":" + uri;
final String expect = final String expect =
KD(H(A1), nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + H(A2)); KD(H(A1), nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + H(A2));

View File

@@ -19,6 +19,7 @@ import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PeerDaemonUser; import com.google.gerrit.server.PeerDaemonUser;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths; import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.sshd.SshScope.Context; import com.google.gerrit.sshd.SshScope.Context;
import com.google.inject.Inject; import com.google.inject.Inject;
@@ -33,6 +34,7 @@ import org.apache.sshd.common.SshException;
import org.apache.sshd.common.util.Buffer; import org.apache.sshd.common.util.Buffer;
import org.apache.sshd.server.PublickeyAuthenticator; import org.apache.sshd.server.PublickeyAuthenticator;
import org.apache.sshd.server.session.ServerSession; import org.apache.sshd.server.session.ServerSession;
import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -47,6 +49,7 @@ import java.security.PublicKey;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Locale;
import java.util.Set; import java.util.Set;
/** /**
@@ -61,17 +64,20 @@ class DatabasePubKeyAuth implements PublickeyAuthenticator {
private final SshLog sshLog; private final SshLog sshLog;
private final IdentifiedUser.GenericFactory userFactory; private final IdentifiedUser.GenericFactory userFactory;
private final PeerDaemonUser.Factory peerFactory; private final PeerDaemonUser.Factory peerFactory;
private final Config config;
private final Set<PublicKey> myHostKeys; private final Set<PublicKey> myHostKeys;
private volatile PeerKeyCache peerKeyCache; private volatile PeerKeyCache peerKeyCache;
@Inject @Inject
DatabasePubKeyAuth(final SshKeyCacheImpl skc, final SshLog l, DatabasePubKeyAuth(final SshKeyCacheImpl skc, final SshLog l,
final IdentifiedUser.GenericFactory uf, final PeerDaemonUser.Factory pf, final IdentifiedUser.GenericFactory uf, final PeerDaemonUser.Factory pf,
final SitePaths site, final KeyPairProvider hostKeyProvider) { final SitePaths site, final KeyPairProvider hostKeyProvider,
final @GerritServerConfig Config cfg) {
sshKeyCache = skc; sshKeyCache = skc;
sshLog = l; sshLog = l;
userFactory = uf; userFactory = uf;
peerFactory = pf; peerFactory = pf;
config = cfg;
myHostKeys = myHostKeys(hostKeyProvider); myHostKeys = myHostKeys(hostKeyProvider);
peerKeyCache = new PeerKeyCache(site.peer_keys); peerKeyCache = new PeerKeyCache(site.peer_keys);
} }
@@ -91,7 +97,7 @@ class DatabasePubKeyAuth implements PublickeyAuthenticator {
} }
} }
public boolean authenticate(final String username, public boolean authenticate(String username,
final PublicKey suppliedKey, final ServerSession session) { final PublicKey suppliedKey, final ServerSession session) {
final SshSession sd = session.getAttribute(SshSession.KEY); final SshSession sd = session.getAttribute(SshSession.KEY);
@@ -107,6 +113,10 @@ class DatabasePubKeyAuth implements PublickeyAuthenticator {
} }
} }
if (config.getBoolean("auth", "userNameToLowerCase", false)) {
username = username.toLowerCase(Locale.US);
}
final Iterable<SshKeyCacheEntry> keyList = sshKeyCache.get(username); final Iterable<SshKeyCacheEntry> keyList = sshKeyCache.get(username);
final SshKeyCacheEntry key = find(keyList, suppliedKey); final SshKeyCacheEntry key = find(keyList, suppliedKey);
if (key == null) { if (key == null) {