Merge changes Icee61ab7,I440ef67c,I2d909950

* changes:
  Enable case insensitive authentication for git operations
  ContainerAuthFilter: fail with FORBIDDEN if username not set
  Enable case insensitive login to Gerrit WebUI for LDAP authentication
This commit is contained in:
Shawn Pearce
2011-10-27 16:09:38 -07:00
committed by gerrit code review
10 changed files with 318 additions and 13 deletions

View File

@@ -298,6 +298,25 @@ then Gerrit will do the authentication (using DIGEST authentication).
+
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
~~~~~~~~~~~~~~~~~~~~~~
@@ -1521,6 +1540,24 @@ Attributes such as `${dn}` or `${uidNumber}` may be useful.
Default is `(memberUid=${username})` for RFC 2307,
and unset (disabled) for Active Directory.
[[ldap.localUsernameToLowerCase]]ldap.localUsernameToLowerCase::
+
Converts the local username, that is used to login into the Gerrit
WebUI, to lower case before doing the LDAP authentication. By setting
this parameter to true, a case insensitive login to the Gerrit WebUI
can be achieved.
+
If set, it must be ensured that the local usernames for all existing
accounts are converted to lower case, otherwise a user that has a
local username that contains upper case characters cannot login
anymore. The local usernames for the existing accounts can be
converted to lower case by running the server program
link:pgm-LocalUsernamesToLowerCase.html[LocalUsernamesToLowerCase].
Please be aware that the conversion of the local usernames to lower
case can't be undone. For newly created accounts the local username
will be directly stored in lower case.
+
By default, unset/false.
[[mimetype]]Section mimetype
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -0,0 +1,68 @@
LocalUsernamesToLowerCase
=========================
NAME
----
LocalUsernamesToLowerCase - Convert the local username of every
account to lower case
SYNOPSIS
--------
[verse]
'java' -jar gerrit.war 'LocalUsernamesToLowerCase' -d <SITE_PATH>
DESCRIPTION
-----------
Converts the local username for every account to lower case. The
local username is the username that is used to login into the Gerrit
WebUI.
This task is only intended to be run if the configuration parameter
link:config-gerrit.html#ldap.localUsernameToLowerCase[ldap.localUsernameToLowerCase]
was set to true to achieve case insensitive LDAP login to the Gerrit
WebUI.
Please be aware that the conversion of the local usernames to lower
case can't be undone.
The program will produce errors if there are accounts that have the
same local username, but with different case. In this case the local
username for these accounts is not converted to lower case.
This task can run in the background concurrently to the server if the
database is MySQL or PostgreSQL. If the database is H2, this task
must be run by itself.
OPTIONS
-------
-d::
\--site-path::
Location of the gerrit.config file, and all other per-site
configuration data, supporting libraries and log files.
\--threads::
Number of threads to perform the scan work with. Defaults to
twice the number of CPUs available.
CONTEXT
-------
This command can only be run on a server which has direct
connectivity to the metadata database.
EXAMPLES
--------
To convert the local username of every account to lower case:
====
$ java -jar gerrit.war LocalUsernamesToLowerCase -d site_path
====
See Also
--------
* Configuration parameter link:config-gerrit.html#ldap.localUsernameToLowerCase[ldap.localUsernameToLowerCase]
GERRIT
------
Part of link:index.html[Gerrit Code Review]

View File

@@ -36,6 +36,9 @@ link:pgm-ExportReviewNotes.html[ExportReviewNotes]::
link:pgm-ScanTrackingIds.html[ScanTrackingIds]::
Rescan all changes after configuring trackingids.
link:pgm-LocalUsernamesToLowerCase.html[LocalUsernamesToLowerCase]::
Convert the local username of every account to lower case.
GERRIT
------
Part of link:index.html[Gerrit Code Review]

View File

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

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

View File

