Git over HTTP BasicAuth against Gerrit basic auth.
Allows the configuration of native Gerrit username/password authentication scheme used for Git over HTTP BasicAuth, as alternative of default DigestAuth scheme against the random generated password on Gerrit DB. Example gerrit.config: [auth] type = LDAP gitBasicAuth = true Git over HTTP protocol will be authenticated using HTTP-BasicAuth and credentials checked on LDAP. Change-Id: Ibe589ab2b0887d85fd5ae5a4c110dea10dac5d6d
This commit is contained in:

committed by
Shawn O. Pearce

parent
3bd0ccc181
commit
4205884c67
@@ -319,6 +319,18 @@ then Gerrit will do the authentication (using DIGEST authentication).
|
|||||||
+
|
+
|
||||||
By default this is set to false.
|
By default this is set to false.
|
||||||
|
|
||||||
|
[[auth.gitBasicAuth]]auth.gitBasicAuth::
|
||||||
|
+
|
||||||
|
If true then Git over HTTP and HTTP/S traffic is authenticated using
|
||||||
|
standard BasicAuth and credentials validated using the same auth
|
||||||
|
method configured for Gerrit Web UI.
|
||||||
|
+
|
||||||
|
This parameter only affects git over http traffic. If set to false
|
||||||
|
then Gerrit will authenticate through DIGEST authentication and
|
||||||
|
the randomly generated HTTP password in Gerrit DB.
|
||||||
|
+
|
||||||
|
By default this is set to false.
|
||||||
|
|
||||||
[[auth.userNameToLowerCase]]auth.userNameToLowerCase::
|
[[auth.userNameToLowerCase]]auth.userNameToLowerCase::
|
||||||
+
|
+
|
||||||
If set the username that is received to authenticate a git operation
|
If set the username that is received to authenticate a git operation
|
||||||
|
@@ -34,6 +34,8 @@ public class GitOverHttpModule extends ServletModule {
|
|||||||
Class<? extends Filter> authFilter;
|
Class<? extends Filter> authFilter;
|
||||||
if (authConfig.isTrustContainerAuth()) {
|
if (authConfig.isTrustContainerAuth()) {
|
||||||
authFilter = ContainerAuthFilter.class;
|
authFilter = ContainerAuthFilter.class;
|
||||||
|
} else if (authConfig.isGitBasichAuth()) {
|
||||||
|
authFilter = ProjectBasicAuthFilter.class;
|
||||||
} else {
|
} else {
|
||||||
authFilter = ProjectDigestFilter.class;
|
authFilter = ProjectDigestFilter.class;
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,202 @@
|
|||||||
|
// Copyright (C) 2012 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;
|
||||||
|
|
||||||
|
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
|
||||||
|
|
||||||
|
import com.google.common.base.Objects;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.gerrit.server.account.AccountCache;
|
||||||
|
import com.google.gerrit.server.account.AccountException;
|
||||||
|
import com.google.gerrit.server.account.AccountManager;
|
||||||
|
import com.google.gerrit.server.account.AccountState;
|
||||||
|
import com.google.gerrit.server.account.AuthMethod;
|
||||||
|
import com.google.gerrit.server.account.AuthRequest;
|
||||||
|
import com.google.gerrit.server.account.AuthResult;
|
||||||
|
import com.google.gerrit.server.config.AuthConfig;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
import com.google.inject.Singleton;
|
||||||
|
|
||||||
|
import org.apache.commons.codec.binary.Base64;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
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.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import javax.servlet.http.HttpServletResponseWrapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticates the current user by HTTP basic authentication.
|
||||||
|
* <p>
|
||||||
|
* The current HTTP request is authenticated by looking up the username and
|
||||||
|
* password from the Base64 encoded Authorization header and validating them
|
||||||
|
* against any username/password configured authentication system in Gerrit.
|
||||||
|
* This filter is intended only to protect the {@link ProjectServlet} and its
|
||||||
|
* handled URLs, which provide remote repository access over HTTP.
|
||||||
|
*
|
||||||
|
* @see <a href="http://www.ietf.org/rfc/rfc2617.txt">RFC 2617</a>
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
class ProjectBasicAuthFilter implements Filter {
|
||||||
|
private static final Logger log = LoggerFactory
|
||||||
|
.getLogger(ProjectBasicAuthFilter.class);
|
||||||
|
|
||||||
|
public static final String REALM_NAME = "Gerrit Code Review";
|
||||||
|
private static final String AUTHORIZATION = "Authorization";
|
||||||
|
private static final String LIT_BASIC = "Basic ";
|
||||||
|
|
||||||
|
private final Provider<WebSession> session;
|
||||||
|
private final AccountCache accountCache;
|
||||||
|
private final AccountManager accountManager;
|
||||||
|
private final AuthConfig authConfig;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ProjectBasicAuthFilter(Provider<WebSession> session,
|
||||||
|
AccountCache accountCache, AccountManager accountManager,
|
||||||
|
AuthConfig authConfig) {
|
||||||
|
this.session = session;
|
||||||
|
this.accountCache = accountCache;
|
||||||
|
this.accountManager = accountManager;
|
||||||
|
this.authConfig = authConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(FilterConfig config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doFilter(ServletRequest request, ServletResponse response,
|
||||||
|
FilterChain chain) throws IOException, ServletException {
|
||||||
|
HttpServletRequest req = (HttpServletRequest) request;
|
||||||
|
Response rsp = new Response((HttpServletResponse) response);
|
||||||
|
|
||||||
|
if (verify(req, rsp)) {
|
||||||
|
chain.doFilter(req, rsp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean verify(HttpServletRequest req, Response rsp)
|
||||||
|
throws IOException {
|
||||||
|
final String hdr = req.getHeader(AUTHORIZATION);
|
||||||
|
if (hdr == null) {
|
||||||
|
// Allow an anonymous connection through, or it might be using a
|
||||||
|
// session cookie instead of basic authentication.
|
||||||
|
//
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final byte[] decoded =
|
||||||
|
Base64.decodeBase64(hdr.substring(LIT_BASIC.length()));
|
||||||
|
String usernamePassword = new String(decoded, encoding(req));
|
||||||
|
int splitPos = usernamePassword.indexOf(':');
|
||||||
|
if (splitPos < 1) {
|
||||||
|
rsp.sendError(SC_UNAUTHORIZED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String username = usernamePassword.substring(0, splitPos);
|
||||||
|
String password = usernamePassword.substring(splitPos + 1);
|
||||||
|
if (Strings.isNullOrEmpty(password)) {
|
||||||
|
rsp.sendError(SC_UNAUTHORIZED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (authConfig.isUserNameToLowerCase()) {
|
||||||
|
username = username.toLowerCase(Locale.US);
|
||||||
|
}
|
||||||
|
|
||||||
|
final AccountState who = accountCache.getByUsername(username);
|
||||||
|
if (who == null || !who.getAccount().isActive()) {
|
||||||
|
log.warn("Authentication failed for " + username
|
||||||
|
+ ": account inactive or not provisioned in Gerrit");
|
||||||
|
rsp.sendError(SC_UNAUTHORIZED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthRequest whoAuth = AuthRequest.forUser(username);
|
||||||
|
whoAuth.setPassword(password);
|
||||||
|
|
||||||
|
try {
|
||||||
|
AuthResult whoAuthResult = accountManager.authenticate(whoAuth);
|
||||||
|
session.get().setUserAccountId(whoAuthResult.getAccountId(),
|
||||||
|
AuthMethod.PASSWORD);
|
||||||
|
return true;
|
||||||
|
} catch (AccountException e) {
|
||||||
|
log.warn("Authentication failed for " + username, e);
|
||||||
|
rsp.sendError(SC_UNAUTHORIZED);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String encoding(HttpServletRequest req) {
|
||||||
|
return Objects.firstNonNull(req.getCharacterEncoding(), "UTF-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
class Response extends HttpServletResponseWrapper {
|
||||||
|
private static final String WWW_AUTHENTICATE = "WWW-Authenticate";
|
||||||
|
|
||||||
|
Response(HttpServletResponse rsp) {
|
||||||
|
super(rsp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void status(int sc) {
|
||||||
|
if (sc == SC_UNAUTHORIZED) {
|
||||||
|
StringBuilder v = new StringBuilder();
|
||||||
|
v.append(LIT_BASIC);
|
||||||
|
v.append("realm=\"" + REALM_NAME + "\"");
|
||||||
|
setHeader(WWW_AUTHENTICATE, v.toString());
|
||||||
|
} else if (containsHeader(WWW_AUTHENTICATE)) {
|
||||||
|
setHeader(WWW_AUTHENTICATE, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendError(int sc, String msg) throws IOException {
|
||||||
|
status(sc);
|
||||||
|
super.sendError(sc, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendError(int sc) throws IOException {
|
||||||
|
status(sc);
|
||||||
|
super.sendError(sc);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStatus(int sc, String sm) {
|
||||||
|
status(sc);
|
||||||
|
super.setStatus(sc, sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStatus(int sc) {
|
||||||
|
status(sc);
|
||||||
|
super.setStatus(sc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -36,6 +36,8 @@ public class AuthConfig {
|
|||||||
private final AuthType authType;
|
private final AuthType authType;
|
||||||
private final String httpHeader;
|
private final String httpHeader;
|
||||||
private final boolean trustContainerAuth;
|
private final boolean trustContainerAuth;
|
||||||
|
private final boolean userNameToLowerCase;
|
||||||
|
private final boolean gitBasicAuth;
|
||||||
private final String logoutUrl;
|
private final String logoutUrl;
|
||||||
private final List<OpenIdProviderPattern> trustedOpenIDs;
|
private final List<OpenIdProviderPattern> trustedOpenIDs;
|
||||||
private final List<OpenIdProviderPattern> allowedOpenIDs;
|
private final List<OpenIdProviderPattern> allowedOpenIDs;
|
||||||
@@ -56,6 +58,9 @@ public class AuthConfig {
|
|||||||
cookiePath = cfg.getString("auth", null, "cookiepath");
|
cookiePath = cfg.getString("auth", null, "cookiepath");
|
||||||
cookieSecure = cfg.getBoolean("auth", "cookiesecure", false);
|
cookieSecure = cfg.getBoolean("auth", "cookiesecure", false);
|
||||||
trustContainerAuth = cfg.getBoolean("auth", "trustContainerAuth", false);
|
trustContainerAuth = cfg.getBoolean("auth", "trustContainerAuth", false);
|
||||||
|
gitBasicAuth = cfg.getBoolean("auth", "gitBasicAuth", false);
|
||||||
|
userNameToLowerCase = cfg.getBoolean("auth", "userNameToLowerCase", false);
|
||||||
|
|
||||||
|
|
||||||
String key = cfg.getString("auth", null, "registerEmailPrivateKey");
|
String key = cfg.getString("auth", null, "registerEmailPrivateKey");
|
||||||
if (key != null && !key.isEmpty()) {
|
if (key != null && !key.isEmpty()) {
|
||||||
@@ -132,6 +137,16 @@ public class AuthConfig {
|
|||||||
return trustContainerAuth;
|
return trustContainerAuth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Whether user name should be converted to lower-case before validation */
|
||||||
|
public boolean isUserNameToLowerCase() {
|
||||||
|
return userNameToLowerCase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Whether git-over-http should use Gerrit basic authentication scheme. */
|
||||||
|
public boolean isGitBasichAuth() {
|
||||||
|
return gitBasicAuth;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isIdentityTrustable(final Collection<AccountExternalId> ids) {
|
public boolean isIdentityTrustable(final Collection<AccountExternalId> ids) {
|
||||||
switch (getAuthType()) {
|
switch (getAuthType()) {
|
||||||
case DEVELOPMENT_BECOME_ANY_ACCOUNT:
|
case DEVELOPMENT_BECOME_ANY_ACCOUNT:
|
||||||
|
Reference in New Issue
Block a user