Merge "Merge branch 'stable-2.11'"

This commit is contained in:
David Pursehouse
2015-05-12 00:37:09 +00:00
committed by Gerrit Code Review
18 changed files with 472 additions and 169 deletions

View File

@@ -3406,10 +3406,14 @@ By default 0.
+
The maximum number of matches evaluated for change access when using full text search.
+
Making this number too high could have a negative impact on performance.
+
By default 100.
[[suggest.fullTextSearchRefresh]]suggest.fullTextSearchRefresh::
+
Refresh interval for the in-memory account search index.
+
By default 1 hour.
[[theme]]
=== Section theme

View File

@@ -85,7 +85,7 @@ encryption algorithm is required.
If you are encountering 'Page Not Found' errors when opening the change
screen, your Apache proxy is very likely decoding the passed URL.
Make sure to either use 'AllowEncodedSlashes On' together with
'ProxyPass .. nodecode' or alternatively a 'mod_rewrite' configuration with
'ProxyPass .. nocanon' or alternatively a 'mod_rewrite' configuration with
'AllowEncodedSlashes NoDecode' set.

View File

@@ -0,0 +1,49 @@
Release notes for Gerrit 2.10.4
===============================
There are no schema changes from link:ReleaseNotes-2.10.3.1.html[2.10.3.1].
Download:
link:https://gerrit-releases.storage.googleapis.com/gerrit-2.10.4.war[
https://gerrit-releases.storage.googleapis.com/gerrit-2.10.4.war]
New Features
------------
* Support identity linking in hybrid OpenID and OAuth2 authentication.
+
Linking of user identities accross protocol boundaries and from one OAuth2
identity to another OAuth2 identity is supported.
* Support identity linking in OAuth2 extension point.
+
Linking of user identities from one OAuth2 identity to another OAuth2
identity is supported.
Bug Fixes
---------
* link:https://code.google.com/p/gerrit/issues/detail?id=3300[Issue 3300]:
Fix >10x performance degradation for Git push and replication operations.
+
A link:https://bugs.eclipse.org/bugs/show_bug.cgi?id=465509[regression in jgit]
caused a performance degradation.
* link:https://code.google.com/p/gerrit/issues/detail?id=3312[Issue 3312]:
Flush padding on patches downloaded as base64.
+
The padding was not flushed, which caused the downloaded patch to not be
valid base64.
OAuth extension point
~~~~~~~~~~~~~~~~~~~~~
* Check for session validity during logout.
+
When user was trying to log out, after Gerrit restart, the session was
invalidated and IllegalStateException was recorded in the error_log.
Updates
-------
* Update jgit to 4.0.0.201505050340-m2.

View File

@@ -0,0 +1,84 @@
Release notes for Gerrit 2.11.1
===============================
Gerrit 2.11.1 is now available:
link:https://gerrit-releases.storage.googleapis.com/gerrit-2.11.1.war[
https://gerrit-releases.storage.googleapis.com/gerrit-2.11.1.war]
Gerrit 2.11.1 includes the bug fixes done with
link:ReleaseNotes-2.10.4.html[Gerrit 2.10.4]. These bug fixes are *not* listed
in these release notes.
There are no schema changes from link:ReleaseNotes-2.11.html[2.11].
New Features
------------
* link:http://code.google.com/p/gerrit/issues/detail?id=321[Issue 321]:
Use in-memory Lucene index for a better reviewer suggestion.
+
Instead of a linear full text search through a list of accounts use an
in-memory Lucene index. The index is periodically refreshed. The refresh period
is configurable via the
link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11.1/config-gerrit.html#suggest.fullTextSearchRefresh[
suggest.fullTextSearchRefresh] parameter.
Bug Fixes
---------
* Fix PatchLineCommentsUtil.draftByChangeAuthor.
+
There is not a native index for this, and the ReviewDb case was not properly
filtering a result by change.
* link:http://code.google.com/p/gerrit/issues/detail?id=3323[Issue 3323]:
Fix internal server error when cloning from a slave while hiding some refs.
* Require 'View Plugins' capability to list plugins through SSH.
* link:http://code.google.com/p/gerrit/issues/detail?id=3191[Issue 3191]:
Always show 'Not Current' as state when looking at old patch set.
+
For merged changes it was confusing for users to see the status as 'Merged' when
they look at an old patch set.
* Fix project creation with plugin config if user is not project owner.
+
On project creation it is possible to specify plugin configuration values that
should be stored in the `project.config` file. This failed if the calling user
was not becoming owner of the created project, because only project owners can
edit the `project.config` file.
* link:http://code.google.com/p/gerrit/issues/detail?id=3342[Issue 3342]:
Log IOExceptions on failure to update project configuration.
+
Without logging these exceptions it's hard to guess why the update of the
project configuration is failing.
* Don't show stack trace when failing to build BloomFilter during reindex.
* link:http://code.google.com/p/gerrit/issues/detail?id=3337[Issue 3337]:
Reenable revert button when revert is cancelled.
* link:http://code.google.com/p/gerrit/issues/detail?id=3325[Issue 3325]:
Add missing `--newrev` parameter to the
link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11.1/config-hooks.html#_change_merged[
change-merged hook documentation].
* Fix gc_log when running in a web container.
+
All logs supposed to be in gc_log file were ending up in main log instead when
deploying Gerrit in a web container.
* link:http://code.google.com/p/gerrit/issues/detail?id=3346[Issue 3346]:
Fix typo in the
link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11.1/config-reverseproxy.html[
Apache 2 configuration documentation].
Updates
-------
* Update CodeMirror to 5.0.

