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:
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user