Use auth.allowedOpenID to limit which providers can be used
Setting allowedOpenID permits an administrator to require that only recognized OpenID providers be used to authenticate to the server. Unlike trustedOpenID, users with providers not on the list are simply not permitted to login. Change-Id: I56106f2d92d100a3085b8738d556717da03ae5d7 Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
@@ -96,6 +96,18 @@ authentication is not possible.
|
||||
+
|
||||
By default, OpenID.
|
||||
|
||||
[[auth.allowedOpenID]]auth.allowedOpenID::
|
||||
+
|
||||
List of permitted OpenID providers. A user may only authenticate
|
||||
with an OpenID that matches this list. Only used if `auth.type`
|
||||
was set to OpenID (the default).
|
||||
+
|
||||
Patterns may be either a regular expression (start with `^` and
|
||||
end with `$`) or be a simple prefix (any other string).
|
||||
+
|
||||
By default, the list contains two values, `http://` and `https://`,
|
||||
allowing users to authenticate with any OpenID provider.
|
||||
|
||||
[[auth.trustedOpenID]]auth.trustedOpenID::
|
||||
+
|
||||
List of trusted OpenID providers. Only used if `auth.type` was
|
||||
|
||||
@@ -17,21 +17,34 @@ package com.google.gerrit.common.auth.openid;
|
||||
import java.util.Map;
|
||||
|
||||
public final class DiscoveryResult {
|
||||
public boolean validProvider;
|
||||
public static enum Status {
|
||||
/** Provider was discovered and {@code providerUrl} is valid. */
|
||||
VALID,
|
||||
|
||||
/** The identifier is not allowed to be used, by site configuration. */
|
||||
NOT_ALLOWED,
|
||||
|
||||
/** Identifier isn't for an OpenID provider. */
|
||||
NO_PROVIDER,
|
||||
|
||||
/** The provider was discovered, but something else failed. */
|
||||
ERROR;
|
||||
}
|
||||
|
||||
public Status status;
|
||||
public String providerUrl;
|
||||
public Map<String, String> providerArgs;
|
||||
|
||||
protected DiscoveryResult() {
|
||||
}
|
||||
|
||||
public DiscoveryResult(final boolean valid, final String redirect,
|
||||
final Map<String, String> args) {
|
||||
validProvider = valid;
|
||||
public DiscoveryResult(final String redirect, final Map<String, String> args) {
|
||||
status = Status.VALID;
|
||||
providerUrl = redirect;
|
||||
providerArgs = args;
|
||||
}
|
||||
|
||||
public DiscoveryResult(final boolean fail) {
|
||||
this(false, null, null);
|
||||
public DiscoveryResult(final Status s) {
|
||||
status = s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package com.google.gerrit.common.data;
|
||||
|
||||
import com.google.gerrit.common.auth.openid.OpenIdProviderPattern;
|
||||
import com.google.gerrit.reviewdb.Account;
|
||||
import com.google.gerrit.reviewdb.AuthType;
|
||||
import com.google.gerrit.reviewdb.Project;
|
||||
@@ -24,6 +25,8 @@ import java.util.Set;
|
||||
|
||||
public class GerritConfig implements Cloneable {
|
||||
protected String registerUrl;
|
||||
protected List<OpenIdProviderPattern> allowedOpenIDs;
|
||||
|
||||
protected GitwebLink gitweb;
|
||||
protected boolean useContributorAgreements;
|
||||
protected boolean useContactInfo;
|
||||
@@ -52,6 +55,14 @@ public class GerritConfig implements Cloneable {
|
||||
registerUrl = u;
|
||||
}
|
||||
|
||||
public List<OpenIdProviderPattern> getAllowedOpenIDs() {
|
||||
return allowedOpenIDs;
|
||||
}
|
||||
|
||||
public void setAllowedOpenIDs(List<OpenIdProviderPattern> l) {
|
||||
allowedOpenIDs = l;
|
||||
}
|
||||
|
||||
public AuthType getAuthType() {
|
||||
return authType;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,10 @@ public interface OpenIdConstants extends Constants {
|
||||
String buttonLinkId();
|
||||
|
||||
String rememberMe();
|
||||
String notSupported();
|
||||
|
||||
String notAllowed();
|
||||
String noProvider();
|
||||
String error();
|
||||
|
||||
String nameGoogle();
|
||||
String nameYahoo();
|
||||
|
||||
@@ -3,7 +3,10 @@ buttonRegister = Register
|
||||
buttonLinkId = Link Identity
|
||||
|
||||
rememberMe = Remember Me
|
||||
notSupported = Provider is not supported, or was incorrectly entered.
|
||||
|
||||
notAllowed = Provider is not allowed.
|
||||
noProvider = Provider is not supported, or was incorrectly entered.
|
||||
error = Unable to connect with OpenID provider.
|
||||
|
||||
nameGoogle = Google Account
|
||||
nameYahoo = Yahoo! ID
|
||||
|
||||
@@ -14,11 +14,13 @@
|
||||
|
||||
package com.google.gerrit.client.auth.openid;
|
||||
|
||||
import com.google.gerrit.client.Gerrit;
|
||||
import com.google.gerrit.client.SignInDialog;
|
||||
import com.google.gerrit.client.rpc.GerritCallback;
|
||||
import com.google.gerrit.client.ui.SmallHeading;
|
||||
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.OpenIdUrls;
|
||||
import com.google.gwt.dom.client.FormElement;
|
||||
import com.google.gwt.event.dom.client.ClickEvent;
|
||||
@@ -230,6 +232,10 @@ public class OpenIdSignInDialog extends SignInDialog implements
|
||||
|
||||
private void link(final String identUrl, final String who,
|
||||
final ImageResource icon) {
|
||||
if (!isAllowedProvider(identUrl)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ClickHandler i = new ClickHandler() {
|
||||
@Override
|
||||
public void onClick(final ClickEvent event) {
|
||||
@@ -266,6 +272,15 @@ public class OpenIdSignInDialog extends SignInDialog implements
|
||||
formBody.add(line);
|
||||
}
|
||||
|
||||
private static boolean isAllowedProvider(final String identUrl) {
|
||||
for (OpenIdProviderPattern p : Gerrit.getConfig().getAllowedOpenIDs()) {
|
||||
if (p.matches(identUrl)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void enable(final boolean on) {
|
||||
providerId.setEnabled(on);
|
||||
login.setEnabled(on);
|
||||
@@ -274,33 +289,40 @@ public class OpenIdSignInDialog extends SignInDialog implements
|
||||
private void onDiscovery(final DiscoveryResult result) {
|
||||
discovering = false;
|
||||
|
||||
if (result.validProvider) {
|
||||
switch (result.status) {
|
||||
case VALID:
|
||||
// The provider won't support operation inside an IFRAME,
|
||||
// so we replace our entire application.
|
||||
//
|
||||
redirectForm.setMethod(FormPanel.METHOD_POST);
|
||||
redirectForm.setAction(result.providerUrl);
|
||||
redirectBody.clear();
|
||||
for (final Map.Entry<String, String> e : result.providerArgs.entrySet()) {
|
||||
redirectBody.add(new Hidden(e.getKey(), e.getValue()));
|
||||
}
|
||||
|
||||
// The provider won't support operation inside an IFRAME, so we
|
||||
// replace our entire application. No fancy waits are needed,
|
||||
// the browser won't update anything until its started to load
|
||||
// the provider's page.
|
||||
//
|
||||
FormElement.as(redirectForm.getElement()).setTarget("_top");
|
||||
redirectForm.submit();
|
||||
break;
|
||||
|
||||
} else {
|
||||
// We failed discovery. We have to use a deferred command here
|
||||
// as we are being called from within an invisible IFRAME. Jump
|
||||
// back to the main event loop in the parent window.
|
||||
//
|
||||
onDiscoveryFailure();
|
||||
case NOT_ALLOWED:
|
||||
showError(OpenIdUtil.C.notAllowed());
|
||||
enableRetryDiscovery();
|
||||
break;
|
||||
|
||||
case NO_PROVIDER:
|
||||
showError(OpenIdUtil.C.noProvider());
|
||||
enableRetryDiscovery();
|
||||
break;
|
||||
|
||||
case ERROR:
|
||||
default:
|
||||
showError(OpenIdUtil.C.error());
|
||||
enableRetryDiscovery();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void onDiscoveryFailure() {
|
||||
showError(OpenIdUtil.C.notSupported());
|
||||
private void enableRetryDiscovery() {
|
||||
enable(true);
|
||||
providerId.selectAll();
|
||||
providerId.setFocus(true);
|
||||
@@ -316,6 +338,12 @@ public class OpenIdSignInDialog extends SignInDialog implements
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isAllowedProvider(openidIdentifier)) {
|
||||
showError(OpenIdUtil.C.notAllowed());
|
||||
enableRetryDiscovery();
|
||||
return;
|
||||
}
|
||||
|
||||
discovering = true;
|
||||
enable(false);
|
||||
hideError();
|
||||
@@ -330,7 +358,7 @@ public class OpenIdSignInDialog extends SignInDialog implements
|
||||
@Override
|
||||
public void onFailure(final Throwable caught) {
|
||||
super.onFailure(caught);
|
||||
onDiscoveryFailure();
|
||||
enableRetryDiscovery();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -80,6 +80,10 @@ class GerritConfigProvider implements Provider<GerritConfig> {
|
||||
private GerritConfig create() throws MalformedURLException {
|
||||
final GerritConfig config = new GerritConfig();
|
||||
switch (authConfig.getAuthType()) {
|
||||
case OPENID:
|
||||
config.setAllowedOpenIDs(authConfig.getAllowedOpenIDs());
|
||||
break;
|
||||
|
||||
case LDAP:
|
||||
case LDAP_BIND:
|
||||
config.setRegisterUrl(cfg.getString("auth", null, "registerurl"));
|
||||
|
||||
@@ -17,6 +17,7 @@ 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;
|
||||
@@ -27,6 +28,7 @@ import com.google.gerrit.server.account.AccountException;
|
||||
import com.google.gerrit.server.account.AccountManager;
|
||||
import com.google.gerrit.server.cache.Cache;
|
||||
import com.google.gerrit.server.cache.SelfPopulatingCache;
|
||||
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;
|
||||
@@ -101,6 +103,7 @@ class OpenIdServiceImpl implements OpenIdService {
|
||||
private final Provider<String> urlProvider;
|
||||
private final AccountManager accountManager;
|
||||
private final ConsumerManager manager;
|
||||
private final List<OpenIdProviderPattern> allowedOpenIDs;
|
||||
private final SelfPopulatingCache<String, List> discoveryCache;
|
||||
|
||||
/** Maximum age, in seconds, before forcing re-authentication of account. */
|
||||
@@ -111,8 +114,8 @@ class OpenIdServiceImpl implements OpenIdService {
|
||||
final Provider<IdentifiedUser> iu,
|
||||
@CanonicalWebUrl @Nullable final Provider<String> up,
|
||||
@Named("openid") final Cache<String, List> openidCache,
|
||||
@GerritServerConfig final Config config, final AccountManager am)
|
||||
throws ConsumerException, MalformedURLException {
|
||||
@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"));
|
||||
@@ -143,6 +146,7 @@ class OpenIdServiceImpl implements OpenIdService {
|
||||
urlProvider = up;
|
||||
accountManager = am;
|
||||
manager = new ConsumerManager();
|
||||
allowedOpenIDs = ac.getAllowedOpenIDs();
|
||||
papeMaxAuthAge = (int) ConfigUtil.getTimeUnit(config, //
|
||||
"auth", null, "maxOpenIdSessionAge", -1, TimeUnit.SECONDS);
|
||||
|
||||
@@ -162,11 +166,16 @@ class OpenIdServiceImpl implements OpenIdService {
|
||||
|
||||
public void discover(final String openidIdentifier, final SignInMode mode,
|
||||
final boolean remember, final String returnToken,
|
||||
final AsyncCallback<DiscoveryResult> callback) {
|
||||
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) {
|
||||
callback.onSuccess(new DiscoveryResult(false));
|
||||
cb.onSuccess(new DiscoveryResult(DiscoveryResult.Status.NO_PROVIDER));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -194,14 +203,16 @@ class OpenIdServiceImpl implements OpenIdService {
|
||||
aReq.addExtension(pape);
|
||||
}
|
||||
} catch (MessageException e) {
|
||||
callback.onSuccess(new DiscoveryResult(false));
|
||||
log.error("Cannot create OpenID redirect for " + openidIdentifier, e);
|
||||
cb.onSuccess(new DiscoveryResult(DiscoveryResult.Status.ERROR));
|
||||
return;
|
||||
} catch (ConsumerException e) {
|
||||
callback.onSuccess(new DiscoveryResult(false));
|
||||
log.error("Cannot create OpenID redirect for " + openidIdentifier, e);
|
||||
cb.onSuccess(new DiscoveryResult(DiscoveryResult.Status.ERROR));
|
||||
return;
|
||||
}
|
||||
|
||||
callback.onSuccess(new DiscoveryResult(true, aReq.getDestinationUrl(false),
|
||||
cb.onSuccess(new DiscoveryResult(aReq.getDestinationUrl(false), //
|
||||
aReq.getParameterMap()));
|
||||
}
|
||||
|
||||
@@ -245,6 +256,13 @@ class OpenIdServiceImpl implements OpenIdService {
|
||||
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.
|
||||
@@ -525,6 +543,15 @@ class OpenIdServiceImpl implements OpenIdService {
|
||||
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;
|
||||
|
||||
@@ -39,7 +39,8 @@ public class AuthConfig {
|
||||
private final AuthType authType;
|
||||
private final String httpHeader;
|
||||
private final String logoutUrl;
|
||||
private final List<OpenIdProviderPattern> trusted;
|
||||
private final List<OpenIdProviderPattern> trustedOpenIDs;
|
||||
private final List<OpenIdProviderPattern> allowedOpenIDs;
|
||||
private final SignedToken emailReg;
|
||||
|
||||
private final AccountGroup.Id administratorGroup;
|
||||
@@ -54,7 +55,8 @@ public class AuthConfig {
|
||||
authType = toType(cfg);
|
||||
httpHeader = cfg.getString("auth", null, "httpheader");
|
||||
logoutUrl = cfg.getString("auth", null, "logouturl");
|
||||
trusted = toPatterns(cfg, "trustedopenid");
|
||||
trustedOpenIDs = toPatterns(cfg, "trustedOpenID");
|
||||
allowedOpenIDs = toPatterns(cfg, "allowedOpenID");
|
||||
emailReg = new SignedToken(5 * 24 * 60 * 60, s.registerEmailPrivateKey);
|
||||
|
||||
final HashSet<AccountGroup.Id> r = new HashSet<AccountGroup.Id>(2);
|
||||
@@ -125,6 +127,11 @@ public class AuthConfig {
|
||||
return registeredGroups;
|
||||
}
|
||||
|
||||
/** OpenID identities which the server permits for authentication. */
|
||||
public List<OpenIdProviderPattern> getAllowedOpenIDs() {
|
||||
return allowedOpenIDs;
|
||||
}
|
||||
|
||||
public boolean isIdentityTrustable(final Collection<AccountExternalId> ids) {
|
||||
switch (getAuthType()) {
|
||||
case DEVELOPMENT_BECOME_ANY_ACCOUNT:
|
||||
@@ -184,7 +191,7 @@ public class AuthConfig {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (final OpenIdProviderPattern p : trusted) {
|
||||
for (final OpenIdProviderPattern p : trustedOpenIDs) {
|
||||
if (p.matches(id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user