View File

@@ -486,7 +486,7 @@ before they are applied to the git repository.
* Plugins can provide project-aware top menu extensions
+
Plugins can provide sub-menu items within the 'Projects' context. The
'${projectName}' placeholder is replaced by the project name.
'$\{projectName\}' placeholder is replaced by the project name.
* Auto register static/init.js as JavaScript plugin.
+

View File

@@ -4,11 +4,13 @@ Gerrit Code Review - Release Notes
[[2_11]]
Version 2.11.x
--------------
* link:ReleaseNotes-2.11.1.html[2.11.1]
* link:ReleaseNotes-2.11.html[2.11]
[[2_10]]
Version 2.10.x
--------------
* link:ReleaseNotes-2.10.4.html[2.10.4]
* link:ReleaseNotes-2.10.3.1.html[2.10.3.1]
* link:ReleaseNotes-2.10.3.html[2.10.3]
* link:ReleaseNotes-2.10.2.html[2.10.2]

View File

@@ -62,9 +62,9 @@ public class SuggestReviewersIT extends AbstractDaemonTest {
group2 = group("users2");
group3 = group("users3");
user1 = user("user1", group1);
user2 = user("user2", group2);
user3 = user("user3", group1, group2);
user1 = user("user1", "First1 Last1", group1);
user2 = user("user2", "First2 Last2", group2);
user3 = user("user3", "First3 Last3", group1, group2);
}
@Test
@@ -116,7 +116,7 @@ public class SuggestReviewersIT extends AbstractDaemonTest {
String changeId = createChange().getChangeId();
List<SuggestedReviewerInfo> reviewers;
reviewers = suggestReviewers(changeId, user2.fullName, 2);
reviewers = suggestReviewers(changeId, user2.username, 2);
assertThat(reviewers).hasSize(1);
assertThat(Iterables.getOnlyElement(reviewers).account.name)
.isEqualTo(user2.fullName);
@@ -126,13 +126,13 @@ public class SuggestReviewersIT extends AbstractDaemonTest {
assertThat(reviewers).isEmpty();
setApiUser(user2);
reviewers = suggestReviewers(changeId, user2.fullName, 2);
reviewers = suggestReviewers(changeId, user2.username, 2);
assertThat(reviewers).hasSize(1);
assertThat(Iterables.getOnlyElement(reviewers).account.name)
.isEqualTo(user2.fullName);
setApiUser(user3);
reviewers = suggestReviewers(changeId, user2.fullName, 2);
reviewers = suggestReviewers(changeId, user2.username, 2);
assertThat(reviewers).hasSize(1);
assertThat(Iterables.getOnlyElement(reviewers).account.name)
.isEqualTo(user2.fullName);
@@ -145,13 +145,13 @@ public class SuggestReviewersIT extends AbstractDaemonTest {
List<SuggestedReviewerInfo> reviewers;
setApiUser(user1);
reviewers = suggestReviewers(changeId, user2.fullName, 2);
reviewers = suggestReviewers(changeId, user2.username, 2);
assertThat(reviewers).isEmpty();
setApiUser(user1); // Clear cached group info.
allowGlobalCapabilities(group1.getGroupUUID(),
GlobalCapability.VIEW_ALL_ACCOUNTS);
reviewers = suggestReviewers(changeId, user2.fullName, 2);
reviewers = suggestReviewers(changeId, user2.username, 2);
assertThat(reviewers).hasSize(1);
assertThat(Iterables.getOnlyElement(reviewers).account.name)
.isEqualTo(user2.fullName);
@@ -170,9 +170,49 @@ public class SuggestReviewersIT extends AbstractDaemonTest {
@GerritConfig(name = "suggest.fullTextSearch", value = "true")
public void suggestReviewersFullTextSearch() throws Exception {
String changeId = createChange().getChangeId();
List<SuggestedReviewerInfo> reviewers =
suggestReviewers(changeId, "ser", 5);
assertThat(reviewers).hasSize(4);
List<SuggestedReviewerInfo> reviewers;
reviewers = suggestReviewers(changeId, "first", 4);
assertThat(reviewers).hasSize(3);
reviewers = suggestReviewers(changeId, "first1", 2);
assertThat(reviewers).hasSize(1);
reviewers = suggestReviewers(changeId, "last", 4);
assertThat(reviewers).hasSize(3);
reviewers = suggestReviewers(changeId, "last1", 2);
assertThat(reviewers).hasSize(1);
reviewers = suggestReviewers(changeId, "fi la", 4);
assertThat(reviewers).hasSize(3);
reviewers = suggestReviewers(changeId, "la fi", 4);
assertThat(reviewers).hasSize(3);
reviewers = suggestReviewers(changeId, "first1 la", 2);
assertThat(reviewers).hasSize(1);
reviewers = suggestReviewers(changeId, "fi last1", 2);
assertThat(reviewers).hasSize(1);
reviewers = suggestReviewers(changeId, "first1 last2", 1);
assertThat(reviewers).hasSize(0);
reviewers = suggestReviewers(changeId, name("user"), 7);
assertThat(reviewers).hasSize(6);
reviewers = suggestReviewers(changeId, user1.username, 2);
assertThat(reviewers).hasSize(1);
reviewers = suggestReviewers(changeId, "example.com", 6);
assertThat(reviewers).hasSize(5);
reviewers = suggestReviewers(changeId, user1.email, 2);
assertThat(reviewers).hasSize(1);
reviewers = suggestReviewers(changeId, user1.username + " example", 2);
assertThat(reviewers).hasSize(1);
}
@Test
@@ -183,14 +223,14 @@ public class SuggestReviewersIT extends AbstractDaemonTest {
public void suggestReviewersFullTextSearchLimitMaxMatches() throws Exception {
String changeId = createChange().getChangeId();
List<SuggestedReviewerInfo> reviewers =
suggestReviewers(changeId, "ser", 3);
suggestReviewers(changeId, name("user"), 2);
assertThat(reviewers).hasSize(2);
}
@Test
public void suggestReviewersWithoutLimitOptionSpecified() throws Exception {
String changeId = createChange().getChangeId();
String query = user3.fullName;
String query = user3.username;
List<SuggestedReviewerInfo> suggestedReviewerInfos = gApi.changes()
.id(changeId)
.suggestReviewers(query)
@@ -214,7 +254,8 @@ public class SuggestReviewersIT extends AbstractDaemonTest {
return GroupDescriptions.toAccountGroup(d);
}
private TestAccount user(String name, AccountGroup... groups) throws Exception {
private TestAccount user(String name, String fullName, AccountGroup... groups)
throws Exception {
name = name(name);
String[] groupNames = FluentIterable.from(Arrays.asList(groups))
.transform(new Function<AccountGroup, String>() {
@@ -223,6 +264,6 @@ public class SuggestReviewersIT extends AbstractDaemonTest {
return in.getName();
}
}).toArray(String.class);
return accounts.create(name, name + "@example.com", name, groupNames);
return accounts.create(name, name + "@example.com", fullName, groupNames);
}
}

View File

@@ -59,7 +59,8 @@ public class MyIdentitiesScreen extends SettingsScreen {
});
add(deleteIdentity);
if (Gerrit.getConfig().getAuthType() == AuthType.OPENID) {
if (Gerrit.getConfig().getAuthType() == AuthType.OPENID
|| Gerrit.getConfig().getAuthType() == AuthType.OAUTH) {
Button linkIdentity = new Button(Util.C.buttonLinkIdentity());
linkIdentity.addClickHandler(new ClickHandler() {
@Override

View File

@@ -50,7 +50,9 @@ class OAuthLogoutServlet extends HttpLogoutServlet {
protected void doLogout(HttpServletRequest req, HttpServletResponse rsp)
throws IOException {
super.doLogout(req, rsp);
oauthSession.get().logout();
if (req.getSession(false) != null) {
oauthSession.get().logout();
}
}
}

View File

@@ -26,11 +26,14 @@ import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.httpd.CanonicalWebUrl;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.AuthResult;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.servlet.SessionScoped;
import org.apache.commons.codec.binary.Base64;
@@ -52,18 +55,22 @@ class OAuthSession {
private static final SecureRandom randomState = newRandomGenerator();
private final String state;
private final DynamicItem<WebSession> webSession;
private final Provider<IdentifiedUser> identifiedUser;
private final AccountManager accountManager;
private final CanonicalWebUrl urlProvider;
private OAuthServiceProvider serviceProvider;
private OAuthToken token;
private OAuthUserInfo user;
private String redirectToken;
private boolean linkMode;
@Inject
OAuthSession(DynamicItem<WebSession> webSession,
Provider<IdentifiedUser> identifiedUser,
AccountManager accountManager,
CanonicalWebUrl urlProvider) {
this.state = generateRandomState();
this.identifiedUser = identifiedUser;
this.webSession = webSession;
this.accountManager = accountManager;
this.urlProvider = urlProvider;
@@ -79,10 +86,6 @@ class OAuthSession {
boolean login(HttpServletRequest request, HttpServletResponse response,
OAuthServiceProvider oauth) throws IOException {
if (isLoggedIn()) {
return true;
}
log.debug("Login " + this);
if (isOAuthFinal(request)) {
@@ -122,46 +125,19 @@ class OAuthSession {
private void authenticateAndRedirect(HttpServletRequest req,
HttpServletResponse rsp) throws IOException {
com.google.gerrit.server.account.AuthRequest areq =
new com.google.gerrit.server.account.AuthRequest(user.getExternalId());
AuthRequest areq = new AuthRequest(user.getExternalId());
AuthResult arsp;
try {
String claimedIdentifier = user.getClaimedIdentity();
Account.Id actualId = accountManager.lookup(user.getExternalId());
if (!Strings.isNullOrEmpty(claimedIdentifier)) {
Account.Id claimedId = accountManager.lookup(claimedIdentifier);
if (claimedId != null && actualId != null) {
if (claimedId.equals(actualId)) {
// Both link to the same account, that's what we expected.
log.debug("OAuth2: claimed identity equals current id");
} else {
// This is (for now) a fatal error. There are two records
// for what might be the same user.
//
log.error("OAuth accounts disagree over user identity:\n"
+ " Claimed ID: " + claimedId + " is " + claimedIdentifier
+ "\n" + " Delgate ID: " + actualId + " is "
+ user.getExternalId());
rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
} else if (claimedId != null && actualId == null) {
// Claimed account already exists: link to it.
//
log.info("OAuth2: linking claimed identity to {}",
claimedId.toString());
try {
accountManager.link(claimedId, areq);
} catch (OrmException e) {
log.error("Cannot link: " + user.getExternalId()
+ " to user identity:\n"
+ " Claimed ID: " + claimedId + " is " + claimedIdentifier);
rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
if (!authenticateWithIdentityClaimedDuringHandshake(areq, rsp,
claimedIdentifier)) {
return;
}
} else if (linkMode) {
if (!authenticateWithLinkedIdentity(areq, rsp)) {
return;
}
} else {
log.debug("OAuth2: claimed identity is empty");
}
areq.setUserName(user.getUserName());
areq.setEmailAddress(user.getEmailAddress());
@@ -181,6 +157,59 @@ class OAuthSession {
rsp.sendRedirect(rdr.toString());
}
private boolean authenticateWithIdentityClaimedDuringHandshake(
AuthRequest req, HttpServletResponse rsp, String claimedIdentifier)
throws AccountException, IOException {
Account.Id claimedId = accountManager.lookup(claimedIdentifier);
Account.Id actualId = accountManager.lookup(user.getExternalId());
if (claimedId != null && actualId != null) {
if (claimedId.equals(actualId)) {
// Both link to the same account, that's what we expected.
log.debug("OAuth2: claimed identity equals current id");
} else {
// This is (for now) a fatal error. There are two records
// for what might be the same user.
//
log.error("OAuth accounts disagree over user identity:\n"
+ " Claimed ID: " + claimedId + " is " + claimedIdentifier
+ "\n" + " Delgate ID: " + actualId + " is "
+ user.getExternalId());
rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
return false;
}
} else if (claimedId != null && actualId == null) {
// Claimed account already exists: link to it.
//
log.info("OAuth2: linking claimed identity to {}",
claimedId.toString());
try {
accountManager.link(claimedId, req);
} catch (OrmException e) {
log.error("Cannot link: " + user.getExternalId()
+ " to user identity:\n"
+ " Claimed ID: " + claimedId + " is " + claimedIdentifier);
rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
return false;
}
}
return true;
}
private boolean authenticateWithLinkedIdentity(AuthRequest areq,
HttpServletResponse rsp) throws AccountException, IOException {
try {
accountManager.link(identifiedUser.get().getAccountId(), areq);
} catch (OrmException e) {
log.error("Cannot link: " + user.getExternalId()
+ " to user identity: " + identifiedUser.get().getAccountId());
rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
return false;
} finally {
linkMode = false;
}
return true;
}
void logout() {
token = null;
user = null;
@@ -224,4 +253,12 @@ class OAuthSession {
public OAuthServiceProvider getServiceProvider() {
return serviceProvider;
}
public void setLinkMode(boolean linkMode) {
this.linkMode = linkMode;
}
public boolean isLinkMode() {
return linkMode;
}
}

View File

@@ -23,7 +23,6 @@ import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.httpd.HtmlDomUtil;
import com.google.gerrit.httpd.LoginUrlToken;
import com.google.gerrit.httpd.template.SiteHeaderFooter;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -48,7 +47,6 @@ import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@Singleton
/* OAuth web filter uses active OAuth session to perform OAuth requests */
@@ -56,7 +54,6 @@ class OAuthWebFilter implements Filter {
static final String GERRIT_LOGIN = "/login";
private final Provider<String> urlProvider;
private final Provider<CurrentUser> currentUserProvider;
private final Provider<OAuthSession> oauthSessionProvider;
private final DynamicMap<OAuthServiceProvider> oauthServiceProviders;
private final SiteHeaderFooter header;
@@ -64,12 +61,10 @@ class OAuthWebFilter implements Filter {
@Inject
OAuthWebFilter(@CanonicalWebUrl @Nullable Provider<String> urlProvider,
Provider<CurrentUser> currentUserProvider,
DynamicMap<OAuthServiceProvider> oauthServiceProviders,
Provider<OAuthSession> oauthSessionProvider,
SiteHeaderFooter header) {
this.urlProvider = urlProvider;
this.currentUserProvider = currentUserProvider;
this.oauthServiceProviders = oauthServiceProviders;
this.oauthSessionProvider = oauthSessionProvider;
this.header = header;
@@ -88,30 +83,20 @@ class OAuthWebFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpSession httpSession = ((HttpServletRequest) request).getSession(false);
OAuthSession oauthSession = oauthSessionProvider.get();
if (currentUserProvider.get().isIdentifiedUser()) {
if (httpSession != null) {
httpSession.invalidate();
}
chain.doFilter(request, response);
return;
} else {
if (oauthSession.isLoggedIn()) {
oauthSession.logout();
}
}
HttpServletResponse httpResponse = (HttpServletResponse) response;
OAuthSession oauthSession = oauthSessionProvider.get();
if (request.getParameter("link") != null) {
oauthSession.setLinkMode(true);
oauthSession.setServiceProvider(null);
}
String provider = httpRequest.getParameter("provider");
OAuthServiceProvider service = ssoProvider == null
? oauthSession.getServiceProvider()
: ssoProvider;
if ((isGerritLogin(httpRequest)
|| oauthSession.isOAuthFinal(httpRequest))
&& !oauthSession.isLoggedIn()) {
if (isGerritLogin(httpRequest) || oauthSession.isOAuthFinal(httpRequest)) {
if (service == null && Strings.isNullOrEmpty(provider)) {
selectProvider(httpRequest, httpResponse, null);
return;

View File

@@ -175,9 +175,9 @@ class LoginForm extends HttpServlet {
oauthSession.logout();
}
if ((isGerritLogin(req)
|| oauthSession.isOAuthFinal(req))
&& !oauthSession.isLoggedIn()) {
|| oauthSession.isOAuthFinal(req))) {
oauthSession.setServiceProvider(oauthProvider);
oauthSession.setLinkMode(link);
oauthSession.login(req, res, oauthProvider);
}
}
@@ -304,7 +304,7 @@ class LoginForm extends HttpServlet {
oauthServiceProviders.byPlugin(pluginName);
for (Map.Entry<String, Provider<OAuthServiceProvider>> e
: m.entrySet()) {
addProvider(providers, pluginName, e.getKey(),
addProvider(providers, link, pluginName, e.getKey(),
e.getValue().get().getName());
}
}
@@ -327,13 +327,18 @@ class LoginForm extends HttpServlet {
}
}
private static void addProvider(Element form, String pluginName,
String id, String serviceName) {
private static void addProvider(Element form, boolean link,
String pluginName, String id, String serviceName) {
Element div = form.getOwnerDocument().createElement("div");
div.setAttribute("id", id);
Element hyperlink = form.getOwnerDocument().createElement("a");
hyperlink.setAttribute("href", String.format("?id=%s_%s",
StringBuilder u = new StringBuilder(String.format("?id=%s_%s",
pluginName, id));
if (link) {
u.append("&link");
}
hyperlink.setAttribute("href", u.toString());
hyperlink.setTextContent(serviceName +
" (" + pluginName + " plugin)");
div.appendChild(hyperlink);

View File

@@ -27,11 +27,13 @@ import com.google.gerrit.httpd.CanonicalWebUrl;
import com.google.gerrit.httpd.LoginUrlToken;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AuthResult;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.servlet.SessionScoped;
import org.apache.commons.codec.binary.Base64;
@@ -55,19 +57,23 @@ class OAuthSessionOverOpenID {
private static final SecureRandom randomState = newRandomGenerator();
private final String state;
private final DynamicItem<WebSession> webSession;
private final Provider<IdentifiedUser> identifiedUser;
private final AccountManager accountManager;
private final CanonicalWebUrl urlProvider;
private OAuthServiceProvider serviceProvider;
private OAuthToken token;
private OAuthUserInfo user;
private String redirectToken;
private boolean linkMode;
@Inject
OAuthSessionOverOpenID(DynamicItem<WebSession> webSession,
Provider<IdentifiedUser> identifiedUser,
AccountManager accountManager,
CanonicalWebUrl urlProvider) {
this.state = generateRandomState();
this.webSession = webSession;
this.identifiedUser = identifiedUser;
this.accountManager = accountManager;
this.urlProvider = urlProvider;
}
@@ -82,10 +88,6 @@ class OAuthSessionOverOpenID {
boolean login(HttpServletRequest request, HttpServletResponse response,
OAuthServiceProvider oauth) throws IOException {
if (isLoggedIn()) {
return true;
}
log.debug("Login " + this);
if (isOAuthFinal(request)) {
@@ -96,7 +98,6 @@ class OAuthSessionOverOpenID {
log.debug("Login-Retrieve-User " + this);
token = oauth.getAccessToken(new OAuthVerifier(request.getParameter("code")));
user = oauth.getUserInfo(token);
if (isLoggedIn()) {
@@ -124,6 +125,7 @@ class OAuthSessionOverOpenID {
try {
String claimedIdentifier = user.getClaimedIdentity();
Account.Id actualId = accountManager.lookup(user.getExternalId());
// Use case 1: claimed identity was provided during handshake phase
if (!Strings.isNullOrEmpty(claimedIdentifier)) {
Account.Id claimedId = accountManager.lookup(claimedIdentifier);
if (claimedId != null && actualId != null) {
@@ -153,6 +155,18 @@ class OAuthSessionOverOpenID {
return;
}
}
} else if (linkMode) {
// Use case 2: link mode activated from the UI
try {
accountManager.link(identifiedUser.get().getAccountId(), areq);
} catch (OrmException e) {
log.error("Cannot link: " + user.getExternalId()
+ " to user identity: " + identifiedUser.get().getAccountId());
rsp.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
} finally {
linkMode = false;
}
}
areq.setUserName(user.getUserName());
areq.setEmailAddress(user.getEmailAddress());
@@ -213,4 +227,12 @@ class OAuthSessionOverOpenID {
public OAuthServiceProvider getServiceProvider() {
return serviceProvider;
}
public void setLinkMode(boolean linkMode) {
this.linkMode = linkMode;
}
public boolean isLinkMode() {
return linkMode;
}
}

View File

@@ -17,7 +17,6 @@ package com.google.gerrit.httpd.auth.openid;
import com.google.common.collect.Iterables;
import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.server.CurrentUser;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -34,7 +33,6 @@ import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/** OAuth web filter uses active OAuth session to perform OAuth requests */
@@ -42,16 +40,13 @@ import javax.servlet.http.HttpSession;
class OAuthWebFilterOverOpenID implements Filter {
static final String GERRIT_LOGIN = "/login";
private final Provider<CurrentUser> currentUserProvider;
private final Provider<OAuthSessionOverOpenID> oauthSessionProvider;
private final DynamicMap<OAuthServiceProvider> oauthServiceProviders;
private OAuthServiceProvider ssoProvider;
@Inject
OAuthWebFilterOverOpenID(Provider<CurrentUser> currentUserProvider,
DynamicMap<OAuthServiceProvider> oauthServiceProviders,
OAuthWebFilterOverOpenID(DynamicMap<OAuthServiceProvider> oauthServiceProviders,
Provider<OAuthSessionOverOpenID> oauthSessionProvider) {
this.currentUserProvider = currentUserProvider;
this.oauthServiceProviders = oauthServiceProviders;
this.oauthSessionProvider = oauthSessionProvider;
}
@@ -69,15 +64,6 @@ class OAuthWebFilterOverOpenID implements Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpSession httpSession = ((HttpServletRequest) request).getSession(false);
if (currentUserProvider.get().isIdentifiedUser()) {
if (httpSession != null) {
httpSession.invalidate();
}
chain.doFilter(request, response);
return;
}
HttpServletResponse httpResponse = (HttpServletResponse) response;
OAuthSessionOverOpenID oauthSession = oauthSessionProvider.get();
@@ -85,9 +71,7 @@ class OAuthWebFilterOverOpenID implements Filter {
? oauthSession.getServiceProvider()
: ssoProvider;
if ((isGerritLogin(httpRequest)
|| oauthSession.isOAuthFinal(httpRequest))
&& !oauthSession.isLoggedIn()) {
if (isGerritLogin(httpRequest) || oauthSession.isOAuthFinal(httpRequest)) {
if (service == null) {
throw new IllegalStateException("service is unknown");
}

View File

@@ -14,22 +14,48 @@
package com.google.gerrit.server.change;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.server.AccountExternalIdAccess;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.config.ConfigUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.analysis.util.CharArraySet;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.IntField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.PrefixQuery;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.RAMDirectory;
import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@@ -44,34 +70,115 @@ import java.util.concurrent.TimeUnit;
public class ReviewerSuggestionCache {
private static final Logger log = LoggerFactory
.getLogger(ReviewerSuggestionCache.class);
private final LoadingCache<Boolean, List<Account>> cache;
private static final String ID = "id";
private static final String NAME = "name";
private static final String EMAIL = "email";
private static final String USERNAME = "username";
private static final String[] ALL = {ID, NAME, EMAIL, USERNAME};
private final LoadingCache<Boolean, IndexSearcher> cache;
private final Provider<ReviewDb> db;
@Inject
ReviewerSuggestionCache(final Provider<ReviewDb> dbProvider) {
ReviewerSuggestionCache(Provider<ReviewDb> db,
@GerritServerConfig Config cfg) {
this.db = db;
long expiration = ConfigUtil.getTimeUnit(cfg,
"suggest", null, "fullTextSearchRefresh",
TimeUnit.HOURS.toMillis(1),
TimeUnit.MILLISECONDS);
this.cache =
CacheBuilder.newBuilder().maximumSize(1)
.expireAfterWrite(30, TimeUnit.SECONDS)
.build(new CacheLoader<Boolean, List<Account>>() {
.expireAfterWrite(expiration, TimeUnit.MILLISECONDS)
.build(new CacheLoader<Boolean, IndexSearcher>() {
@Override
public List<Account> load(Boolean key) throws Exception {
return ImmutableList.copyOf(Iterables.filter(
dbProvider.get().accounts().all(),
new Predicate<Account>() {
@Override
public boolean apply(Account in) {
return in.isActive();
}
}));
public IndexSearcher load(Boolean key) throws Exception {
return index();
}
});
}
List<Account> get() {
List<AccountInfo> search(String query, int n) throws IOException {
IndexSearcher searcher = get();
if (searcher == null) {
return Collections.emptyList();
}
List<String> segments = Splitter.on(' ').omitEmptyStrings().splitToList(
query.toLowerCase());
BooleanQuery q = new BooleanQuery();
for (String field : ALL) {
BooleanQuery and = new BooleanQuery();
for (String s : segments) {
and.add(new PrefixQuery(new Term(field, s)), Occur.MUST);
}
q.add(and, Occur.SHOULD);
}
TopDocs results = searcher.search(q, n);
ScoreDoc[] hits = results.scoreDocs;
List<AccountInfo> result = new LinkedList<>();
for (ScoreDoc h : hits) {
Document doc = searcher.doc(h.doc);
AccountInfo info = new AccountInfo(
doc.getField(ID).numericValue().intValue());
info.name = doc.get(NAME);
info.email = doc.get(EMAIL);
info.username = doc.get(USERNAME);
result.add(info);
}
return result;
}
private IndexSearcher get() {
try {
return cache.get(true);
} catch (ExecutionException e) {
log.warn("Cannot fetch reviewers from cache", e);
return Collections.emptyList();
return null;
}
}
private IndexSearcher index() throws IOException, OrmException {
RAMDirectory idx = new RAMDirectory();
IndexWriterConfig config = new IndexWriterConfig(
new StandardAnalyzer(CharArraySet.EMPTY_SET));
config.setOpenMode(OpenMode.CREATE);
try (IndexWriter writer = new IndexWriter(idx, config)) {
for (Account a : db.get().accounts().all()) {
if (a.isActive()) {
addAccount(writer, a);
}
}
}
return new IndexSearcher(DirectoryReader.open(idx));
}
private void addAccount(IndexWriter writer, Account a)
throws IOException, OrmException {
Document doc = new Document();
doc.add(new IntField(ID, a.getId().get(), Store.YES));
if (a.getFullName() != null) {
doc.add(new TextField(NAME, a.getFullName(), Store.YES));
}
if (a.getPreferredEmail() != null) {
doc.add(new StringField(EMAIL, a.getPreferredEmail().toLowerCase(),
Store.YES));
doc.add(new TextField(EMAIL, a.getPreferredEmail(), Store.YES));
}
AccountExternalIdAccess extIdAccess = db.get().accountExternalIds();
String username = AccountState.getUserName(
extIdAccess.byAccount(a.getId()).toList());
if (username != null) {
doc.add(new StringField(USERNAME, username, Store.YES));
}
writer.addDocument(doc);
}
}

View File

@@ -55,6 +55,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -279,41 +280,20 @@ public class SuggestReviewers implements RestReadView<ChangeResource> {
}
private List<AccountInfo> suggestAccountFullTextSearch(
VisibilityControl visibilityControl) throws OrmException {
String str = query.toLowerCase();
Map<Account.Id, AccountInfo> accountMap = new LinkedHashMap<>();
List<Account> fullNameMatches = new ArrayList<>(fullTextMaxMatches);
List<Account> emailMatches = new ArrayList<>(fullTextMaxMatches);
VisibilityControl visibilityControl) throws IOException, OrmException {
List<AccountInfo> results = reviewerSuggestionCache.search(
query, fullTextMaxMatches);
for (Account a : reviewerSuggestionCache.get()) {
if (a.getFullName() != null
&& a.getFullName().toLowerCase().contains(str)) {
fullNameMatches.add(a);
} else if (a.getPreferredEmail() != null
&& emailMatches.size() < fullTextMaxMatches
&& a.getPreferredEmail().toLowerCase().contains(str)) {
emailMatches.add(a);
}
if (fullNameMatches.size() >= fullTextMaxMatches) {
break;
Iterator<AccountInfo> it = results.iterator();
while (it.hasNext()) {
Account.Id accountId = new Account.Id(it.next()._accountId);
if (!(visibilityControl.isVisibleTo(accountId)
&& accountControl.canSee(accountId))) {
it.remove();
}
}
for (Account a : fullNameMatches) {
addSuggestion(accountMap, a.getId(), visibilityControl);
if (accountMap.size() >= limit) {
break;
}
}
if (accountMap.size() < limit) {
for (Account a : emailMatches) {
addSuggestion(accountMap, a.getId(), visibilityControl);
if (accountMap.size() >= limit) {
break;
}
}
}
accountLoader.fill();
return Lists.newArrayList(accountMap.values());
return results;
}
private boolean addSuggestion(Map<Account.Id, AccountInfo> map,

View File

@@ -23,7 +23,7 @@ import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
@RequiresCapability(GlobalCapability.VIEW_PLUGINS)
@CommandMetaData(name = "ls", description = "List the installed plugins",
runsAt = MASTER_OR_SLAVE)
final class PluginLsCommand extends SshCommand {

View File

@@ -1,13 +1,13 @@
include_defs('//lib/maven.defs')
REPO = GERRIT # Leave here even if set to MAVEN_CENTRAL.
VERS = '3.7.0.201502260915-r.63-gf1a15f7'
REPO = MAVEN_CENTRAL # Leave here even if set to MAVEN_CENTRAL.
VERS = '4.0.0.201505050340-m2'
maven_jar(
name = 'jgit',
id = 'org.eclipse.jgit:org.eclipse.jgit:' + VERS,
bin_sha1 = 'f0690b06d4270cc823b03ca88fb996897edbc410',
src_sha1 = 'c0b8232d4c5e8422198b35a90409c9826af68fa3',
bin_sha1 = '1cc3120d39ed2b55584e631634e65c5d2e6c1cf7',
src_sha1 = '425f578cc9d5ccb8f3b050a5ab1e2d7a0becb25d',
license = 'jgit',
repository = REPO,
unsign = True,
@@ -22,7 +22,7 @@ maven_jar(
maven_jar(
name = 'jgit-servlet',
id = 'org.eclipse.jgit:org.eclipse.jgit.http.server:' + VERS,
sha1 = '1284d550981a037ffe92441faf5d16cdfc09396d',
sha1 = '2a9f55d1d92afef795542b995db6ab261007857f',
license = 'jgit',
repository = REPO,
deps = [':jgit'],
@@ -36,7 +36,7 @@ maven_jar(
maven_jar(
name = 'jgit-archive',
id = 'org.eclipse.jgit:org.eclipse.jgit.archive:' + VERS,
sha1 = '6d96c9c27cb85c6db165b160fb0e8038b66b764a',
sha1 = 'ee3954753067818f8f734981a01c13ac33425f2c',
license = 'jgit',
repository = REPO,
deps = [':jgit',
@@ -53,7 +53,7 @@ maven_jar(
maven_jar(
name = 'junit',
id = 'org.eclipse.jgit:org.eclipse.jgit.junit:' + VERS,
sha1 = '48e25624fda973f2e9b7fa70a5204ee7a72e1bb3',
sha1 = '6cc19f8f0a1791e26d4225625ecba6a31d9b830e',
license = 'DO_NOT_DISTRIBUTE',
repository = REPO,
unsign = True,