Line data Source code
1 : // Copyright (C) 2011 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.httpd; 16 : 17 : import static com.google.common.base.MoreObjects.firstNonNull; 18 : import static com.google.common.base.Strings.emptyToNull; 19 : import static com.google.common.net.HttpHeaders.AUTHORIZATION; 20 : import static com.google.gerrit.extensions.api.lfs.LfsDefinitions.CONTENTTYPE_VND_GIT_LFS_JSON; 21 : import static com.google.gerrit.httpd.GerritAuthModule.NOT_AUTHORIZED_LFS_URL_REGEX; 22 : import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN; 23 : import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED; 24 : 25 : import com.google.common.base.Strings; 26 : import com.google.gerrit.extensions.registration.DynamicItem; 27 : import com.google.gerrit.httpd.restapi.RestApiServlet; 28 : import com.google.gerrit.server.AccessPath; 29 : import com.google.gerrit.server.account.AccountCache; 30 : import com.google.gerrit.server.account.AccountState; 31 : import com.google.gerrit.server.config.AuthConfig; 32 : import com.google.gerrit.server.config.GerritServerConfig; 33 : import com.google.inject.Inject; 34 : import com.google.inject.Singleton; 35 : import java.io.IOException; 36 : import java.util.Locale; 37 : import java.util.Optional; 38 : import java.util.regex.Pattern; 39 : import javax.servlet.Filter; 40 : import javax.servlet.FilterChain; 41 : import javax.servlet.FilterConfig; 42 : import javax.servlet.ServletException; 43 : import javax.servlet.ServletRequest; 44 : import javax.servlet.ServletResponse; 45 : import javax.servlet.http.HttpServletRequest; 46 : import javax.servlet.http.HttpServletResponse; 47 : import org.eclipse.jgit.lib.Config; 48 : 49 : /** 50 : * Trust the authentication which is done by the container. 51 : * 52 : * <p>Check whether the container has already authenticated the user. If yes, then lookup the 53 : * account and set the account ID in our current session. 54 : * 55 : * <p>This filter should only be configured to run, when authentication is configured to trust 56 : * container authentication. This filter is intended to protect the {@link GitOverHttpServlet} and 57 : * its handled URLs, which provide remote repository access over HTTP. It also protects {@link 58 : * RestApiServlet}. 59 : */ 60 : @Singleton 61 : class ContainerAuthFilter implements Filter { 62 : private static final String LFS_AUTH_PREFIX = "Ssh: "; 63 0 : private static final Pattern LFS_ENDPOINT = Pattern.compile(NOT_AUTHORIZED_LFS_URL_REGEX); 64 : 65 : private final DynamicItem<WebSession> session; 66 : private final AccountCache accountCache; 67 : private final Config config; 68 : private final String loginHttpHeader; 69 : 70 : @Inject 71 : ContainerAuthFilter( 72 : DynamicItem<WebSession> session, 73 : AccountCache accountCache, 74 : AuthConfig authConfig, 75 0 : @GerritServerConfig Config config) { 76 0 : this.session = session; 77 0 : this.accountCache = accountCache; 78 0 : this.config = config; 79 : 80 0 : loginHttpHeader = firstNonNull(emptyToNull(authConfig.getLoginHttpHeader()), AUTHORIZATION); 81 0 : } 82 : 83 : @Override 84 0 : public void init(FilterConfig config) {} 85 : 86 : @Override 87 0 : public void destroy() {} 88 : 89 : @Override 90 : public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 91 : throws IOException, ServletException { 92 0 : HttpServletRequest req = (HttpServletRequest) request; 93 0 : HttpServletResponse rsp = (HttpServletResponse) response; 94 : 95 0 : if (verify(req, rsp)) { 96 0 : chain.doFilter(req, response); 97 : } 98 0 : } 99 : 100 : private boolean verify(HttpServletRequest req, HttpServletResponse rsp) throws IOException { 101 0 : String username = RemoteUserUtil.getRemoteUser(req, loginHttpHeader); 102 0 : if (username == null) { 103 0 : if (isLfsOverSshRequest(req)) { 104 : // LFS-over-SSH auth request cannot be authorized by container 105 : // therefore let it go through the filter 106 0 : return true; 107 : } 108 0 : rsp.sendError(SC_FORBIDDEN); 109 0 : return false; 110 : } 111 0 : if (config.getBoolean("auth", "userNameToLowerCase", false)) { 112 0 : username = username.toLowerCase(Locale.US); 113 : } 114 0 : Optional<AccountState> who = 115 0 : accountCache.getByUsername(username).filter(a -> a.account().isActive()); 116 0 : if (!who.isPresent()) { 117 0 : rsp.sendError(SC_UNAUTHORIZED); 118 0 : return false; 119 : } 120 0 : WebSession ws = session.get(); 121 0 : ws.setUserAccountId(who.get().account().id()); 122 0 : ws.setAccessPathOk(AccessPath.GIT, true); 123 0 : ws.setAccessPathOk(AccessPath.REST_API, true); 124 0 : return true; 125 : } 126 : 127 : private static boolean isLfsOverSshRequest(HttpServletRequest req) { 128 0 : String hdr = req.getHeader(AUTHORIZATION); 129 0 : return CONTENTTYPE_VND_GIT_LFS_JSON.equals(req.getContentType()) 130 0 : && !Strings.isNullOrEmpty(hdr) 131 0 : && hdr.startsWith(LFS_AUTH_PREFIX) 132 0 : && LFS_ENDPOINT.matcher(req.getRequestURI()).matches(); 133 : } 134 : }