Line data Source code
1 : // Copyright (C) 2009 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.auth.container; 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.server.account.externalids.ExternalId.SCHEME_GERRIT; 21 : import static java.nio.charset.StandardCharsets.ISO_8859_1; 22 : import static java.nio.charset.StandardCharsets.UTF_8; 23 : 24 : import com.google.gerrit.common.Nullable; 25 : import com.google.gerrit.extensions.registration.DynamicItem; 26 : import com.google.gerrit.httpd.HtmlDomUtil; 27 : import com.google.gerrit.httpd.RemoteUserUtil; 28 : import com.google.gerrit.httpd.WebSession; 29 : import com.google.gerrit.server.account.externalids.ExternalId; 30 : import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory; 31 : import com.google.gerrit.server.config.AuthConfig; 32 : import com.google.gerrit.util.http.CacheHeaders; 33 : import com.google.gerrit.util.http.RequestUtil; 34 : import com.google.inject.Inject; 35 : import com.google.inject.Singleton; 36 : import java.io.FileNotFoundException; 37 : import java.io.IOException; 38 : import java.io.OutputStream; 39 : import java.util.Locale; 40 : import java.util.Optional; 41 : import javax.servlet.Filter; 42 : import javax.servlet.FilterChain; 43 : import javax.servlet.FilterConfig; 44 : import javax.servlet.ServletException; 45 : import javax.servlet.ServletRequest; 46 : import javax.servlet.ServletResponse; 47 : import javax.servlet.http.HttpServletRequest; 48 : import javax.servlet.http.HttpServletResponse; 49 : 50 : /** 51 : * Watches request for the host page and requires login if not yet signed in. 52 : * 53 : * <p>If HTTP authentication has been enabled on this server this filter is bound in front of the 54 : * Gerrit and redirects users who are not yet signed in to visit {@code /login/}, so the web 55 : * container can force login. This redirect is performed with JavaScript, such that any existing 56 : * anchor token in the URL can be rewritten and preserved through the authentication process of any 57 : * enterprise single sign-on solutions. 58 : */ 59 : @Singleton 60 : class HttpAuthFilter implements Filter { 61 : private final DynamicItem<WebSession> sessionProvider; 62 : private final byte[] signInRaw; 63 : private final byte[] signInGzip; 64 : private final String loginHeader; 65 : private final String displaynameHeader; 66 : private final String emailHeader; 67 : private final String externalIdHeader; 68 : private final boolean userNameToLowerCase; 69 : private final ExternalIdKeyFactory externalIdKeyFactory; 70 : 71 : @Inject 72 : HttpAuthFilter( 73 : DynamicItem<WebSession> webSession, 74 : AuthConfig authConfig, 75 : ExternalIdKeyFactory externalIdKeyFactory) 76 0 : throws IOException { 77 0 : this.sessionProvider = webSession; 78 0 : this.externalIdKeyFactory = externalIdKeyFactory; 79 : 80 0 : final String pageName = "LoginRedirect.html"; 81 0 : final String doc = HtmlDomUtil.readFile(getClass(), pageName); 82 0 : if (doc == null) { 83 0 : throw new FileNotFoundException("No " + pageName + " in webapp"); 84 : } 85 : 86 0 : signInRaw = doc.getBytes(HtmlDomUtil.ENC); 87 0 : signInGzip = HtmlDomUtil.compress(signInRaw); 88 0 : loginHeader = firstNonNull(emptyToNull(authConfig.getLoginHttpHeader()), AUTHORIZATION); 89 0 : displaynameHeader = emptyToNull(authConfig.getHttpDisplaynameHeader()); 90 0 : emailHeader = emptyToNull(authConfig.getHttpEmailHeader()); 91 0 : externalIdHeader = emptyToNull(authConfig.getHttpExternalIdHeader()); 92 0 : userNameToLowerCase = authConfig.isUserNameToLowerCase(); 93 0 : } 94 : 95 : @Override 96 : public void doFilter(final ServletRequest request, ServletResponse response, FilterChain chain) 97 : throws IOException, ServletException { 98 0 : if (isSessionValid((HttpServletRequest) request)) { 99 0 : chain.doFilter(request, response); 100 : } else { 101 : // Not signed in yet. Since the browser state might have an anchor 102 : // token which we want to capture and carry through the auth process 103 : // we send back JavaScript now to capture that, and do the real work 104 : // of redirecting to the authentication area. 105 : // 106 0 : final HttpServletRequest req = (HttpServletRequest) request; 107 0 : final HttpServletResponse rsp = (HttpServletResponse) response; 108 : final byte[] tosend; 109 0 : if (RequestUtil.acceptsGzipEncoding(req)) { 110 0 : rsp.setHeader("Content-Encoding", "gzip"); 111 0 : tosend = signInGzip; 112 : } else { 113 0 : tosend = signInRaw; 114 : } 115 : 116 0 : CacheHeaders.setNotCacheable(rsp); 117 0 : rsp.setContentType("text/html"); 118 0 : rsp.setCharacterEncoding(HtmlDomUtil.ENC.name()); 119 0 : rsp.setContentLength(tosend.length); 120 0 : try (OutputStream out = rsp.getOutputStream()) { 121 0 : out.write(tosend); 122 : } 123 : } 124 0 : } 125 : 126 : private boolean isSessionValid(HttpServletRequest req) { 127 0 : WebSession session = sessionProvider.get(); 128 0 : if (session.isSignedIn()) { 129 0 : String user = getRemoteUser(req); 130 0 : return user == null || correctUser(user, session); 131 : } 132 0 : return false; 133 : } 134 : 135 : private boolean correctUser(String user, WebSession session) { 136 0 : Optional<ExternalId.Key> id = session.getUser().getLastLoginExternalIdKey(); 137 0 : return id.map(i -> i.equals(externalIdKeyFactory.create(SCHEME_GERRIT, user))).orElse(false); 138 : } 139 : 140 : String getRemoteUser(HttpServletRequest req) { 141 0 : String remoteUser = RemoteUserUtil.getRemoteUser(req, loginHeader); 142 0 : return (userNameToLowerCase && remoteUser != null) 143 0 : ? remoteUser.toLowerCase(Locale.US) 144 0 : : remoteUser; 145 : } 146 : 147 : @Nullable 148 : String getRemoteDisplayname(HttpServletRequest req) { 149 0 : if (displaynameHeader != null) { 150 0 : String raw = req.getHeader(displaynameHeader); 151 0 : return emptyToNull(new String(raw.getBytes(ISO_8859_1), UTF_8)); 152 : } 153 0 : return null; 154 : } 155 : 156 : @Nullable 157 : String getRemoteEmail(HttpServletRequest req) { 158 0 : if (emailHeader != null) { 159 0 : return emptyToNull(req.getHeader(emailHeader)); 160 : } 161 0 : return null; 162 : } 163 : 164 : @Nullable 165 : String getRemoteExternalIdToken(HttpServletRequest req) { 166 0 : if (externalIdHeader != null) { 167 0 : return emptyToNull(req.getHeader(externalIdHeader)); 168 : } 169 0 : return null; 170 : } 171 : 172 : String getLoginHeader() { 173 0 : return loginHeader; 174 : } 175 : 176 : @Override 177 0 : public void init(FilterConfig filterConfig) {} 178 : 179 : @Override 180 0 : public void destroy() {} 181 : }