Enable case insensitive login to Gerrit WebUI for LDAP authentication
Gerrit treats user names as case sensitive, while some LDAP servers don't. On first login to Gerrit the user enters his user name and Gerrit queries LDAP for it. Since LDAP is case-insensitive with regards to the username, the LDAP authentication succeeds regardless in which case the user typed in his user name. The username is stored in Gerrit exactly as entered by the user. For further logins the user always has to use the same case. If the user specifies his user name in a different case Gerrit tries to create a new account which fails with "Cannot assign user name ... to account ...; name already in use.". This error occurs because the LDAP query resolves to the same LDAP user and storing the username for SSH (which is by default always lower case) fails because such an entry exists already for the first account that the user created. This change introduces a new configuration parameter that converts the user name always to lower case before doing the LDAP authentication. By this the login to the Gerrit WebUI gets case insensitive. If this configuration parameter is set, the user names for all existing accounts have to be converted to lower case. This change includes a server program to do this conversion. Change-Id: I2d9099509b9438eaf88506c99e4dd3cc82a9140f Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
parent
f7b65a20d4
commit
b3b0d29467
@ -1515,6 +1515,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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
68
Documentation/pgm-LocalUsernamesToLowerCase.txt
Normal file
68
Documentation/pgm-LocalUsernamesToLowerCase.txt
Normal 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]
|
@ -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]
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user