Merge branch 'stable-2.10' into stable-2.11

* stable-2.10:
  Update version to 2.10.2
  Release notes for Gerrit 2.10.2
  Do not return 403 when clicking on Gitweb breadcrumb
  Add log messages to troubleshoot OAuth/OpenID linking
  Remove unused OAuthToken in authorisation URL
  OnlineReindexer: log the success/failure numbers on exit
  Update replication plugin
  OAuth: Allow to link claimed identity to existing accounts
  OAuth: Allow to change username

Change-Id: Ia9fc371b9f957c8e0fc3e215084baa3d31dadd41
This commit is contained in:
David Pursehouse
2015-03-29 20:49:13 +01:00
9 changed files with 94 additions and 22 deletions

View File

@@ -0,0 +1,31 @@
Release notes for Gerrit 2.10.2
===============================
There are no schema changes from link:ReleaseNotes-2.10.1.html[2.10.1].
Download:
link:https://gerrit-releases.storage.googleapis.com/gerrit-2.10.2.war[
https://gerrit-releases.storage.googleapis.com/gerrit-2.10.2.war]
Bug Fixes
---------
* Work around MyersDiff infinite loop in PatchListLoader. If the MyersDiff diff
doesn't finish within 5 seconds, interrupt it and fall back to a different diff
algorithm. From the user perspective, the only difference when the infinite
loop is detected is that the files in the commit will not be compared in-depth,
which will result in bigger edit regions.
Secondary Index
---------------
* Online reindexing: log the number of done/failed changes in the error_log.
Administrators can use the logged information to decide whether to activate the
new index version or not.
Gitweb
------
* Do not return `Forbidden` when clicking on Gitweb breadcrumb. Now when the
user clicks on the parent folder, redirect to Gerrit projects list screen with
the parent folder path as the filter.

View File

@@ -9,6 +9,7 @@ Version 2.11.x
[[2_10]] [[2_10]]
Version 2.10.x Version 2.10.x
-------------- --------------
* link:ReleaseNotes-2.10.2.html[2.10.2]
* link:ReleaseNotes-2.10.1.html[2.10.1] * link:ReleaseNotes-2.10.1.html[2.10.1]
* link:ReleaseNotes-2.10.html[2.10] * link:ReleaseNotes-2.10.html[2.10]

View File

