Extract OpenID RPC implementation into its own module
This avoids a dependency on openid4java for users that don't need OpenID support. Change-Id: I8dfce1e32f145aebed5e6c1d01c77872528ab43f
This commit is contained in:
committed by
Shawn O. Pearce
parent
ad3209eebf
commit
fb61967081
@@ -0,0 +1,57 @@
|
||||
// Copyright (C) 2008 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.httpd.auth.openid;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/** Handles the <code>/OpenID</code> URL for web based single-sign-on. */
|
||||
@SuppressWarnings("serial")
|
||||
@Singleton
|
||||
class OpenIdLoginServlet extends HttpServlet {
|
||||
private final OpenIdServiceImpl impl;
|
||||
|
||||
@Inject
|
||||
OpenIdLoginServlet(final OpenIdServiceImpl i) {
|
||||
impl = i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doGet(final HttpServletRequest req, final HttpServletResponse rsp)
|
||||
throws IOException {
|
||||
doPost(req, rsp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doPost(final HttpServletRequest req, final HttpServletResponse rsp)
|
||||
throws IOException {
|
||||
try {
|
||||
rsp.setHeader("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
|
||||
rsp.setHeader("Pragma", "no-cache");
|
||||
rsp.setHeader("Cache-Control", "no-cache, must-revalidate");
|
||||
impl.doAuth(req, rsp);
|
||||
} catch (Exception e) {
|
||||
getServletContext().log("Unexpected error during authentication", e);
|
||||
rsp.reset();
|
||||
rsp.sendError(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (C) 2009 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.httpd.auth.openid;
|
||||
|
||||
import com.google.gerrit.httpd.rpc.RpcServletModule;
|
||||
import com.google.inject.servlet.ServletModule;
|
||||
|
||||
/** Servlets and RPC support related to OpenID authentication. */
|
||||
public class OpenIdModule extends ServletModule {
|
||||
@Override
|
||||
protected void configureServlets() {
|
||||
serve("/" + OpenIdServiceImpl.RETURN_URL).with(OpenIdLoginServlet.class);
|
||||
serve("/" + XrdsServlet.LOCATION).with(XrdsServlet.class);
|
||||
filter("/").through(XrdsFilter.class);
|
||||
|
||||
install(new RpcServletModule(RpcServletModule.PREFIX) {
|
||||
@Override
|
||||
protected void configureServlets() {
|
||||
rpc(OpenIdServiceImpl.class);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,554 @@
|
||||
// Copyright (C) 2009 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.httpd.auth.openid;
|
||||
|
||||
import com.google.gerrit.common.PageLinks;
|
||||
import com.google.gerrit.common.auth.SignInMode;
|
||||
import com.google.gerrit.common.auth.openid.DiscoveryResult;
|
||||
import com.google.gerrit.common.auth.openid.OpenIdProviderPattern;
|
||||
import com.google.gerrit.common.auth.openid.OpenIdService;
|
||||
import com.google.gerrit.common.auth.openid.OpenIdUrls;
|
||||
import com.google.gerrit.httpd.WebSession;
|
||||
import com.google.gerrit.reviewdb.Account;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.UrlEncoded;
|
||||
import com.google.gerrit.server.account.AccountException;
|
||||
import com.google.gerrit.server.account.AccountManager;
|
||||
import com.google.gerrit.server.config.AuthConfig;
|
||||
import com.google.gerrit.server.config.CanonicalWebUrl;
|
||||
import com.google.gerrit.server.config.ConfigUtil;
|
||||
import com.google.gerrit.server.config.GerritServerConfig;
|
||||
import com.google.gwt.user.client.rpc.AsyncCallback;
|
||||
import com.google.gwtorm.client.KeyUtil;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
import org.openid4java.consumer.ConsumerException;
|
||||
import org.openid4java.consumer.ConsumerManager;
|
||||
import org.openid4java.consumer.VerificationResult;
|
||||
import org.openid4java.discovery.DiscoveryException;
|
||||
import org.openid4java.discovery.DiscoveryInformation;
|
||||
import org.openid4java.message.AuthRequest;
|
||||
import org.openid4java.message.Message;
|
||||
import org.openid4java.message.MessageException;
|
||||
import org.openid4java.message.MessageExtension;
|
||||
import org.openid4java.message.ParameterList;
|
||||
import org.openid4java.message.ax.AxMessage;
|
||||
import org.openid4java.message.ax.FetchRequest;
|
||||
import org.openid4java.message.ax.FetchResponse;
|
||||
import org.openid4java.message.pape.PapeMessage;
|
||||
import org.openid4java.message.pape.PapeRequest;
|
||||
import org.openid4java.message.pape.PapeResponse;
|
||||
import org.openid4java.message.sreg.SRegMessage;
|
||||
import org.openid4java.message.sreg.SRegRequest;
|
||||
import org.openid4java.message.sreg.SRegResponse;
|
||||
import org.openid4java.util.HttpClientFactory;
|
||||
import org.openid4java.util.ProxyProperties;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@Singleton
|
||||
class OpenIdServiceImpl implements OpenIdService {
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(OpenIdServiceImpl.class);
|
||||
|
||||
static final String RETURN_URL = "OpenID";
|
||||
|
||||
private static final String P_MODE = "gerrit.mode";
|
||||
private static final String P_TOKEN = "gerrit.token";
|
||||
private static final String P_REMEMBER = "gerrit.remember";
|
||||
private static final String P_CLAIMED = "gerrit.claimed";
|
||||
private static final int LASTID_AGE = 365 * 24 * 60 * 60; // seconds
|
||||
|
||||
private static final String OPENID_MODE = "openid.mode";
|
||||
private static final String OMODE_CANCEL = "cancel";
|
||||
|
||||
private static final String SCHEMA_EMAIL =
|
||||
"http://schema.openid.net/contact/email";
|
||||
private static final String SCHEMA_FIRSTNAME =
|
||||
"http://schema.openid.net/namePerson/first";
|
||||
private static final String SCHEMA_LASTNAME =
|
||||
"http://schema.openid.net/namePerson/last";
|
||||
|
||||
private final Provider<WebSession> webSession;
|
||||
private final Provider<IdentifiedUser> identifiedUser;
|
||||
private final Provider<String> urlProvider;
|
||||
private final AccountManager accountManager;
|
||||
private final ConsumerManager manager;
|
||||
private final List<OpenIdProviderPattern> allowedOpenIDs;
|
||||
|
||||
/** Maximum age, in seconds, before forcing re-authentication of account. */
|
||||
private final int papeMaxAuthAge;
|
||||
|
||||
@Inject
|
||||
OpenIdServiceImpl(final Provider<WebSession> cf,
|
||||
final Provider<IdentifiedUser> iu,
|
||||
@CanonicalWebUrl @Nullable final Provider<String> up,
|
||||
@GerritServerConfig final Config config, final AuthConfig ac,
|
||||
final AccountManager am) throws ConsumerException, MalformedURLException {
|
||||
|
||||
if (config.getString("http", null, "proxy") != null) {
|
||||
final URL proxyUrl = new URL(config.getString("http", null, "proxy"));
|
||||
String username = config.getString("http", null, "proxyUsername");
|
||||
String password = config.getString("http", null, "proxyPassword");
|
||||
|
||||
final String userInfo = proxyUrl.getUserInfo();
|
||||
if (userInfo != null) {
|
||||
int c = userInfo.indexOf(':');
|
||||
if (0 < c) {
|
||||
username = userInfo.substring(0, c);
|
||||
password = userInfo.substring(c + 1);
|
||||
} else {
|
||||
username = userInfo;
|
||||
}
|
||||
}
|
||||
|
||||
final ProxyProperties proxy = new ProxyProperties();
|
||||
proxy.setProxyHostName(proxyUrl.getHost());
|
||||
proxy.setProxyPort(proxyUrl.getPort());
|
||||
proxy.setUserName(username);
|
||||
proxy.setPassword(password);
|
||||
HttpClientFactory.setProxyProperties(proxy);
|
||||
}
|
||||
|
||||
webSession = cf;
|
||||
identifiedUser = iu;
|
||||
urlProvider = up;
|
||||
accountManager = am;
|
||||
manager = new ConsumerManager();
|
||||
allowedOpenIDs = ac.getAllowedOpenIDs();
|
||||
papeMaxAuthAge = (int) ConfigUtil.getTimeUnit(config, //
|
||||
"auth", null, "maxOpenIdSessionAge", -1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void discover(final String openidIdentifier, final SignInMode mode,
|
||||
final boolean remember, final String returnToken,
|
||||
final AsyncCallback<DiscoveryResult> cb) {
|
||||
if (!isAllowedOpenID(openidIdentifier)) {
|
||||
cb.onSuccess(new DiscoveryResult(DiscoveryResult.Status.NOT_ALLOWED));
|
||||
return;
|
||||
}
|
||||
|
||||
final State state;
|
||||
state = init(openidIdentifier, mode, remember, returnToken);
|
||||
if (state == null) {
|
||||
cb.onSuccess(new DiscoveryResult(DiscoveryResult.Status.NO_PROVIDER));
|
||||
return;
|
||||
}
|
||||
|
||||
final AuthRequest aReq;
|
||||
try {
|
||||
aReq = manager.authenticate(state.discovered, state.retTo.toString());
|
||||
aReq.setRealm(state.contextUrl);
|
||||
|
||||
if (requestRegistration(aReq)) {
|
||||
final SRegRequest sregReq = SRegRequest.createFetchRequest();
|
||||
sregReq.addAttribute("fullname", true);
|
||||
sregReq.addAttribute("email", true);
|
||||
aReq.addExtension(sregReq);
|
||||
|
||||
final FetchRequest fetch = FetchRequest.createFetchRequest();
|
||||
fetch.addAttribute("FirstName", SCHEMA_FIRSTNAME, true);
|
||||
fetch.addAttribute("LastName", SCHEMA_LASTNAME, true);
|
||||
fetch.addAttribute("Email", SCHEMA_EMAIL, true);
|
||||
aReq.addExtension(fetch);
|
||||
}
|
||||
|
||||
if (0 <= papeMaxAuthAge) {
|
||||
final PapeRequest pape = PapeRequest.createPapeRequest();
|
||||
pape.setMaxAuthAge(papeMaxAuthAge);
|
||||
aReq.addExtension(pape);
|
||||
}
|
||||
} catch (MessageException e) {
|
||||
log.error("Cannot create OpenID redirect for " + openidIdentifier, e);
|
||||
cb.onSuccess(new DiscoveryResult(DiscoveryResult.Status.ERROR));
|
||||
return;
|
||||
} catch (ConsumerException e) {
|
||||
log.error("Cannot create OpenID redirect for " + openidIdentifier, e);
|
||||
cb.onSuccess(new DiscoveryResult(DiscoveryResult.Status.ERROR));
|
||||
return;
|
||||
}
|
||||
|
||||
cb.onSuccess(new DiscoveryResult(aReq.getDestinationUrl(false), //
|
||||
aReq.getParameterMap()));
|
||||
}
|
||||
|
||||
private boolean requestRegistration(final AuthRequest aReq) {
|
||||
if (AuthRequest.SELECT_ID.equals(aReq.getIdentity())) {
|
||||
// We don't know anything about the identity, as the provider
|
||||
// will offer the user a way to indicate their identity. Skip
|
||||
// any database query operation and assume we must ask for the
|
||||
// registration information, in case the identity is new to us.
|
||||
//
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// We might already have this account on file. Look for it.
|
||||
//
|
||||
try {
|
||||
return accountManager.lookup(aReq.getIdentity()) == null;
|
||||
} catch (AccountException e) {
|
||||
log.warn("Cannot determine if user account exists", e);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/** Called by {@link OpenIdLoginServlet} doGet, doPost */
|
||||
void doAuth(final HttpServletRequest req, final HttpServletResponse rsp)
|
||||
throws Exception {
|
||||
if (OMODE_CANCEL.equals(req.getParameter(OPENID_MODE))) {
|
||||
cancel(req, rsp);
|
||||
return;
|
||||
}
|
||||
|
||||
// Process the authentication response.
|
||||
//
|
||||
final SignInMode mode = signInMode(req);
|
||||
final String openidIdentifier = req.getParameter("openid.identity");
|
||||
final String claimedIdentifier = req.getParameter(P_CLAIMED);
|
||||
final String returnToken = req.getParameter(P_TOKEN);
|
||||
final boolean remember = "1".equals(req.getParameter(P_REMEMBER));
|
||||
final String rediscoverIdentifier =
|
||||
claimedIdentifier != null ? claimedIdentifier : openidIdentifier;
|
||||
final State state;
|
||||
|
||||
if (!isAllowedOpenID(rediscoverIdentifier)
|
||||
|| !isAllowedOpenID(openidIdentifier)
|
||||
|| (claimedIdentifier != null && !isAllowedOpenID(claimedIdentifier))) {
|
||||
cancelWithError(req, rsp, "Provider not allowed");
|
||||
return;
|
||||
}
|
||||
|
||||
state = init(rediscoverIdentifier, mode, remember, returnToken);
|
||||
if (state == null) {
|
||||
// Re-discovery must have failed, we can't run a login.
|
||||
//
|
||||
cancel(req, rsp);
|
||||
return;
|
||||
}
|
||||
|
||||
final String returnTo = req.getParameter("openid.return_to");
|
||||
if (returnTo != null && returnTo.contains("openid.rpnonce=")) {
|
||||
// Some providers (claimid.com) seem to embed these request
|
||||
// parameters into our return_to URL, and then give us them
|
||||
// in the return_to request parameter. But not all.
|
||||
//
|
||||
state.retTo.put("openid.rpnonce", req.getParameter("openid.rpnonce"));
|
||||
state.retTo.put("openid.rpsig", req.getParameter("openid.rpsig"));
|
||||
}
|
||||
|
||||
final VerificationResult result =
|
||||
manager.verify(state.retTo.toString(), new ParameterList(req
|
||||
.getParameterMap()), state.discovered);
|
||||
if (result.getVerifiedId() == null /* authentication failure */) {
|
||||
if ("Nonce verification failed.".equals(result.getStatusMsg())) {
|
||||
// We might be suffering from clock skew on this system.
|
||||
//
|
||||
log.error("OpenID failure: " + result.getStatusMsg()
|
||||
+ " Likely caused by clock skew on this server,"
|
||||
+ " install/configure NTP.");
|
||||
cancelWithError(req, rsp, result.getStatusMsg());
|
||||
|
||||
} else if (result.getStatusMsg() != null) {
|
||||
// Authentication failed.
|
||||
//
|
||||
log.error("OpenID failure: " + result.getStatusMsg());
|
||||
cancelWithError(req, rsp, result.getStatusMsg());
|
||||
|
||||
} else {
|
||||
// Assume authentication was canceled.
|
||||
//
|
||||
cancel(req, rsp);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final Message authRsp = result.getAuthResponse();
|
||||
SRegResponse sregRsp = null;
|
||||
FetchResponse fetchRsp = null;
|
||||
|
||||
if (0 <= papeMaxAuthAge) {
|
||||
PapeResponse ext;
|
||||
boolean unsupported = false;
|
||||
|
||||
try {
|
||||
ext = (PapeResponse) authRsp.getExtension(PapeMessage.OPENID_NS_PAPE);
|
||||
} catch (MessageException err) {
|
||||
// Far too many providers are unable to provide PAPE extensions
|
||||
// right now. Instead of blocking all of them log the error and
|
||||
// let the authentication complete anyway.
|
||||
//
|
||||
log.error("Invalid PAPE response " + openidIdentifier + ": " + err);
|
||||
unsupported = true;
|
||||
ext = null;
|
||||
}
|
||||
if (!unsupported && ext == null) {
|
||||
log.error("No PAPE extension response from " + openidIdentifier);
|
||||
cancelWithError(req, rsp, "OpenID provider does not support PAPE.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (authRsp.hasExtension(SRegMessage.OPENID_NS_SREG)) {
|
||||
final MessageExtension ext =
|
||||
authRsp.getExtension(SRegMessage.OPENID_NS_SREG);
|
||||
if (ext instanceof SRegResponse) {
|
||||
sregRsp = (SRegResponse) ext;
|
||||
}
|
||||
}
|
||||
|
||||
if (authRsp.hasExtension(AxMessage.OPENID_NS_AX)) {
|
||||
final MessageExtension ext = authRsp.getExtension(AxMessage.OPENID_NS_AX);
|
||||
if (ext instanceof FetchResponse) {
|
||||
fetchRsp = (FetchResponse) ext;
|
||||
}
|
||||
}
|
||||
|
||||
final com.google.gerrit.server.account.AuthRequest areq =
|
||||
new com.google.gerrit.server.account.AuthRequest(openidIdentifier);
|
||||
|
||||
if (sregRsp != null) {
|
||||
areq.setDisplayName(sregRsp.getAttributeValue("fullname"));
|
||||
areq.setEmailAddress(sregRsp.getAttributeValue("email"));
|
||||
|
||||
} else if (fetchRsp != null) {
|
||||
final String firstName = fetchRsp.getAttributeValue("FirstName");
|
||||
final String lastName = fetchRsp.getAttributeValue("LastName");
|
||||
final StringBuilder n = new StringBuilder();
|
||||
if (firstName != null && firstName.length() > 0) {
|
||||
n.append(firstName);
|
||||
}
|
||||
if (lastName != null && lastName.length() > 0) {
|
||||
if (n.length() > 0) {
|
||||
n.append(' ');
|
||||
}
|
||||
n.append(lastName);
|
||||
}
|
||||
areq.setDisplayName(n.length() > 0 ? n.toString() : null);
|
||||
areq.setEmailAddress(fetchRsp.getAttributeValue("Email"));
|
||||
}
|
||||
|
||||
if (claimedIdentifier != null) {
|
||||
// The user used a claimed identity which has delegated to the verified
|
||||
// identity we have in our AuthRequest above. We still should have a
|
||||
// link between the two, so set one up if not present.
|
||||
//
|
||||
Account.Id claimedId = accountManager.lookup(claimedIdentifier);
|
||||
Account.Id actualId = accountManager.lookup(areq.getExternalId());
|
||||
|
||||
if (claimedId != null && actualId != null) {
|
||||
if (claimedId.equals(actualId)) {
|
||||
// Both link to the same account, that's what we expected.
|
||||
} else {
|
||||
// This is (for now) a fatal error. There are two records
|
||||
// for what might be the same user.
|
||||
//
|
||||
log.error("OpenID accounts disagree over user identity:\n"
|
||||
+ " Claimed ID: " + claimedId + " is " + claimedIdentifier
|
||||
+ "\n" + " Delgate ID: " + actualId + " is "
|
||||
+ areq.getExternalId());
|
||||
cancelWithError(req, rsp, "Contact site administrator");
|
||||
return;
|
||||
}
|
||||
|
||||
} else if (claimedId == null && actualId != null) {
|
||||
// Older account, the actual was already created but the claimed
|
||||
// was missing due to a bug in Gerrit. Link the claimed.
|
||||
//
|
||||
final com.google.gerrit.server.account.AuthRequest linkReq =
|
||||
new com.google.gerrit.server.account.AuthRequest(claimedIdentifier);
|
||||
linkReq.setDisplayName(areq.getDisplayName());
|
||||
linkReq.setEmailAddress(areq.getEmailAddress());
|
||||
accountManager.link(actualId, linkReq);
|
||||
|
||||
} else if (claimedId != null && actualId == null) {
|
||||
// Claimed account already exists, but it smells like the user has
|
||||
// changed their delegate to point to a different provider. Link
|
||||
// the new provider.
|
||||
//
|
||||
accountManager.link(claimedId, areq);
|
||||
|
||||
} else {
|
||||
// Both are null, we are going to create a new account below.
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
final com.google.gerrit.server.account.AuthResult arsp;
|
||||
switch (mode) {
|
||||
case REGISTER:
|
||||
case SIGN_IN:
|
||||
arsp = accountManager.authenticate(areq);
|
||||
|
||||
final Cookie lastId = new Cookie(OpenIdUrls.LASTID_COOKIE, "");
|
||||
lastId.setPath(req.getContextPath() + "/");
|
||||
if (remember) {
|
||||
lastId.setValue(rediscoverIdentifier);
|
||||
lastId.setMaxAge(LASTID_AGE);
|
||||
} else {
|
||||
lastId.setMaxAge(0);
|
||||
}
|
||||
rsp.addCookie(lastId);
|
||||
webSession.get().login(arsp, remember);
|
||||
if (arsp.isNew() && claimedIdentifier != null) {
|
||||
final com.google.gerrit.server.account.AuthRequest linkReq =
|
||||
new com.google.gerrit.server.account.AuthRequest(
|
||||
claimedIdentifier);
|
||||
linkReq.setDisplayName(areq.getDisplayName());
|
||||
linkReq.setEmailAddress(areq.getEmailAddress());
|
||||
accountManager.link(arsp.getAccountId(), linkReq);
|
||||
}
|
||||
callback(arsp.isNew(), req, rsp);
|
||||
break;
|
||||
|
||||
case LINK_IDENTIY: {
|
||||
arsp = accountManager.link(identifiedUser.get().getAccountId(), areq);
|
||||
webSession.get().login(arsp, remember);
|
||||
callback(false, req, rsp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (AccountException e) {
|
||||
log.error("OpenID authentication failure", e);
|
||||
cancelWithError(req, rsp, "Contact site administrator");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSignIn(final SignInMode mode) {
|
||||
switch (mode) {
|
||||
case SIGN_IN:
|
||||
case REGISTER:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static SignInMode signInMode(final HttpServletRequest req) {
|
||||
try {
|
||||
return SignInMode.valueOf(req.getParameter(P_MODE));
|
||||
} catch (RuntimeException e) {
|
||||
return SignInMode.SIGN_IN;
|
||||
}
|
||||
}
|
||||
|
||||
private void callback(final boolean isNew, final HttpServletRequest req,
|
||||
final HttpServletResponse rsp) throws IOException {
|
||||
String token = req.getParameter(P_TOKEN);
|
||||
if (token == null || token.isEmpty() || token.startsWith("/SignInFailure,")) {
|
||||
token = PageLinks.MINE;
|
||||
}
|
||||
|
||||
final StringBuilder rdr = new StringBuilder();
|
||||
rdr.append(urlProvider.get());
|
||||
rdr.append('#');
|
||||
if (isNew && !token.startsWith(PageLinks.REGISTER + "/")) {
|
||||
rdr.append(PageLinks.REGISTER);
|
||||
}
|
||||
rdr.append(token);
|
||||
rsp.sendRedirect(rdr.toString());
|
||||
}
|
||||
|
||||
private void cancel(final HttpServletRequest req,
|
||||
final HttpServletResponse rsp) throws IOException {
|
||||
if (isSignIn(signInMode(req))) {
|
||||
webSession.get().logout();
|
||||
}
|
||||
callback(false, req, rsp);
|
||||
}
|
||||
|
||||
private void cancelWithError(final HttpServletRequest req,
|
||||
final HttpServletResponse rsp, final String errorDetail)
|
||||
throws IOException {
|
||||
final SignInMode mode = signInMode(req);
|
||||
if (isSignIn(mode)) {
|
||||
webSession.get().logout();
|
||||
}
|
||||
final StringBuilder rdr = new StringBuilder();
|
||||
rdr.append(urlProvider.get());
|
||||
rdr.append('#');
|
||||
rdr.append("SignInFailure");
|
||||
rdr.append(',');
|
||||
rdr.append(mode.name());
|
||||
rdr.append(',');
|
||||
rdr.append(errorDetail != null ? KeyUtil.encode(errorDetail) : "");
|
||||
rsp.sendRedirect(rdr.toString());
|
||||
}
|
||||
|
||||
private State init(final String openidIdentifier, final SignInMode mode,
|
||||
final boolean remember, final String returnToken) {
|
||||
final List<?> list;
|
||||
try {
|
||||
list = manager.discover(openidIdentifier);
|
||||
} catch (DiscoveryException e) {
|
||||
log.error("Cannot discover OpenID " + openidIdentifier, e);
|
||||
return null;
|
||||
}
|
||||
if (list == null || list.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final String contextUrl = urlProvider.get();
|
||||
final DiscoveryInformation discovered = manager.associate(list);
|
||||
final UrlEncoded retTo = new UrlEncoded(contextUrl + RETURN_URL);
|
||||
retTo.put(P_MODE, mode.name());
|
||||
if (returnToken != null && returnToken.length() > 0) {
|
||||
retTo.put(P_TOKEN, returnToken);
|
||||
}
|
||||
if (remember) {
|
||||
retTo.put(P_REMEMBER, "1");
|
||||
}
|
||||
if (discovered.hasClaimedIdentifier()) {
|
||||
retTo.put(P_CLAIMED, discovered.getClaimedIdentifier().getIdentifier());
|
||||
}
|
||||
return new State(discovered, retTo, contextUrl);
|
||||
}
|
||||
|
||||
private boolean isAllowedOpenID(final String id) {
|
||||
for (final OpenIdProviderPattern pattern : allowedOpenIDs) {
|
||||
if (pattern.matches(id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static class State {
|
||||
final DiscoveryInformation discovered;
|
||||
final UrlEncoded retTo;
|
||||
final String contextUrl;
|
||||
|
||||
State(final DiscoveryInformation d, final UrlEncoded r, final String c) {
|
||||
discovered = d;
|
||||
retTo = r;
|
||||
contextUrl = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.google.gerrit.httpd.auth.openid;
|
||||
|
||||
import com.google.gerrit.server.config.CanonicalWebUrl;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@Singleton
|
||||
class XrdsFilter implements Filter {
|
||||
private final Provider<String> url;
|
||||
|
||||
@Inject
|
||||
XrdsFilter(@CanonicalWebUrl final Provider<String> url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response,
|
||||
FilterChain chain) throws IOException, ServletException {
|
||||
HttpServletResponse rsp = (HttpServletResponse) response;
|
||||
rsp.setHeader("X-XRDS-Location", url.get() + XrdsServlet.LOCATION);
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright (C) 2010 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.httpd.auth.openid;
|
||||
|
||||
import com.google.gerrit.server.config.CanonicalWebUrl;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@Singleton
|
||||
class XrdsServlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String ENC = "UTF-8";
|
||||
static final String LOCATION = "OpenID.XRDS";
|
||||
|
||||
private final Provider<String> url;
|
||||
|
||||
@Inject
|
||||
XrdsServlet(@CanonicalWebUrl final Provider<String> url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse rsp)
|
||||
throws IOException {
|
||||
final StringBuilder r = new StringBuilder();
|
||||
r.append("<?xml version=\"1.0\" encoding=\"" + ENC + "\"?>");
|
||||
r.append("<xrds:XRDS");
|
||||
r.append(" xmlns:xrds=\"xri://$xrds\"");
|
||||
r.append(" xmlns:openid=\"http://openid.net/xmlns/1.0\"");
|
||||
r.append(" xmlns=\"xri://$xrd*($v*2.0)\">");
|
||||
r.append("<XRD>");
|
||||
r.append("<Service priority=\"1\">");
|
||||
r.append("<Type>http://specs.openid.net/auth/2.0/return_to</Type>");
|
||||
r.append("<URI>" + url.get() + OpenIdServiceImpl.RETURN_URL + "</URI>");
|
||||
r.append("</Service>");
|
||||
r.append("</XRD>");
|
||||
r.append("</xrds:XRDS>");
|
||||
r.append("\n");
|
||||
|
||||
final byte[] raw = r.toString().getBytes(ENC);
|
||||
rsp.setContentLength(raw.length);
|
||||
rsp.setContentType("application/xrds+xml");
|
||||
rsp.setCharacterEncoding(ENC);
|
||||
|
||||
final ServletOutputStream out = rsp.getOutputStream();
|
||||
try {
|
||||
out.write(raw);
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user