| // Copyright (C) 2009 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.auth.container; | |
| import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_EXTERNAL; | |
| import static java.nio.charset.StandardCharsets.UTF_8; | |
| import com.google.common.flogger.FluentLogger; | |
| import com.google.gerrit.common.PageLinks; | |
| import com.google.gerrit.extensions.registration.DynamicItem; | |
| import com.google.gerrit.httpd.CanonicalWebUrl; | |
| import com.google.gerrit.httpd.HtmlDomUtil; | |
| import com.google.gerrit.httpd.LoginUrlToken; | |
| import com.google.gerrit.httpd.WebSession; | |
| import com.google.gerrit.server.account.AccountException; | |
| import com.google.gerrit.server.account.AccountManager; | |
| import com.google.gerrit.server.account.AuthRequest; | |
| import com.google.gerrit.server.account.AuthResult; | |
| import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory; | |
| import com.google.gerrit.server.config.AuthConfig; | |
| import com.google.gerrit.util.http.CacheHeaders; | |
| import com.google.inject.Inject; | |
| import com.google.inject.Singleton; | |
| import java.io.IOException; | |
| import javax.servlet.ServletException; | |
| import javax.servlet.ServletOutputStream; | |
| import javax.servlet.http.HttpServlet; | |
| import javax.servlet.http.HttpServletRequest; | |
| import javax.servlet.http.HttpServletResponse; | |
| import org.eclipse.jgit.errors.ConfigInvalidException; | |
| import org.w3c.dom.Document; | |
| import org.w3c.dom.Element; | |
| import org.w3c.dom.Node; | |
| import org.w3c.dom.NodeList; | |
| /** | |
| * Initializes the user session if HTTP authentication is enabled. | |
| * | |
| * <p>If HTTP authentication has been enabled this servlet binds to {@code /login/} and initializes | |
| * the user session based on user information contained in the HTTP request. | |
| */ | |
| @Singleton | |
| class HttpLoginServlet extends HttpServlet { | |
| private static final long serialVersionUID = 1L; | |
| private static final FluentLogger logger = FluentLogger.forEnclosingClass(); | |
| private final DynamicItem<WebSession> webSession; | |
| private final CanonicalWebUrl urlProvider; | |
| private final AccountManager accountManager; | |
| private final HttpAuthFilter authFilter; | |
| private final AuthConfig authConfig; | |
| private final ExternalIdKeyFactory externalIdKeyFactory; | |
| private final AuthRequest.Factory authRequestFactory; | |
| @Inject | |
| HttpLoginServlet( | |
| final DynamicItem<WebSession> webSession, | |
| final CanonicalWebUrl urlProvider, | |
| final AccountManager accountManager, | |
| final HttpAuthFilter authFilter, | |
| final AuthConfig authConfig, | |
| final ExternalIdKeyFactory externalIdKeyFactory, | |
| final AuthRequest.Factory authRequestFactory) { | |
| this.webSession = webSession; | |
| this.urlProvider = urlProvider; | |
| this.accountManager = accountManager; | |
| this.authFilter = authFilter; | |
| this.authConfig = authConfig; | |
| this.externalIdKeyFactory = externalIdKeyFactory; | |
| this.authRequestFactory = authRequestFactory; | |
| } | |
| @Override | |
| protected void doGet(HttpServletRequest req, HttpServletResponse rsp) | |
| throws ServletException, IOException { | |
| final String token = LoginUrlToken.getToken(req); | |
| CacheHeaders.setNotCacheable(rsp); | |
| final String user = authFilter.getRemoteUser(req); | |
| if (user == null || "".equals(user)) { | |
| logger.atSevere().log( | |
| "Unable to authenticate user by %s request header." | |
| + " Check container or server configuration.", | |
| authFilter.getLoginHeader()); | |
| final Document doc = | |
| HtmlDomUtil.parseFile( // | |
| HttpLoginServlet.class, "ConfigurationError.html"); | |
| replace(doc, "loginHeader", authFilter.getLoginHeader()); | |
| replace(doc, "ServerName", req.getServerName()); | |
| replace(doc, "ServerPort", ":" + req.getServerPort()); | |
| replace(doc, "ContextPath", req.getContextPath()); | |
| final byte[] bin = HtmlDomUtil.toUTF8(doc); | |
| rsp.setStatus(HttpServletResponse.SC_FORBIDDEN); | |
| rsp.setContentType("text/html"); | |
| rsp.setCharacterEncoding(UTF_8.name()); | |
| rsp.setContentLength(bin.length); | |
| try (ServletOutputStream out = rsp.getOutputStream()) { | |
| out.write(bin); | |
| } | |
| return; | |
| } | |
| final AuthRequest areq = authRequestFactory.createForUser(user); | |
| areq.setDisplayName(authFilter.getRemoteDisplayname(req)); | |
| areq.setEmailAddress(authFilter.getRemoteEmail(req)); | |
| final AuthResult arsp; | |
| try { | |
| arsp = accountManager.authenticate(areq); | |
| } catch (AccountException e) { | |
| logger.atSevere().withCause(e).log("Unable to authenticate user \"%s\"", user); | |
| rsp.sendError(HttpServletResponse.SC_FORBIDDEN); | |
| return; | |
| } | |
| String remoteExternalId = authFilter.getRemoteExternalIdToken(req); | |
| if (remoteExternalId != null) { | |
| try { | |
| logger.atFine().log( | |
| "Associating external identity \"%s\" to user \"%s\"", remoteExternalId, user); | |
| updateRemoteExternalId(arsp, remoteExternalId); | |
| } catch (AccountException | ConfigInvalidException e) { | |
| logger.atSevere().withCause(e).log( | |
| "Unable to associate external identity \"%s\" to user \"%s\"", remoteExternalId, user); | |
| rsp.sendError(HttpServletResponse.SC_FORBIDDEN); | |
| return; | |
| } | |
| } | |
| final StringBuilder rdr = new StringBuilder(); | |
| if (arsp.isNew() && authConfig.getRegisterPageUrl() != null) { | |
| rdr.append(authConfig.getRegisterPageUrl()); | |
| } else { | |
| rdr.append(urlProvider.get(req)); | |
| if (arsp.isNew() && !token.startsWith(PageLinks.REGISTER + "/")) { | |
| rdr.append('#' + PageLinks.REGISTER); | |
| } | |
| rdr.append(token); | |
| } | |
| webSession.get().login(arsp, true /* persistent cookie */); | |
| rsp.sendRedirect(rdr.toString()); | |
| } | |
| private void updateRemoteExternalId(AuthResult arsp, String remoteAuthToken) | |
| throws AccountException, IOException, ConfigInvalidException { | |
| accountManager.updateLink( | |
| arsp.getAccountId(), | |
| authRequestFactory.create(externalIdKeyFactory.create(SCHEME_EXTERNAL, remoteAuthToken))); | |
| } | |
| private void replace(Document doc, String name, String value) { | |
| Element e = HtmlDomUtil.find(doc, name); | |
| if (e != null) { | |
| e.setTextContent(value); | |
| } else { | |
| replaceByClass(doc, name, value); | |
| } | |
| } | |
| private void replaceByClass(Node parent, String name, String value) { | |
| final NodeList list = parent.getChildNodes(); | |
| for (int i = 0; i < list.getLength(); i++) { | |
| final Node n = list.item(i); | |
| if (n instanceof Element) { | |
| final Element e = (Element) n; | |
| if (name.equals(e.getAttribute("class"))) { | |
| e.setTextContent(value); | |
| } | |
| } | |
| replaceByClass(n, name, value); | |
| } | |
| } | |
| } |