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:
		
							
								
								
									
										31
									
								
								ReleaseNotes/ReleaseNotes-2.10.2.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								ReleaseNotes/ReleaseNotes-2.10.2.txt
									
									
									
									
									
										Normal 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.
 | 
				
			||||||
@@ -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]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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',
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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());
 | 
				
			||||||
 | 
					    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;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        log.debug("OAuth2: claimed identity is empty");
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      areq.setUserName(user.getUserName());
 | 
					      areq.setUserName(user.getUserName());
 | 
				
			||||||
      areq.setEmailAddress(user.getEmailAddress());
 | 
					      areq.setEmailAddress(user.getEmailAddress());
 | 
				
			||||||
      areq.setDisplayName(user.getDisplayName());
 | 
					      areq.setDisplayName(user.getDisplayName());
 | 
				
			||||||
    AuthResult arsp;
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      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);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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)) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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;
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user