@@ -22,30 +22,21 @@ import java.io.IOException;
@ExtensionPoint @ExtensionPoint
public interface OAuthServiceProvider { public interface OAuthServiceProvider {
/**
* Retrieve the request token.
*
* @return request token
*/
OAuthToken getRequestToken();
/** /**
* Returns the URL where you should redirect your users to authenticate * Returns the URL where you should redirect your users to authenticate
* your application. * your application.
* *
* @param requestToken the request token you need to authorize * @return the OAuth service URL to redirect your users for authentication
* @return the URL where you should redirect your users
*/ */
String getAuthorizationUrl(OAuthToken requestToken); String getAuthorizationUrl();
/** /**
* Retrieve the access token * Retrieve the access token
* *
* @param requestToken request token (obtained previously)
* @param verifier verifier code * @param verifier verifier code
* @return access token * @return access token
*/ */
OAuthToken getAccessToken(OAuthToken requestToken, OAuthVerifier verifier); OAuthToken getAccessToken(OAuthVerifier verifier);
/** /**
* After establishing of secure communication channel, this method supossed to * After establishing of secure communication channel, this method supossed to

View File

@@ -20,15 +20,18 @@ public class OAuthUserInfo {
private final String userName; private final String userName;
private final String emailAddress; private final String emailAddress;
private final String displayName; private final String displayName;
private final String claimedIdentity;
public OAuthUserInfo(String externalId, public OAuthUserInfo(String externalId,
String userName, String userName,
String emailAddress, String emailAddress,
String displayName) { String displayName,
String claimedIdentity) {
this.externalId = externalId; this.externalId = externalId;
this.userName = userName; this.userName = userName;
this.emailAddress = emailAddress; this.emailAddress = emailAddress;
this.displayName = displayName; this.displayName = displayName;
this.claimedIdentity = claimedIdentity;
} }
public String getExternalId() { public String getExternalId() {
@@ -46,4 +49,8 @@ public class OAuthUserInfo {
public String getDisplayName() { public String getDisplayName() {
return displayName; return displayName;
} }
public String getClaimedIdentity() {
return claimedIdentity;
}
} }

View File

@@ -79,7 +79,9 @@ public class OnlineReindexer {
SiteIndexer.Result result = SiteIndexer.Result result =
batchIndexer.indexAll(index, projectCache.all()); batchIndexer.indexAll(index, projectCache.all());
if (!result.success()) { if (!result.success()) {
log.error("Online reindex of schema version {} failed", version(index)); log.error("Online reindex of schema version {} failed. Successfully"
+ " indexed {} changes, failed to index {} changes",
version(index), result.doneCount(), result.failedCount());
return; return;
} }

View File

@@ -11,9 +11,11 @@ java_library(
'//gerrit-common:annotations', '//gerrit-common:annotations',
'//gerrit-extension-api:api', '//gerrit-extension-api:api',
'//gerrit-httpd:httpd', '//gerrit-httpd:httpd',
'//gerrit-reviewdb:server',
'//gerrit-server:server', '//gerrit-server:server',
'//lib:gson', '//lib:gson',
'//lib:guava', '//lib:guava',
'//lib:gwtorm',
'//lib/commons:codec', '//lib/commons:codec',
'//lib/guice:guice', '//lib/guice:guice',
'//lib/guice:guice-servlet', '//lib/guice:guice-servlet',

View File

@@ -23,9 +23,11 @@ import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo;
import com.google.gerrit.extensions.auth.oauth.OAuthVerifier; import com.google.gerrit.extensions.auth.oauth.OAuthVerifier;
import com.google.gerrit.extensions.registration.DynamicItem; import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.httpd.WebSession; import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountException; import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager; import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AuthResult; import com.google.gerrit.server.account.AuthResult;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.servlet.SessionScoped; import com.google.inject.servlet.SessionScoped;
@@ -87,8 +89,7 @@ class OAuthSession {
} }
log.debug("Login-Retrieve-User " + this); log.debug("Login-Retrieve-User " + this);
token = oauth.getAccessToken(null, token = oauth.getAccessToken(new OAuthVerifier(request.getParameter("code")));
new OAuthVerifier(request.getParameter("code")));
user = oauth.getUserInfo(token); user = oauth.getUserInfo(token);
@@ -103,7 +104,7 @@ class OAuthSession {
} else { } else {
log.debug("Login-PHASE1 " + this); log.debug("Login-PHASE1 " + this);
redirectUrl = request.getRequestURI(); redirectUrl = request.getRequestURI();
response.sendRedirect(oauth.getAuthorizationUrl(null) + response.sendRedirect(oauth.getAuthorizationUrl() +
"&state=" + state); "&state=" + state);
return false; return false;
} }
@@ -113,11 +114,48 @@ class OAuthSession {
throws IOException { throws IOException {
com.google.gerrit.server.account.AuthRequest areq = com.google.gerrit.server.account.AuthRequest areq =
new com.google.gerrit.server.account.AuthRequest(user.getExternalId()); new com.google.gerrit.server.account.AuthRequest(user.getExternalId());
areq.setUserName(user.getUserName());
areq.setEmailAddress(user.getEmailAddress());
areq.setDisplayName(user.getDisplayName());
AuthResult arsp; AuthResult arsp;
try { 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;
}
}
} else {
log.debug("OAuth2: claimed identity is empty");
}
areq.setUserName(user.getUserName());
areq.setEmailAddress(user.getEmailAddress());
areq.setDisplayName(user.getDisplayName());
arsp = accountManager.authenticate(areq); arsp = accountManager.authenticate(areq);
} catch (AccountException e) { } catch (AccountException e) {
log.error("Unable to authenticate user \"" + user + "\"", e); log.error("Unable to authenticate user \"" + user + "\"", e);

View File

@@ -144,6 +144,7 @@ class OpenIdServiceImpl {
final AuthRequest aReq; final AuthRequest aReq;
try { try {
aReq = manager.authenticate(state.discovered, state.retTo.toString()); aReq = manager.authenticate(state.discovered, state.retTo.toString());
log.debug("OpenID: openid-realm={}", state.contextUrl);
aReq.setRealm(state.contextUrl); aReq.setRealm(state.contextUrl);
if (requestRegistration(aReq)) { if (requestRegistration(aReq)) {

View File

@@ -40,8 +40,7 @@ public class DefaultRealm extends AbstractRealm {
@Override @Override
public boolean allowsEdit(final Account.FieldName field) { public boolean allowsEdit(final Account.FieldName field) {
if (authConfig.getAuthType() == AuthType.HTTP if (authConfig.getAuthType() == AuthType.HTTP) {
|| authConfig.getAuthType() == AuthType.OAUTH) {
switch (field) { switch (field) {
case USER_NAME: case USER_NAME:
return false; return false;