@@ -0,0 +1,145 @@
// 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.pgm;
import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.pgm.util.SiteProgram;
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.schema.SchemaVersionCheck;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.client.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Injector;
import org.eclipse.jgit.lib.TextProgressMonitor;
import org.kohsuke.args4j.Option;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
/** Converts the local username for all accounts to lower case */
public class LocalUsernamesToLowerCase extends SiteProgram {
@Option(name = "--threads", usage = "Number of concurrent threads to run")
private int threads = 2;
private final LifecycleManager manager = new LifecycleManager();
private final TextProgressMonitor monitor = new TextProgressMonitor();
private List<AccountExternalId> todo;
private Injector dbInjector;
@Inject
private SchemaFactory<ReviewDb> database;
@Override
public int run() throws Exception {
if (threads <= 0) {
threads = 1;
}
dbInjector = createDbInjector(MULTI_USER);
manager.add(dbInjector,
dbInjector.createChildInjector(SchemaVersionCheck.module()));
manager.start();
dbInjector.injectMembers(this);
final ReviewDb db = database.open();
try {
todo = db.accountExternalIds().all().toList();
synchronized (monitor) {
monitor.beginTask("Converting local username", todo.size());
}
} finally {
db.close();
}
final List<Worker> workers = new ArrayList<Worker>(threads);
for (int tid = 0; tid < threads; tid++) {
Worker t = new Worker();
t.start();
workers.add(t);
}
for (Worker t : workers) {
t.join();
}
synchronized (monitor) {
monitor.endTask();
}
manager.stop();
return 0;
}
private void convertLocalUserToLowerCase(final ReviewDb db,
final AccountExternalId extId) {
if (extId.isScheme(AccountExternalId.SCHEME_GERRIT)) {
final String localUser = extId.getSchemeRest();
final String localUserLowerCase = localUser.toLowerCase(Locale.US);
if (!localUser.equals(localUserLowerCase)) {
final AccountExternalId.Key extIdKeyLowerCase =
new AccountExternalId.Key(AccountExternalId.SCHEME_GERRIT,
localUserLowerCase);
final AccountExternalId extIdLowerCase =
new AccountExternalId(extId.getAccountId(), extIdKeyLowerCase);
try {
db.accountExternalIds().insert(Collections.singleton(extIdLowerCase));
db.accountExternalIds().delete(Collections.singleton(extId));
} catch (OrmException error) {
System.err.println("ERR " + error.getMessage());
}
}
}
}
private AccountExternalId next() {
synchronized (todo) {
if (todo.isEmpty()) {
return null;
}
return todo.remove(todo.size() - 1);
}
}
private class Worker extends Thread {
@Override
public void run() {
final ReviewDb db;
try {
db = database.open();
} catch (OrmException e) {
e.printStackTrace();
return;
}
try {
for (;;) {
final AccountExternalId extId = next();
if (extId == null) {
break;
}
convertLocalUserToLowerCase(db, extId);
synchronized (monitor) {
monitor.update(1);
}
}
} finally {
db.close();
}
}
}
}

View File

@@ -42,4 +42,7 @@ public interface AccountExternalIdAccess extends
@Query("WHERE emailAddress >= ? AND emailAddress <= ? ORDER BY emailAddress LIMIT ?")
ResultSet<AccountExternalId> suggestByEmailAddress(String emailA,
String emailB, int limit) throws OrmException;
@Query
ResultSet<AccountExternalId> all() throws OrmException;
}

View File

@@ -52,7 +52,7 @@ public class AuthRequest {
return r;
}
private final String externalId;
private String externalId;
private String password;
private String displayName;
private String emailAddress;
@@ -78,6 +78,14 @@ public class AuthRequest {
return null;
}
public void setLocalUser(final String localUser) {
if (isScheme(SCHEME_GERRIT)) {
final AccountExternalId.Key key =
new AccountExternalId.Key(SCHEME_GERRIT, localUser);
externalId = key.get();
}
}
public String getPassword() {
return password;
}

View File

@@ -49,6 +49,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -67,6 +68,7 @@ class LdapRealm implements Realm {
private final EmailExpander emailExpander;
private final Cache<String, Account.Id> usernameCache;
private final Set<Account.FieldName> readOnlyAccountFields;
private final Config config;
private final Cache<String, Set<AccountGroup.UUID>> membershipCache;
@@ -83,6 +85,7 @@ class LdapRealm implements Realm {
this.emailExpander = emailExpander;
this.usernameCache = usernameCache;
this.membershipCache = membershipCache;
this.config = config;
this.readOnlyAccountFields = new HashSet<Account.FieldName>();
@@ -181,6 +184,10 @@ class LdapRealm implements Realm {
public AuthRequest authenticate(final AuthRequest who)
throws AccountException {
if (config.getBoolean("ldap", "localUsernameToLowerCase", false)) {
who.setLocalUser(who.getLocalUser().toLowerCase(Locale.US));
}
final String username = who.getLocalUser();
try {
final DirContext ctx;

View File

@@ -19,6 +19,7 @@ import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PeerDaemonUser;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.sshd.SshScope.Context;
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.server.PublickeyAuthenticator;
import org.apache.sshd.server.session.ServerSession;
import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -47,6 +49,7 @@ import java.security.PublicKey;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
/**
@@ -61,17 +64,20 @@ class DatabasePubKeyAuth implements PublickeyAuthenticator {
private final SshLog sshLog;
private final IdentifiedUser.GenericFactory userFactory;
private final PeerDaemonUser.Factory peerFactory;
private final Config config;
private final Set<PublicKey> myHostKeys;
private volatile PeerKeyCache peerKeyCache;
@Inject
DatabasePubKeyAuth(final SshKeyCacheImpl skc, final SshLog l,
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;
sshLog = l;
userFactory = uf;
peerFactory = pf;
config = cfg;
myHostKeys = myHostKeys(hostKeyProvider);
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 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 SshKeyCacheEntry key = find(keyList, suppliedKey);
if (key == null) {