Authenticate /p/ HTTP and SSH access by password
Use HTTP digest authentication to verify user access to any of the /p/ URLs which do not permit anonymous requests. The SSH daemon now also honors the user's password. Change-Id: I6f8775077b3ee8fcb66a2d07c225f668afa0d530 Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
		@@ -0,0 +1,335 @@
 | 
				
			|||||||
 | 
					// 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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static java.util.concurrent.TimeUnit.HOURS;
 | 
				
			||||||
 | 
					import static java.util.concurrent.TimeUnit.SECONDS;
 | 
				
			||||||
 | 
					import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
 | 
				
			||||||
 | 
					import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
 | 
				
			||||||
 | 
					import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.google.gerrit.server.account.AccountCache;
 | 
				
			||||||
 | 
					import com.google.gerrit.server.account.AccountState;
 | 
				
			||||||
 | 
					import com.google.gerrit.server.config.CanonicalWebUrl;
 | 
				
			||||||
 | 
					import com.google.gerrit.server.config.Nullable;
 | 
				
			||||||
 | 
					import com.google.gwtjsonrpc.server.SignedToken;
 | 
				
			||||||
 | 
					import com.google.gwtjsonrpc.server.XsrfException;
 | 
				
			||||||
 | 
					import com.google.inject.Inject;
 | 
				
			||||||
 | 
					import com.google.inject.Provider;
 | 
				
			||||||
 | 
					import com.google.inject.Singleton;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					import java.io.UnsupportedEncodingException;
 | 
				
			||||||
 | 
					import java.security.MessageDigest;
 | 
				
			||||||
 | 
					import java.security.NoSuchAlgorithmException;
 | 
				
			||||||
 | 
					import java.util.Collections;
 | 
				
			||||||
 | 
					import java.util.HashMap;
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.servlet.Filter;
 | 
				
			||||||
 | 
					import javax.servlet.FilterChain;
 | 
				
			||||||
 | 
					import javax.servlet.FilterConfig;
 | 
				
			||||||
 | 
					import javax.servlet.ServletContext;
 | 
				
			||||||
 | 
					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 digest authentication.
 | 
				
			||||||
 | 
					 * <p>
 | 
				
			||||||
 | 
					 * The current HTTP request is authenticated by looking up the username from the
 | 
				
			||||||
 | 
					 * Authorization header and checking the digest response against the stored
 | 
				
			||||||
 | 
					 * password. 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 ProjectDigestFilter implements Filter {
 | 
				
			||||||
 | 
					  public static final String REALM_NAME = "Gerrit Code Review";
 | 
				
			||||||
 | 
					  private static final String AUTHORIZATION = "Authorization";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private final Provider<String> urlProvider;
 | 
				
			||||||
 | 
					  private final Provider<WebSession> session;
 | 
				
			||||||
 | 
					  private final AccountCache accountCache;
 | 
				
			||||||
 | 
					  private final SignedToken tokens;
 | 
				
			||||||
 | 
					  private ServletContext context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Inject
 | 
				
			||||||
 | 
					  ProjectDigestFilter(@CanonicalWebUrl @Nullable Provider<String> urlProvider,
 | 
				
			||||||
 | 
					      Provider<WebSession> session, AccountCache accountCache)
 | 
				
			||||||
 | 
					      throws XsrfException {
 | 
				
			||||||
 | 
					    this.urlProvider = urlProvider;
 | 
				
			||||||
 | 
					    this.session = session;
 | 
				
			||||||
 | 
					    this.accountCache = accountCache;
 | 
				
			||||||
 | 
					    this.tokens = new SignedToken((int) SECONDS.convert(1, HOURS));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Override
 | 
				
			||||||
 | 
					  public void init(FilterConfig config) {
 | 
				
			||||||
 | 
					    context = config.getServletContext();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @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 digest authentication.
 | 
				
			||||||
 | 
					      //
 | 
				
			||||||
 | 
					      return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final Map<String, String> p = parseAuthorization(hdr);
 | 
				
			||||||
 | 
					    final String username = p.get("username");
 | 
				
			||||||
 | 
					    final String realm = p.get("realm");
 | 
				
			||||||
 | 
					    final String nonce = p.get("nonce");
 | 
				
			||||||
 | 
					    final String uri = p.get("uri");
 | 
				
			||||||
 | 
					    final String response = p.get("response");
 | 
				
			||||||
 | 
					    final String qop = p.get("qop");
 | 
				
			||||||
 | 
					    final String nc = p.get("nc");
 | 
				
			||||||
 | 
					    final String cnonce = p.get("cnonce");
 | 
				
			||||||
 | 
					    final String method = req.getMethod();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (username == null //
 | 
				
			||||||
 | 
					        || realm == null //
 | 
				
			||||||
 | 
					        || nonce == null //
 | 
				
			||||||
 | 
					        || uri == null //
 | 
				
			||||||
 | 
					        || response == null //
 | 
				
			||||||
 | 
					        || !REALM_NAME.equals(realm)) {
 | 
				
			||||||
 | 
					      context.log("Invalid header: " + AUTHORIZATION + ": " + hdr);
 | 
				
			||||||
 | 
					      rsp.sendError(SC_FORBIDDEN);
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final AccountState who = accountCache.getByUsername(username);
 | 
				
			||||||
 | 
					    if (who == null) {
 | 
				
			||||||
 | 
					      rsp.sendError(SC_UNAUTHORIZED);
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final String passwd = who.getPassword(username);
 | 
				
			||||||
 | 
					    if (passwd == null) {
 | 
				
			||||||
 | 
					      rsp.sendError(SC_UNAUTHORIZED);
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final String A1 = username + ":" + realm + ":" + passwd;
 | 
				
			||||||
 | 
					    final String A2 = method + ":" + uri;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final String expect;
 | 
				
			||||||
 | 
					    if ("auth".equals(qop)) {
 | 
				
			||||||
 | 
					      expect = KD(H(A1), //
 | 
				
			||||||
 | 
					          nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + H(A2));
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      expect = KD(H(A1), nonce + ":" + H(A2));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (expect.equals(response)) {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        if (tokens.checkToken(nonce, "") != null) {
 | 
				
			||||||
 | 
					          session.get().setUserAccountId(who.getAccount().getId());
 | 
				
			||||||
 | 
					          return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          rsp.stale = true;
 | 
				
			||||||
 | 
					          rsp.sendError(SC_UNAUTHORIZED);
 | 
				
			||||||
 | 
					          return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } catch (XsrfException e) {
 | 
				
			||||||
 | 
					        context.log("Error validating nonce for digest authentication", e);
 | 
				
			||||||
 | 
					        rsp.sendError(SC_INTERNAL_SERVER_ERROR);
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      rsp.sendError(SC_UNAUTHORIZED);
 | 
				
			||||||
 | 
					      return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static String H(String data) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      MessageDigest md = newMD5();
 | 
				
			||||||
 | 
					      md.update(data.getBytes("UTF-8"));
 | 
				
			||||||
 | 
					      return LHEX(md.digest());
 | 
				
			||||||
 | 
					    } catch (UnsupportedEncodingException e) {
 | 
				
			||||||
 | 
					      throw new RuntimeException("UTF-8 encoding not available", e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static String KD(String secret, String data) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      MessageDigest md = newMD5();
 | 
				
			||||||
 | 
					      md.update(secret.getBytes("UTF-8"));
 | 
				
			||||||
 | 
					      md.update((byte) ':');
 | 
				
			||||||
 | 
					      md.update(data.getBytes("UTF-8"));
 | 
				
			||||||
 | 
					      return LHEX(md.digest());
 | 
				
			||||||
 | 
					    } catch (UnsupportedEncodingException e) {
 | 
				
			||||||
 | 
					      throw new RuntimeException("UTF-8 encoding not available", e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static MessageDigest newMD5() {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      return MessageDigest.getInstance("MD5");
 | 
				
			||||||
 | 
					    } catch (NoSuchAlgorithmException e) {
 | 
				
			||||||
 | 
					      throw new RuntimeException("No MD5 available", e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static final char[] LHEX =
 | 
				
			||||||
 | 
					      {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', //
 | 
				
			||||||
 | 
					          'a', 'b', 'c', 'd', 'e', 'f'};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static String LHEX(byte[] bin) {
 | 
				
			||||||
 | 
					    StringBuilder r = new StringBuilder(bin.length * 2);
 | 
				
			||||||
 | 
					    for (int i = 0; i < bin.length; i++) {
 | 
				
			||||||
 | 
					      byte b = bin[i];
 | 
				
			||||||
 | 
					      r.append(LHEX[(b >>> 4) & 0x0f]);
 | 
				
			||||||
 | 
					      r.append(LHEX[b & 0x0f]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return r.toString();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private Map<String, String> parseAuthorization(String auth) {
 | 
				
			||||||
 | 
					    if (!auth.startsWith("Digest ")) {
 | 
				
			||||||
 | 
					      // We only support Digest authentication scheme, deny the rest.
 | 
				
			||||||
 | 
					      //
 | 
				
			||||||
 | 
					      return Collections.emptyMap();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Map<String, String> p = new HashMap<String, String>();
 | 
				
			||||||
 | 
					    int next = "Digest ".length();
 | 
				
			||||||
 | 
					    while (next < auth.length()) {
 | 
				
			||||||
 | 
					      if (next < auth.length() && auth.charAt(next) == ',') {
 | 
				
			||||||
 | 
					        next++;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      while (next < auth.length() && Character.isWhitespace(auth.charAt(next))) {
 | 
				
			||||||
 | 
					        next++;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      int eq = auth.indexOf('=', next);
 | 
				
			||||||
 | 
					      if (eq < 0 || eq + 1 == auth.length()) {
 | 
				
			||||||
 | 
					        return Collections.emptyMap();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      final String name = auth.substring(next, eq);
 | 
				
			||||||
 | 
					      final String value;
 | 
				
			||||||
 | 
					      if (auth.charAt(eq + 1) == '"') {
 | 
				
			||||||
 | 
					        int dq = auth.indexOf('"', eq + 2);
 | 
				
			||||||
 | 
					        if (dq < 0) {
 | 
				
			||||||
 | 
					          return Collections.emptyMap();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        value = auth.substring(eq + 2, dq);
 | 
				
			||||||
 | 
					        next = dq + 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        int space = auth.indexOf(' ', eq + 1);
 | 
				
			||||||
 | 
					        int comma = auth.indexOf(',', eq + 1);
 | 
				
			||||||
 | 
					        if (space < 0) space = auth.length();
 | 
				
			||||||
 | 
					        if (comma < 0) comma = auth.length();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final int e = Math.min(space, comma);
 | 
				
			||||||
 | 
					        value = auth.substring(eq + 1, e);
 | 
				
			||||||
 | 
					        next = e + 1;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      p.put(name, value);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return p;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private String getDomain() {
 | 
				
			||||||
 | 
					    return urlProvider.get() + "p/";
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private String newNonce() {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      return tokens.newToken("");
 | 
				
			||||||
 | 
					    } catch (XsrfException e) {
 | 
				
			||||||
 | 
					      throw new RuntimeException("Cannot generate new nonce", e);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  class Response extends HttpServletResponseWrapper {
 | 
				
			||||||
 | 
					    private static final String WWW_AUTHENTICATE = "WWW-Authenticate";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Boolean stale;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Response(HttpServletResponse rsp) {
 | 
				
			||||||
 | 
					      super(rsp);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void status(int sc) {
 | 
				
			||||||
 | 
					      if (sc == SC_UNAUTHORIZED) {
 | 
				
			||||||
 | 
					        StringBuilder v = new StringBuilder();
 | 
				
			||||||
 | 
					        v.append("Digest");
 | 
				
			||||||
 | 
					        v.append(" realm=\"" + REALM_NAME + "\"");
 | 
				
			||||||
 | 
					        v.append(", domain=\"" + getDomain() + "\"");
 | 
				
			||||||
 | 
					        v.append(", qop=\"auth\"");
 | 
				
			||||||
 | 
					        if (stale != null) {
 | 
				
			||||||
 | 
					          v.append(", stale=" + stale);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        v.append(", nonce=\"" + newNonce() + "\"");
 | 
				
			||||||
 | 
					        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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -17,6 +17,7 @@ package com.google.gerrit.httpd;
 | 
				
			|||||||
import com.google.gerrit.common.PageLinks;
 | 
					import com.google.gerrit.common.PageLinks;
 | 
				
			||||||
import com.google.gerrit.reviewdb.Change;
 | 
					import com.google.gerrit.reviewdb.Change;
 | 
				
			||||||
import com.google.gerrit.reviewdb.Project;
 | 
					import com.google.gerrit.reviewdb.Project;
 | 
				
			||||||
 | 
					import com.google.gerrit.server.AnonymousUser;
 | 
				
			||||||
import com.google.gerrit.server.IdentifiedUser;
 | 
					import com.google.gerrit.server.IdentifiedUser;
 | 
				
			||||||
import com.google.gerrit.server.config.CanonicalWebUrl;
 | 
					import com.google.gerrit.server.config.CanonicalWebUrl;
 | 
				
			||||||
import com.google.gerrit.server.config.Nullable;
 | 
					import com.google.gerrit.server.config.Nullable;
 | 
				
			||||||
@@ -131,7 +132,8 @@ public class ProjectServlet extends GitServlet {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public Repository open(HttpServletRequest req, String projectName)
 | 
					    public Repository open(HttpServletRequest req, String projectName)
 | 
				
			||||||
        throws RepositoryNotFoundException {
 | 
					        throws RepositoryNotFoundException, ServiceNotAuthorizedException,
 | 
				
			||||||
 | 
					        ServiceNotEnabledException {
 | 
				
			||||||
      if (projectName.endsWith(".git")) {
 | 
					      if (projectName.endsWith(".git")) {
 | 
				
			||||||
        // Be nice and drop the trailing ".git" suffix, which we never keep
 | 
					        // Be nice and drop the trailing ".git" suffix, which we never keep
 | 
				
			||||||
        // in our database, but clients might mistakenly provide anyway.
 | 
					        // in our database, but clients might mistakenly provide anyway.
 | 
				
			||||||
@@ -151,10 +153,17 @@ public class ProjectServlet extends GitServlet {
 | 
				
			|||||||
      final ProjectControl pc;
 | 
					      final ProjectControl pc;
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        final Project.NameKey nameKey = new Project.NameKey(projectName);
 | 
					        final Project.NameKey nameKey = new Project.NameKey(projectName);
 | 
				
			||||||
        pc = projectControlFactory.validateFor(nameKey);
 | 
					        pc = projectControlFactory.controlFor(nameKey);
 | 
				
			||||||
      } catch (NoSuchProjectException err) {
 | 
					      } catch (NoSuchProjectException err) {
 | 
				
			||||||
        throw new RepositoryNotFoundException(projectName);
 | 
					        throw new RepositoryNotFoundException(projectName);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      if (!pc.isVisible()) {
 | 
				
			||||||
 | 
					        if (pc.getCurrentUser() instanceof AnonymousUser) {
 | 
				
			||||||
 | 
					          throw new ServiceNotAuthorizedException();
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          throw new ServiceNotEnabledException();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      req.setAttribute(ATT_CONTROL, pc);
 | 
					      req.setAttribute(ATT_CONTROL, pc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return manager.openRepository(pc.getProject().getName());
 | 
					      return manager.openRepository(pc.getProject().getName());
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,6 +49,8 @@ class UrlModule extends ServletModule {
 | 
				
			|||||||
    serve("/signout").with(HttpLogoutServlet.class);
 | 
					    serve("/signout").with(HttpLogoutServlet.class);
 | 
				
			||||||
    serve("/ssh_info").with(SshInfoServlet.class);
 | 
					    serve("/ssh_info").with(SshInfoServlet.class);
 | 
				
			||||||
    serve("/static/*").with(StaticServlet.class);
 | 
					    serve("/static/*").with(StaticServlet.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    filter("/p/*").through(ProjectDigestFilter.class);
 | 
				
			||||||
    serve("/p/*").with(ProjectServlet.class);
 | 
					    serve("/p/*").with(ProjectServlet.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    serve("/Main.class").with(notFound());
 | 
					    serve("/Main.class").with(notFound());
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -148,6 +148,12 @@ public final class WebSession {
 | 
				
			|||||||
    saveCookie();
 | 
					    saveCookie();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Set the user account for this current request only. */
 | 
				
			||||||
 | 
					  void setUserAccountId(Account.Id id) {
 | 
				
			||||||
 | 
					    key = new Key("id:" + id);
 | 
				
			||||||
 | 
					    val = new Val(id, 0, false, null);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public void logout() {
 | 
					  public void logout() {
 | 
				
			||||||
    if (val != null) {
 | 
					    if (val != null) {
 | 
				
			||||||
      manager.destroy(key);
 | 
					      manager.destroy(key);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,5 +20,9 @@ import com.google.gerrit.reviewdb.Account;
 | 
				
			|||||||
public interface AccountCache {
 | 
					public interface AccountCache {
 | 
				
			||||||
  public AccountState get(Account.Id accountId);
 | 
					  public AccountState get(Account.Id accountId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public AccountState getByUsername(String username);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public void evict(Account.Id accountId);
 | 
					  public void evict(Account.Id accountId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public void evictByUsername(String username);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -45,8 +45,8 @@ public class AccountCacheImpl implements AccountCache {
 | 
				
			|||||||
    return new CacheModule() {
 | 
					    return new CacheModule() {
 | 
				
			||||||
      @Override
 | 
					      @Override
 | 
				
			||||||
      protected void configure() {
 | 
					      protected void configure() {
 | 
				
			||||||
        final TypeLiteral<Cache<Account.Id, AccountState>> type =
 | 
					        final TypeLiteral<Cache<Object, AccountState>> type =
 | 
				
			||||||
            new TypeLiteral<Cache<Account.Id, AccountState>>() {};
 | 
					            new TypeLiteral<Cache<Object, AccountState>>() {};
 | 
				
			||||||
        core(type, CACHE_NAME);
 | 
					        core(type, CACHE_NAME);
 | 
				
			||||||
        bind(AccountCacheImpl.class);
 | 
					        bind(AccountCacheImpl.class);
 | 
				
			||||||
        bind(AccountCache.class).to(AccountCacheImpl.class);
 | 
					        bind(AccountCache.class).to(AccountCacheImpl.class);
 | 
				
			||||||
@@ -56,7 +56,8 @@ public class AccountCacheImpl implements AccountCache {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private final SchemaFactory<ReviewDb> schema;
 | 
					  private final SchemaFactory<ReviewDb> schema;
 | 
				
			||||||
  private final GroupCache groupCache;
 | 
					  private final GroupCache groupCache;
 | 
				
			||||||
  private final SelfPopulatingCache<Account.Id, AccountState> self;
 | 
					  private final SelfPopulatingCache<Account.Id, AccountState> byId;
 | 
				
			||||||
 | 
					  private final SelfPopulatingCache<String, Account.Id> byName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private final Set<AccountGroup.Id> registered;
 | 
					  private final Set<AccountGroup.Id> registered;
 | 
				
			||||||
  private final Set<AccountGroup.Id> anonymous;
 | 
					  private final Set<AccountGroup.Id> anonymous;
 | 
				
			||||||
@@ -64,13 +65,13 @@ public class AccountCacheImpl implements AccountCache {
 | 
				
			|||||||
  @Inject
 | 
					  @Inject
 | 
				
			||||||
  AccountCacheImpl(final SchemaFactory<ReviewDb> sf, final AuthConfig auth,
 | 
					  AccountCacheImpl(final SchemaFactory<ReviewDb> sf, final AuthConfig auth,
 | 
				
			||||||
      final GroupCache groupCache,
 | 
					      final GroupCache groupCache,
 | 
				
			||||||
      @Named(CACHE_NAME) final Cache<Account.Id, AccountState> rawCache) {
 | 
					      @Named(CACHE_NAME) final Cache<Object, AccountState> rawCache) {
 | 
				
			||||||
    schema = sf;
 | 
					    schema = sf;
 | 
				
			||||||
    registered = auth.getRegisteredGroups();
 | 
					    registered = auth.getRegisteredGroups();
 | 
				
			||||||
    anonymous = auth.getAnonymousGroups();
 | 
					    anonymous = auth.getAnonymousGroups();
 | 
				
			||||||
    this.groupCache = groupCache;
 | 
					    this.groupCache = groupCache;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    self = new SelfPopulatingCache<Account.Id, AccountState>(rawCache) {
 | 
					    byId = new SelfPopulatingCache<Account.Id, AccountState>((Cache) rawCache) {
 | 
				
			||||||
      @Override
 | 
					      @Override
 | 
				
			||||||
      protected AccountState createEntry(Account.Id key) throws Exception {
 | 
					      protected AccountState createEntry(Account.Id key) throws Exception {
 | 
				
			||||||
        return lookup(key);
 | 
					        return lookup(key);
 | 
				
			||||||
@@ -81,44 +82,72 @@ public class AccountCacheImpl implements AccountCache {
 | 
				
			|||||||
        return missingAccount(key);
 | 
					        return missingAccount(key);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    byName = new SelfPopulatingCache<String, Account.Id>((Cache) rawCache) {
 | 
				
			||||||
 | 
					      @Override
 | 
				
			||||||
 | 
					      protected Account.Id createEntry(String username) throws Exception {
 | 
				
			||||||
 | 
					        return lookup(username);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private AccountState lookup(final Account.Id who) throws OrmException {
 | 
					  private AccountState lookup(final Account.Id who) throws OrmException {
 | 
				
			||||||
    final ReviewDb db = schema.open();
 | 
					    final ReviewDb db = schema.open();
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      final Account account = db.accounts().get(who);
 | 
					      final AccountState state = load(db, who);
 | 
				
			||||||
      if (account == null) {
 | 
					      if (state.getUserName() != null) {
 | 
				
			||||||
        // Account no longer exists? They are anonymous.
 | 
					        byName.put(state.getUserName(), state.getAccount().getId());
 | 
				
			||||||
        //
 | 
					 | 
				
			||||||
        return missingAccount(who);
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      return state;
 | 
				
			||||||
      final Collection<AccountExternalId> externalIds =
 | 
					 | 
				
			||||||
          Collections.unmodifiableCollection(db.accountExternalIds().byAccount(
 | 
					 | 
				
			||||||
              who).toList());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      Set<AccountGroup.Id> internalGroups = new HashSet<AccountGroup.Id>();
 | 
					 | 
				
			||||||
      for (AccountGroupMember g : db.accountGroupMembers().byAccount(who)) {
 | 
					 | 
				
			||||||
        final AccountGroup.Id groupId = g.getAccountGroupId();
 | 
					 | 
				
			||||||
        final AccountGroup group = groupCache.get(groupId);
 | 
					 | 
				
			||||||
        if (group != null && group.getType() == AccountGroup.Type.INTERNAL) {
 | 
					 | 
				
			||||||
          internalGroups.add(groupId);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (internalGroups.isEmpty()) {
 | 
					 | 
				
			||||||
        internalGroups = registered;
 | 
					 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        internalGroups.addAll(registered);
 | 
					 | 
				
			||||||
        internalGroups = Collections.unmodifiableSet(internalGroups);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return new AccountState(account, internalGroups, externalIds);
 | 
					 | 
				
			||||||
    } finally {
 | 
					    } finally {
 | 
				
			||||||
      db.close();
 | 
					      db.close();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private Account.Id lookup(final String username) throws OrmException {
 | 
				
			||||||
 | 
					    final ReviewDb db = schema.open();
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      final AccountExternalId.Key key =
 | 
				
			||||||
 | 
					          new AccountExternalId.Key(AccountExternalId.SCHEME_USERNAME, username);
 | 
				
			||||||
 | 
					      final AccountExternalId id = db.accountExternalIds().get(key);
 | 
				
			||||||
 | 
					      return id != null ? id.getAccountId() : null;
 | 
				
			||||||
 | 
					    } finally {
 | 
				
			||||||
 | 
					      db.close();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private AccountState load(final ReviewDb db, final Account.Id who)
 | 
				
			||||||
 | 
					      throws OrmException {
 | 
				
			||||||
 | 
					    final Account account = db.accounts().get(who);
 | 
				
			||||||
 | 
					    if (account == null) {
 | 
				
			||||||
 | 
					      // Account no longer exists? They are anonymous.
 | 
				
			||||||
 | 
					      //
 | 
				
			||||||
 | 
					      return missingAccount(who);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final Collection<AccountExternalId> externalIds =
 | 
				
			||||||
 | 
					        Collections.unmodifiableCollection(db.accountExternalIds().byAccount(
 | 
				
			||||||
 | 
					            who).toList());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Set<AccountGroup.Id> internalGroups = new HashSet<AccountGroup.Id>();
 | 
				
			||||||
 | 
					    for (AccountGroupMember g : db.accountGroupMembers().byAccount(who)) {
 | 
				
			||||||
 | 
					      final AccountGroup.Id groupId = g.getAccountGroupId();
 | 
				
			||||||
 | 
					      final AccountGroup group = groupCache.get(groupId);
 | 
				
			||||||
 | 
					      if (group != null && group.getType() == AccountGroup.Type.INTERNAL) {
 | 
				
			||||||
 | 
					        internalGroups.add(groupId);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (internalGroups.isEmpty()) {
 | 
				
			||||||
 | 
					      internalGroups = registered;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      internalGroups.addAll(registered);
 | 
				
			||||||
 | 
					      internalGroups = Collections.unmodifiableSet(internalGroups);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return new AccountState(account, internalGroups, externalIds);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private AccountState missingAccount(final Account.Id accountId) {
 | 
					  private AccountState missingAccount(final Account.Id accountId) {
 | 
				
			||||||
    final Account account = new Account(accountId);
 | 
					    final Account account = new Account(accountId);
 | 
				
			||||||
    final Collection<AccountExternalId> ids = Collections.emptySet();
 | 
					    final Collection<AccountExternalId> ids = Collections.emptySet();
 | 
				
			||||||
@@ -126,10 +155,20 @@ public class AccountCacheImpl implements AccountCache {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public AccountState get(final Account.Id accountId) {
 | 
					  public AccountState get(final Account.Id accountId) {
 | 
				
			||||||
    return self.get(accountId);
 | 
					    return byId.get(accountId);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Override
 | 
				
			||||||
 | 
					  public AccountState getByUsername(String username) {
 | 
				
			||||||
 | 
					    Account.Id id = byName.get(username);
 | 
				
			||||||
 | 
					    return id != null ? get(id) : null;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public void evict(final Account.Id accountId) {
 | 
					  public void evict(final Account.Id accountId) {
 | 
				
			||||||
    self.remove(accountId);
 | 
					    byId.remove(accountId);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public void evictByUsername(String username) {
 | 
				
			||||||
 | 
					    byName.remove(username);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -53,6 +53,17 @@ public class AccountState {
 | 
				
			|||||||
    return account.getUserName();
 | 
					    return account.getUserName();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** @return the password matching the requested username; or null. */
 | 
				
			||||||
 | 
					  public String getPassword(String username) {
 | 
				
			||||||
 | 
					    for (AccountExternalId id : getExternalIds()) {
 | 
				
			||||||
 | 
					      if (id.isScheme(AccountExternalId.SCHEME_USERNAME)
 | 
				
			||||||
 | 
					          && username.equals(id.getSchemeRest())) {
 | 
				
			||||||
 | 
					        return id.getPassword();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * All email addresses registered to this account.
 | 
					   * All email addresses registered to this account.
 | 
				
			||||||
   * <p>
 | 
					   * <p>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -127,9 +127,11 @@ public class ChangeUserName implements Callable<VoidResult> {
 | 
				
			|||||||
    db.accountExternalIds().delete(old);
 | 
					    db.accountExternalIds().delete(old);
 | 
				
			||||||
    for (AccountExternalId i : old) {
 | 
					    for (AccountExternalId i : old) {
 | 
				
			||||||
      sshKeyCache.evict(i.getSchemeRest());
 | 
					      sshKeyCache.evict(i.getSchemeRest());
 | 
				
			||||||
 | 
					      accountCache.evictByUsername(i.getSchemeRest());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    accountCache.evict(user.getAccountId());
 | 
					    accountCache.evict(user.getAccountId());
 | 
				
			||||||
 | 
					    accountCache.evictByUsername(newUsername);
 | 
				
			||||||
    sshKeyCache.evict(newUsername);
 | 
					    sshKeyCache.evict(newUsername);
 | 
				
			||||||
    return VoidResult.INSTANCE;
 | 
					    return VoidResult.INSTANCE;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,102 @@
 | 
				
			|||||||
 | 
					// 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.sshd;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static com.google.gerrit.sshd.SshUtil.AUTH_ATTEMPTED_AS;
 | 
				
			||||||
 | 
					import static com.google.gerrit.sshd.SshUtil.AUTH_ERROR;
 | 
				
			||||||
 | 
					import static com.google.gerrit.sshd.SshUtil.CURRENT_ACCOUNT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.google.gerrit.reviewdb.AccountExternalId;
 | 
				
			||||||
 | 
					import com.google.gerrit.server.account.AccountCache;
 | 
				
			||||||
 | 
					import com.google.gerrit.server.account.AccountState;
 | 
				
			||||||
 | 
					import com.google.gerrit.sshd.SshScopes.Context;
 | 
				
			||||||
 | 
					import com.google.inject.Inject;
 | 
				
			||||||
 | 
					import com.google.inject.Singleton;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.apache.mina.core.future.IoFuture;
 | 
				
			||||||
 | 
					import org.apache.mina.core.future.IoFutureListener;
 | 
				
			||||||
 | 
					import org.apache.sshd.server.PasswordAuthenticator;
 | 
				
			||||||
 | 
					import org.apache.sshd.server.session.ServerSession;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Authenticates by password through {@link AccountExternalId} entities.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@Singleton
 | 
				
			||||||
 | 
					class DatabasePasswordAuth implements PasswordAuthenticator {
 | 
				
			||||||
 | 
					  private final AccountCache accountCache;
 | 
				
			||||||
 | 
					  private final SshLog log;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Inject
 | 
				
			||||||
 | 
					  DatabasePasswordAuth(final AccountCache ac, final SshLog l) {
 | 
				
			||||||
 | 
					    accountCache = ac;
 | 
				
			||||||
 | 
					    log = l;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Override
 | 
				
			||||||
 | 
					  public boolean authenticate(final String username, final String password,
 | 
				
			||||||
 | 
					      final ServerSession session) {
 | 
				
			||||||
 | 
					    AccountState state = accountCache.getByUsername(username);
 | 
				
			||||||
 | 
					    if (state == null) {
 | 
				
			||||||
 | 
					      return fail(username, session, "user-not-found");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final String p = state.getPassword(username);
 | 
				
			||||||
 | 
					    if (p == null) {
 | 
				
			||||||
 | 
					      return fail(username, session, "no-password");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!p.equals(password)) {
 | 
				
			||||||
 | 
					      return fail(username, session, "incorrect-password");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (session.setAttribute(CURRENT_ACCOUNT, state.getAccount().getId()) == null) {
 | 
				
			||||||
 | 
					      // If this is the first time we've authenticated this
 | 
				
			||||||
 | 
					      // session, record a login event in the log and add
 | 
				
			||||||
 | 
					      // a close listener to record a logout event.
 | 
				
			||||||
 | 
					      //
 | 
				
			||||||
 | 
					      final Context ctx = new Context(session);
 | 
				
			||||||
 | 
					      final Context old = SshScopes.current.get();
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        SshScopes.current.set(ctx);
 | 
				
			||||||
 | 
					        log.onLogin();
 | 
				
			||||||
 | 
					      } finally {
 | 
				
			||||||
 | 
					        SshScopes.current.set(old);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      session.getIoSession().getCloseFuture().addListener(
 | 
				
			||||||
 | 
					          new IoFutureListener<IoFuture>() {
 | 
				
			||||||
 | 
					            @Override
 | 
				
			||||||
 | 
					            public void operationComplete(IoFuture future) {
 | 
				
			||||||
 | 
					              final Context old = SshScopes.current.get();
 | 
				
			||||||
 | 
					              try {
 | 
				
			||||||
 | 
					                SshScopes.current.set(ctx);
 | 
				
			||||||
 | 
					                log.onLogout();
 | 
				
			||||||
 | 
					              } finally {
 | 
				
			||||||
 | 
					                SshScopes.current.set(old);
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static boolean fail(final String username,
 | 
				
			||||||
 | 
					      final ServerSession session, final String err) {
 | 
				
			||||||
 | 
					    session.setAttribute(AUTH_ATTEMPTED_AS, username);
 | 
				
			||||||
 | 
					    session.setAttribute(AUTH_ERROR, err);
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -32,10 +32,6 @@ import java.security.PublicKey;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Authenticates by public key through {@link AccountSshKey} entities.
 | 
					 * Authenticates by public key through {@link AccountSshKey} entities.
 | 
				
			||||||
 * <p>
 | 
					 | 
				
			||||||
 * The username supplied by the client must be the user's preferred email
 | 
					 | 
				
			||||||
 * address, as listed in their Account entity. Only keys listed under that
 | 
					 | 
				
			||||||
 * account as authorized keys are permitted to access the account.
 | 
					 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@Singleton
 | 
					@Singleton
 | 
				
			||||||
class DatabasePubKeyAuth implements PublickeyAuthenticator {
 | 
					class DatabasePubKeyAuth implements PublickeyAuthenticator {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -60,8 +60,10 @@ import org.apache.sshd.common.util.SecurityUtils;
 | 
				
			|||||||
import org.apache.sshd.server.Command;
 | 
					import org.apache.sshd.server.Command;
 | 
				
			||||||
import org.apache.sshd.server.CommandFactory;
 | 
					import org.apache.sshd.server.CommandFactory;
 | 
				
			||||||
import org.apache.sshd.server.ForwardingFilter;
 | 
					import org.apache.sshd.server.ForwardingFilter;
 | 
				
			||||||
 | 
					import org.apache.sshd.server.PasswordAuthenticator;
 | 
				
			||||||
import org.apache.sshd.server.PublickeyAuthenticator;
 | 
					import org.apache.sshd.server.PublickeyAuthenticator;
 | 
				
			||||||
import org.apache.sshd.server.UserAuth;
 | 
					import org.apache.sshd.server.UserAuth;
 | 
				
			||||||
 | 
					import org.apache.sshd.server.auth.UserAuthPassword;
 | 
				
			||||||
import org.apache.sshd.server.auth.UserAuthPublicKey;
 | 
					import org.apache.sshd.server.auth.UserAuthPublicKey;
 | 
				
			||||||
import org.apache.sshd.server.channel.ChannelDirectTcpip;
 | 
					import org.apache.sshd.server.channel.ChannelDirectTcpip;
 | 
				
			||||||
import org.apache.sshd.server.channel.ChannelSession;
 | 
					import org.apache.sshd.server.channel.ChannelSession;
 | 
				
			||||||
@@ -119,6 +121,7 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @Inject
 | 
					  @Inject
 | 
				
			||||||
  SshDaemon(final CommandFactory commandFactory,
 | 
					  SshDaemon(final CommandFactory commandFactory,
 | 
				
			||||||
 | 
					      final PasswordAuthenticator passAuth,
 | 
				
			||||||
      final PublickeyAuthenticator userAuth,
 | 
					      final PublickeyAuthenticator userAuth,
 | 
				
			||||||
      final KeyPairProvider hostKeyProvider, final IdGenerator idGenerator,
 | 
					      final KeyPairProvider hostKeyProvider, final IdGenerator idGenerator,
 | 
				
			||||||
      @GerritServerConfig final Config cfg, final SshLog sshLog) {
 | 
					      @GerritServerConfig final Config cfg, final SshLog sshLog) {
 | 
				
			||||||
@@ -140,7 +143,7 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
 | 
				
			|||||||
    initForwardingFilter();
 | 
					    initForwardingFilter();
 | 
				
			||||||
    initSubsystems();
 | 
					    initSubsystems();
 | 
				
			||||||
    initCompression();
 | 
					    initCompression();
 | 
				
			||||||
    initUserAuth(userAuth);
 | 
					    initUserAuth(passAuth, userAuth);
 | 
				
			||||||
    setKeyPairProvider(hostKeyProvider);
 | 
					    setKeyPairProvider(hostKeyProvider);
 | 
				
			||||||
    setCommandFactory(commandFactory);
 | 
					    setCommandFactory(commandFactory);
 | 
				
			||||||
    setShellFactory(new NoShell());
 | 
					    setShellFactory(new NoShell());
 | 
				
			||||||
@@ -452,9 +455,11 @@ public class SshDaemon extends SshServer implements SshInfo, LifecycleListener {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @SuppressWarnings("unchecked")
 | 
					  @SuppressWarnings("unchecked")
 | 
				
			||||||
  private void initUserAuth(final PublickeyAuthenticator pubkey) {
 | 
					  private void initUserAuth(final PasswordAuthenticator pass,
 | 
				
			||||||
    setUserAuthFactories(Arrays
 | 
					      final PublickeyAuthenticator pubkey) {
 | 
				
			||||||
        .<NamedFactory<UserAuth>> asList(new UserAuthPublicKey.Factory()));
 | 
					    setUserAuthFactories(Arrays.<NamedFactory<UserAuth>> asList(
 | 
				
			||||||
 | 
					        new UserAuthPublicKey.Factory(), new UserAuthPassword.Factory()));
 | 
				
			||||||
 | 
					    setPasswordAuthenticator(pass);
 | 
				
			||||||
    setPublickeyAuthenticator(pubkey);
 | 
					    setPublickeyAuthenticator(pubkey);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -47,6 +47,7 @@ import com.google.inject.servlet.SessionScoped;
 | 
				
			|||||||
import org.apache.sshd.common.KeyPairProvider;
 | 
					import org.apache.sshd.common.KeyPairProvider;
 | 
				
			||||||
import org.apache.sshd.common.session.AbstractSession;
 | 
					import org.apache.sshd.common.session.AbstractSession;
 | 
				
			||||||
import org.apache.sshd.server.CommandFactory;
 | 
					import org.apache.sshd.server.CommandFactory;
 | 
				
			||||||
 | 
					import org.apache.sshd.server.PasswordAuthenticator;
 | 
				
			||||||
import org.apache.sshd.server.PublickeyAuthenticator;
 | 
					import org.apache.sshd.server.PublickeyAuthenticator;
 | 
				
			||||||
import org.apache.sshd.server.session.ServerSession;
 | 
					import org.apache.sshd.server.session.ServerSession;
 | 
				
			||||||
import org.kohsuke.args4j.spi.OptionHandler;
 | 
					import org.kohsuke.args4j.spi.OptionHandler;
 | 
				
			||||||
@@ -81,6 +82,7 @@ public class SshModule extends FactoryModule {
 | 
				
			|||||||
        .toProvider(CommandExecutorProvider.class).in(SINGLETON);
 | 
					        .toProvider(CommandExecutorProvider.class).in(SINGLETON);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bind(PublickeyAuthenticator.class).to(DatabasePubKeyAuth.class);
 | 
					    bind(PublickeyAuthenticator.class).to(DatabasePubKeyAuth.class);
 | 
				
			||||||
 | 
					    bind(PasswordAuthenticator.class).to(DatabasePasswordAuth.class);
 | 
				
			||||||
    bind(KeyPairProvider.class).toProvider(HostKeyProvider.class).in(SINGLETON);
 | 
					    bind(KeyPairProvider.class).toProvider(HostKeyProvider.class).in(SINGLETON);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    install(new DefaultCommandModule());
 | 
					    install(new DefaultCommandModule());
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user