Allow to link user identity to another OAuth provider

GitHub-Bug: https://github.com/davido/gerrit-oauth-provider/issues/12
Change-Id: I9507d15983cd021ba883afbdf4e526091d55c517
This commit is contained in:
David Ostrovsky
2015-04-18 22:10:16 +02:00
parent 8ca666bdcc
commit 6269edfc68
3 changed files with 84 additions and 37 deletions

View File

@@ -59,7 +59,8 @@ public class MyIdentitiesScreen extends SettingsScreen {
}); });
add(deleteIdentity); 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()); Button linkIdentity = new Button(Util.C.buttonLinkIdentity());
linkIdentity.addClickHandler(new ClickHandler() { linkIdentity.addClickHandler(new ClickHandler() {
@Override @Override

View File

@@ -26,11 +26,14 @@ import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.httpd.CanonicalWebUrl; import com.google.gerrit.httpd.CanonicalWebUrl;
import com.google.gerrit.httpd.WebSession; import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.reviewdb.client.Account; 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.AccountException;
import com.google.gerrit.server.account.AccountManager; import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.AuthResult; import com.google.gerrit.server.account.AuthResult;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.servlet.SessionScoped; import com.google.inject.servlet.SessionScoped;
import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64;
@@ -52,18 +55,22 @@ class OAuthSession {
private static final SecureRandom randomState = newRandomGenerator(); private static final SecureRandom randomState = newRandomGenerator();
private final String state; private final String state;
private final DynamicItem<WebSession> webSession; private final DynamicItem<WebSession> webSession;
private final Provider<IdentifiedUser> identifiedUser;
private final AccountManager accountManager; private final AccountManager accountManager;
private final CanonicalWebUrl urlProvider; private final CanonicalWebUrl urlProvider;
private OAuthServiceProvider serviceProvider; private OAuthServiceProvider serviceProvider;
private OAuthToken token; private OAuthToken token;
private OAuthUserInfo user; private OAuthUserInfo user;
private String redirectToken; private String redirectToken;
private boolean linkMode;
@Inject @Inject
OAuthSession(DynamicItem<WebSession> webSession, OAuthSession(DynamicItem<WebSession> webSession,
Provider<IdentifiedUser> identifiedUser,
AccountManager accountManager, AccountManager accountManager,
CanonicalWebUrl urlProvider) { CanonicalWebUrl urlProvider) {
this.state = generateRandomState(); this.state = generateRandomState();
this.identifiedUser = identifiedUser;
this.webSession = webSession; this.webSession = webSession;
this.accountManager = accountManager; this.accountManager = accountManager;
this.urlProvider = urlProvider; this.urlProvider = urlProvider;
@@ -122,47 +129,20 @@ class OAuthSession {
private void authenticateAndRedirect(HttpServletRequest req, private void authenticateAndRedirect(HttpServletRequest req,
HttpServletResponse rsp) throws IOException { HttpServletResponse rsp) throws IOException {
com.google.gerrit.server.account.AuthRequest areq = AuthRequest areq = new AuthRequest(user.getExternalId());
new com.google.gerrit.server.account.AuthRequest(user.getExternalId());
AuthResult arsp; AuthResult arsp;
try { try {
String claimedIdentifier = user.getClaimedIdentity(); String claimedIdentifier = user.getClaimedIdentity();
Account.Id actualId = accountManager.lookup(user.getExternalId());
if (!Strings.isNullOrEmpty(claimedIdentifier)) { if (!Strings.isNullOrEmpty(claimedIdentifier)) {
Account.Id claimedId = accountManager.lookup(claimedIdentifier); if (!authenticateWithIdentityClaimedDuringHandshake(areq, rsp,
if (claimedId != null && actualId != null) { claimedIdentifier)) {
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; return;
} }
} else if (claimedId != null && actualId == null) { } else if (linkMode) {
// Claimed account already exists: link to it. if (!authenticateWithLinkedIdentity(areq, rsp)) {
//
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; 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());
@@ -181,6 +161,59 @@ class OAuthSession {
rsp.sendRedirect(rdr.toString()); 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() { void logout() {
token = null; token = null;
user = null; user = null;
@@ -224,4 +257,12 @@ class OAuthSession {
public OAuthServiceProvider getServiceProvider() { public OAuthServiceProvider getServiceProvider() {
return serviceProvider; return serviceProvider;
} }
public void setLinkMode(boolean linkMode) {
this.linkMode = linkMode;
}
public boolean isLinkMode() {
return linkMode;
}
} }

View File

@@ -90,7 +90,12 @@ class OAuthWebFilter implements Filter {
HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpSession httpSession = ((HttpServletRequest) request).getSession(false); HttpSession httpSession = ((HttpServletRequest) request).getSession(false);
OAuthSession oauthSession = oauthSessionProvider.get(); OAuthSession oauthSession = oauthSessionProvider.get();
if (currentUserProvider.get().isIdentifiedUser()) { boolean link = request.getParameter("link") != null;
if (link) {
oauthSession.setLinkMode(link);
}
if (!oauthSession.isLinkMode()
&& currentUserProvider.get().isIdentifiedUser()) {
if (httpSession != null) { if (httpSession != null) {
httpSession.invalidate(); httpSession.invalidate();
} }