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:
@@ -178,6 +178,7 @@ class LoginForm extends HttpServlet {
|
|||||||
|| oauthSession.isOAuthFinal(req))
|
|| oauthSession.isOAuthFinal(req))
|
||||||
&& !oauthSession.isLoggedIn()) {
|
&& !oauthSession.isLoggedIn()) {
|
||||||
oauthSession.setServiceProvider(oauthProvider);
|
oauthSession.setServiceProvider(oauthProvider);
|
||||||
|
oauthSession.setLinkMode(link);
|
||||||
oauthSession.login(req, res, oauthProvider);
|
oauthSession.login(req, res, oauthProvider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -303,7 +304,7 @@ class LoginForm extends HttpServlet {
|
|||||||
oauthServiceProviders.byPlugin(pluginName);
|
oauthServiceProviders.byPlugin(pluginName);
|
||||||
for (Map.Entry<String, Provider<OAuthServiceProvider>> e
|
for (Map.Entry<String, Provider<OAuthServiceProvider>> e
|
||||||
: m.entrySet()) {
|
: m.entrySet()) {
|
||||||
addProvider(providers, pluginName, e.getKey(),
|
addProvider(providers, link, pluginName, e.getKey(),
|
||||||
e.getValue().get().getName());
|
e.getValue().get().getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -326,13 +327,18 @@ class LoginForm extends HttpServlet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addProvider(Element form, String pluginName,
|
private static void addProvider(Element form, boolean link,
|
||||||
String id, String serviceName) {
|
String pluginName, String id, String serviceName) {
|
||||||
Element div = form.getOwnerDocument().createElement("div");
|
Element div = form.getOwnerDocument().createElement("div");
|
||||||
div.setAttribute("id", id);
|
div.setAttribute("id", id);
|
||||||
Element hyperlink = form.getOwnerDocument().createElement("a");
|
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));
|
pluginName, id));
|
||||||
|
if (link) {
|
||||||
|
u.append("&link");
|
||||||
|
}
|
||||||
|
hyperlink.setAttribute("href", u.toString());
|
||||||
|
|
||||||
hyperlink.setTextContent(serviceName +
|
hyperlink.setTextContent(serviceName +
|
||||||
" (" + pluginName + " plugin)");
|
" (" + pluginName + " plugin)");
|
||||||
div.appendChild(hyperlink);
|
div.appendChild(hyperlink);
|
||||||
|
|||||||
@@ -27,11 +27,13 @@ import com.google.gerrit.httpd.CanonicalWebUrl;
|
|||||||
import com.google.gerrit.httpd.LoginUrlToken;
|
import com.google.gerrit.httpd.LoginUrlToken;
|
||||||
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.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;
|
||||||
@@ -55,19 +57,23 @@ class OAuthSessionOverOpenID {
|
|||||||
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
|
||||||
OAuthSessionOverOpenID(DynamicItem<WebSession> webSession,
|
OAuthSessionOverOpenID(DynamicItem<WebSession> webSession,
|
||||||
|
Provider<IdentifiedUser> identifiedUser,
|
||||||
AccountManager accountManager,
|
AccountManager accountManager,
|
||||||
CanonicalWebUrl urlProvider) {
|
CanonicalWebUrl urlProvider) {
|
||||||
this.state = generateRandomState();
|
this.state = generateRandomState();
|
||||||
this.webSession = webSession;
|
this.webSession = webSession;
|
||||||
|
this.identifiedUser = identifiedUser;
|
||||||
this.accountManager = accountManager;
|
this.accountManager = accountManager;
|
||||||
this.urlProvider = urlProvider;
|
this.urlProvider = urlProvider;
|
||||||
}
|
}
|
||||||
@@ -96,7 +102,6 @@ class OAuthSessionOverOpenID {
|
|||||||
|
|
||||||
log.debug("Login-Retrieve-User " + this);
|
log.debug("Login-Retrieve-User " + this);
|
||||||
token = oauth.getAccessToken(new OAuthVerifier(request.getParameter("code")));
|
token = oauth.getAccessToken(new OAuthVerifier(request.getParameter("code")));
|
||||||
|
|
||||||
user = oauth.getUserInfo(token);
|
user = oauth.getUserInfo(token);
|
||||||
|
|
||||||
if (isLoggedIn()) {
|
if (isLoggedIn()) {
|
||||||
@@ -124,6 +129,7 @@ class OAuthSessionOverOpenID {
|
|||||||
try {
|
try {
|
||||||
String claimedIdentifier = user.getClaimedIdentity();
|
String claimedIdentifier = user.getClaimedIdentity();
|
||||||
Account.Id actualId = accountManager.lookup(user.getExternalId());
|
Account.Id actualId = accountManager.lookup(user.getExternalId());
|
||||||
|
// Use case 1: claimed identity was provided during handshake phase
|
||||||
if (!Strings.isNullOrEmpty(claimedIdentifier)) {
|
if (!Strings.isNullOrEmpty(claimedIdentifier)) {
|
||||||
Account.Id claimedId = accountManager.lookup(claimedIdentifier);
|
Account.Id claimedId = accountManager.lookup(claimedIdentifier);
|
||||||
if (claimedId != null && actualId != null) {
|
if (claimedId != null && actualId != null) {
|
||||||
@@ -153,6 +159,18 @@ class OAuthSessionOverOpenID {
|
|||||||
return;
|
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.setUserName(user.getUserName());
|
||||||
areq.setEmailAddress(user.getEmailAddress());
|
areq.setEmailAddress(user.getEmailAddress());
|
||||||
@@ -213,4 +231,12 @@ class OAuthSessionOverOpenID {
|
|||||||
public OAuthServiceProvider getServiceProvider() {
|
public OAuthServiceProvider getServiceProvider() {
|
||||||
return serviceProvider;
|
return serviceProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setLinkMode(boolean linkMode) {
|
||||||
|
this.linkMode = linkMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLinkMode() {
|
||||||
|
return linkMode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,9 @@ class OAuthWebFilterOverOpenID implements Filter {
|
|||||||
FilterChain chain) throws IOException, ServletException {
|
FilterChain chain) throws IOException, ServletException {
|
||||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||||
HttpSession httpSession = ((HttpServletRequest) request).getSession(false);
|
HttpSession httpSession = ((HttpServletRequest) request).getSession(false);
|
||||||
if (currentUserProvider.get().isIdentifiedUser()) {
|
OAuthSessionOverOpenID oauthSession = oauthSessionProvider.get();
|
||||||
|
if (!oauthSession.isLinkMode()
|
||||||
|
&& currentUserProvider.get().isIdentifiedUser()) {
|
||||||
if (httpSession != null) {
|
if (httpSession != null) {
|
||||||
httpSession.invalidate();
|
httpSession.invalidate();
|
||||||
}
|
}
|
||||||
@@ -80,7 +82,6 @@ class OAuthWebFilterOverOpenID implements Filter {
|
|||||||
|
|
||||||
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||||
|
|
||||||
OAuthSessionOverOpenID oauthSession = oauthSessionProvider.get();
|
|
||||||
OAuthServiceProvider service = ssoProvider == null
|
OAuthServiceProvider service = ssoProvider == null
|
||||||
? oauthSession.getServiceProvider()
|
? oauthSession.getServiceProvider()
|
||||||
: ssoProvider;
|
: ssoProvider;
|
||||||
|
|||||||
Reference in New Issue
Block a user