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.gerrit.server.account.externalids.ExternalId.SCHEME_EXTERNAL; 18 : import static java.nio.charset.StandardCharsets.UTF_8; 19 : 20 : import com.google.common.flogger.FluentLogger; 21 : import com.google.gerrit.common.PageLinks; 22 : import com.google.gerrit.extensions.registration.DynamicItem; 23 : import com.google.gerrit.httpd.CanonicalWebUrl; 24 : import com.google.gerrit.httpd.HtmlDomUtil; 25 : import com.google.gerrit.httpd.LoginUrlToken; 26 : import com.google.gerrit.httpd.WebSession; 27 : import com.google.gerrit.server.account.AccountException; 28 : import com.google.gerrit.server.account.AccountManager; 29 : import com.google.gerrit.server.account.AuthRequest; 30 : import com.google.gerrit.server.account.AuthResult; 31 : import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory; 32 : import com.google.gerrit.server.config.AuthConfig; 33 : import com.google.gerrit.util.http.CacheHeaders; 34 : import com.google.inject.Inject; 35 : import com.google.inject.Singleton; 36 : import java.io.IOException; 37 : import javax.servlet.ServletException; 38 : import javax.servlet.ServletOutputStream; 39 : import javax.servlet.http.HttpServlet; 40 : import javax.servlet.http.HttpServletRequest; 41 : import javax.servlet.http.HttpServletResponse; 42 : import org.eclipse.jgit.errors.ConfigInvalidException; 43 : import org.w3c.dom.Document; 44 : import org.w3c.dom.Element; 45 : import org.w3c.dom.Node; 46 : import org.w3c.dom.NodeList; 47 : 48 : /** 49 : * Initializes the user session if HTTP authentication is enabled. 50 : * 51 : * <p>If HTTP authentication has been enabled this servlet binds to {@code /login/} and initializes 52 : * the user session based on user information contained in the HTTP request. 53 : */ 54 : @Singleton 55 : class HttpLoginServlet extends HttpServlet { 56 : private static final long serialVersionUID = 1L; 57 0 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 58 : 59 : private final DynamicItem<WebSession> webSession; 60 : private final CanonicalWebUrl urlProvider; 61 : private final AccountManager accountManager; 62 : private final HttpAuthFilter authFilter; 63 : private final AuthConfig authConfig; 64 : private final ExternalIdKeyFactory externalIdKeyFactory; 65 : private final AuthRequest.Factory authRequestFactory; 66 : 67 : @Inject 68 : HttpLoginServlet( 69 : final DynamicItem<WebSession> webSession, 70 : final CanonicalWebUrl urlProvider, 71 : final AccountManager accountManager, 72 : final HttpAuthFilter authFilter, 73 : final AuthConfig authConfig, 74 : final ExternalIdKeyFactory externalIdKeyFactory, 75 0 : final AuthRequest.Factory authRequestFactory) { 76 0 : this.webSession = webSession; 77 0 : this.urlProvider = urlProvider; 78 0 : this.accountManager = accountManager; 79 0 : this.authFilter = authFilter; 80 0 : this.authConfig = authConfig; 81 0 : this.externalIdKeyFactory = externalIdKeyFactory; 82 0 : this.authRequestFactory = authRequestFactory; 83 0 : } 84 : 85 : @Override 86 : protected void doGet(HttpServletRequest req, HttpServletResponse rsp) 87 : throws ServletException, IOException { 88 0 : final String token = LoginUrlToken.getToken(req); 89 : 90 0 : CacheHeaders.setNotCacheable(rsp); 91 0 : final String user = authFilter.getRemoteUser(req); 92 0 : if (user == null || "".equals(user)) { 93 0 : logger.atSevere().log( 94 : "Unable to authenticate user by %s request header." 95 : + " Check container or server configuration.", 96 0 : authFilter.getLoginHeader()); 97 : 98 0 : final Document doc = 99 0 : HtmlDomUtil.parseFile( // 100 : HttpLoginServlet.class, "ConfigurationError.html"); 101 : 102 0 : replace(doc, "loginHeader", authFilter.getLoginHeader()); 103 0 : replace(doc, "ServerName", req.getServerName()); 104 0 : replace(doc, "ServerPort", ":" + req.getServerPort()); 105 0 : replace(doc, "ContextPath", req.getContextPath()); 106 : 107 0 : final byte[] bin = HtmlDomUtil.toUTF8(doc); 108 0 : rsp.setStatus(HttpServletResponse.SC_FORBIDDEN); 109 0 : rsp.setContentType("text/html"); 110 0 : rsp.setCharacterEncoding(UTF_8.name()); 111 0 : rsp.setContentLength(bin.length); 112 0 : try (ServletOutputStream out = rsp.getOutputStream()) { 113 0 : out.write(bin); 114 : } 115 0 : return; 116 : } 117 : 118 0 : final AuthRequest areq = authRequestFactory.createForUser(user); 119 0 : areq.setDisplayName(authFilter.getRemoteDisplayname(req)); 120 0 : areq.setEmailAddress(authFilter.getRemoteEmail(req)); 121 : final AuthResult arsp; 122 : try { 123 0 : arsp = accountManager.authenticate(areq); 124 0 : } catch (AccountException e) { 125 0 : logger.atSevere().withCause(e).log("Unable to authenticate user \"%s\"", user); 126 0 : rsp.sendError(HttpServletResponse.SC_FORBIDDEN); 127 0 : return; 128 0 : } 129 : 130 0 : String remoteExternalId = authFilter.getRemoteExternalIdToken(req); 131 0 : if (remoteExternalId != null) { 132 : try { 133 0 : logger.atFine().log( 134 : "Associating external identity \"%s\" to user \"%s\"", remoteExternalId, user); 135 0 : updateRemoteExternalId(arsp, remoteExternalId); 136 0 : } catch (AccountException | ConfigInvalidException e) { 137 0 : logger.atSevere().withCause(e).log( 138 : "Unable to associate external identity \"%s\" to user \"%s\"", remoteExternalId, user); 139 0 : rsp.sendError(HttpServletResponse.SC_FORBIDDEN); 140 0 : return; 141 0 : } 142 : } 143 : 144 0 : final StringBuilder rdr = new StringBuilder(); 145 0 : if (arsp.isNew() && authConfig.getRegisterPageUrl() != null) { 146 0 : rdr.append(authConfig.getRegisterPageUrl()); 147 : } else { 148 0 : rdr.append(urlProvider.get(req)); 149 0 : if (arsp.isNew() && !token.startsWith(PageLinks.REGISTER + "/")) { 150 0 : rdr.append('#' + PageLinks.REGISTER); 151 : } 152 0 : rdr.append(token); 153 : } 154 : 155 0 : webSession.get().login(arsp, true /* persistent cookie */); 156 0 : rsp.sendRedirect(rdr.toString()); 157 0 : } 158 : 159 : private void updateRemoteExternalId(AuthResult arsp, String remoteAuthToken) 160 : throws AccountException, IOException, ConfigInvalidException { 161 0 : accountManager.updateLink( 162 0 : arsp.getAccountId(), 163 0 : authRequestFactory.create(externalIdKeyFactory.create(SCHEME_EXTERNAL, remoteAuthToken))); 164 0 : } 165 : 166 : private void replace(Document doc, String name, String value) { 167 0 : Element e = HtmlDomUtil.find(doc, name); 168 0 : if (e != null) { 169 0 : e.setTextContent(value); 170 : } else { 171 0 : replaceByClass(doc, name, value); 172 : } 173 0 : } 174 : 175 : private void replaceByClass(Node parent, String name, String value) { 176 0 : final NodeList list = parent.getChildNodes(); 177 0 : for (int i = 0; i < list.getLength(); i++) { 178 0 : final Node n = list.item(i); 179 0 : if (n instanceof Element) { 180 0 : final Element e = (Element) n; 181 0 : if (name.equals(e.getAttribute("class"))) { 182 0 : e.setTextContent(value); 183 : } 184 : } 185 0 : replaceByClass(n, name, value); 186 : } 187 0 : } 188 : }