Hybrid OpenID/OAuth: Allow to link identity accross protocols

This change support all linking directions:
* From OpenID to OAuth
* From OAuth to OpenID
* From OAuth to OAuth

TEST PLAN:

1. Set up vanilla Gerrit site
2. Assign auth scheme to OpenID
3. Install gerrit-oauth-provider plugin
4. Configure GitHub or Google provider (or both)
5. Sign in with source identity
6. Click User => Settings => Identities => Link Another Identity
7. Select target identity from the login form
8. Confirm that the target identity is linked to the source identity

GitHub-Bug: https://github.com/davido/gerrit-oauth-provider/issues/12
Change-Id: I06e5cfc2ad1dde81050b951c0b7f602461af7992
This commit is contained in:
David Ostrovsky
2015-04-18 22:11:31 +02:00
parent e6782847f8
commit 3ae7ec043f
3 changed files with 40 additions and 7 deletions

View File

@@ -178,6 +178,7 @@ class LoginForm extends HttpServlet {
|| oauthSession.isOAuthFinal(req))
&& !oauthSession.isLoggedIn()) {
oauthSession.setServiceProvider(oauthProvider);
oauthSession.setLinkMode(link);
oauthSession.login(req, res, oauthProvider);
}
}
@@ -303,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());
}
}
@@ -326,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;
}
@@ -96,7 +102,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 +129,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 +159,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 +231,12 @@ class OAuthSessionOverOpenID {
public OAuthServiceProvider getServiceProvider() {
return serviceProvider;
}
public void setLinkMode(boolean linkMode) {
this.linkMode = linkMode;
}
public boolean isLinkMode() {
return linkMode;
}
}

View File

@@ -70,7 +70,9 @@ class OAuthWebFilterOverOpenID implements Filter {
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpSession httpSession = ((HttpServletRequest) request).getSession(false);
if (currentUserProvider.get().isIdentifiedUser()) {
OAuthSessionOverOpenID oauthSession = oauthSessionProvider.get();
if (!oauthSession.isLinkMode()
&& currentUserProvider.get().isIdentifiedUser()) {
if (httpSession != null) {
httpSession.invalidate();
}
@@ -80,7 +82,6 @@ class OAuthWebFilterOverOpenID implements Filter {
HttpServletResponse httpResponse = (HttpServletResponse) response;
OAuthSessionOverOpenID oauthSession = oauthSessionProvider.get();
OAuthServiceProvider service = ssoProvider == null
? oauthSession.getServiceProvider()
: ssoProvider;