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.ldap; 16 : 17 : import static java.nio.charset.StandardCharsets.UTF_8; 18 : 19 : import com.google.common.base.MoreObjects; 20 : import com.google.common.base.Strings; 21 : import com.google.common.flogger.FluentLogger; 22 : import com.google.gerrit.common.Nullable; 23 : import com.google.gerrit.extensions.registration.DynamicItem; 24 : import com.google.gerrit.httpd.CanonicalWebUrl; 25 : import com.google.gerrit.httpd.HtmlDomUtil; 26 : import com.google.gerrit.httpd.LoginUrlToken; 27 : import com.google.gerrit.httpd.WebSession; 28 : import com.google.gerrit.httpd.template.SiteHeaderFooter; 29 : import com.google.gerrit.server.account.AccountException; 30 : import com.google.gerrit.server.account.AccountManager; 31 : import com.google.gerrit.server.account.AccountUserNameException; 32 : import com.google.gerrit.server.account.AuthRequest; 33 : import com.google.gerrit.server.account.AuthResult; 34 : import com.google.gerrit.server.account.AuthenticationFailedException; 35 : import com.google.gerrit.server.auth.AuthenticationUnavailableException; 36 : import com.google.gerrit.util.http.CacheHeaders; 37 : import com.google.inject.Inject; 38 : import com.google.inject.Singleton; 39 : import java.io.IOException; 40 : import javax.servlet.ServletException; 41 : import javax.servlet.ServletOutputStream; 42 : import javax.servlet.http.HttpServlet; 43 : import javax.servlet.http.HttpServletRequest; 44 : import javax.servlet.http.HttpServletResponse; 45 : import org.w3c.dom.Document; 46 : import org.w3c.dom.Element; 47 : 48 : /** Handles username/password based authentication against the directory. */ 49 : @Singleton 50 : class LdapLoginServlet extends HttpServlet { 51 : private static final long serialVersionUID = 1L; 52 : 53 1 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 54 : 55 : private final AccountManager accountManager; 56 : private final DynamicItem<WebSession> webSession; 57 : private final CanonicalWebUrl urlProvider; 58 : private final SiteHeaderFooter headers; 59 : private final AuthRequest.Factory authRequestFactory; 60 : 61 : @Inject 62 : LdapLoginServlet( 63 : AccountManager accountManager, 64 : DynamicItem<WebSession> webSession, 65 : CanonicalWebUrl urlProvider, 66 : SiteHeaderFooter headers, 67 1 : AuthRequest.Factory authRequestFactory) { 68 1 : this.accountManager = accountManager; 69 1 : this.webSession = webSession; 70 1 : this.urlProvider = urlProvider; 71 1 : this.headers = headers; 72 1 : this.authRequestFactory = authRequestFactory; 73 1 : } 74 : 75 : private void sendForm( 76 : HttpServletRequest req, HttpServletResponse res, @Nullable String errorMessage) 77 : throws IOException { 78 0 : String self = req.getRequestURI(); 79 0 : String cancel = MoreObjects.firstNonNull(urlProvider.get(req), "/"); 80 0 : cancel += LoginUrlToken.getToken(req); 81 : 82 0 : Document doc = headers.parse(LdapLoginServlet.class, "LoginForm.html"); 83 0 : HtmlDomUtil.find(doc, "hostName").setTextContent(req.getServerName()); 84 0 : HtmlDomUtil.find(doc, "login_form").setAttribute("action", self); 85 0 : HtmlDomUtil.find(doc, "cancel_link").setAttribute("href", cancel); 86 : 87 0 : Element emsg = HtmlDomUtil.find(doc, "error_message"); 88 0 : if (Strings.isNullOrEmpty(errorMessage)) { 89 0 : emsg.getParentNode().removeChild(emsg); 90 : } else { 91 0 : emsg.setTextContent(errorMessage); 92 : } 93 : 94 0 : byte[] bin = HtmlDomUtil.toUTF8(doc); 95 0 : res.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 96 0 : res.setContentType("text/html"); 97 0 : res.setCharacterEncoding(UTF_8.name()); 98 0 : res.setContentLength(bin.length); 99 0 : try (ServletOutputStream out = res.getOutputStream()) { 100 0 : out.write(bin); 101 : } 102 0 : } 103 : 104 : @Override 105 : protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException { 106 0 : sendForm(req, res, null); 107 0 : } 108 : 109 : @Override 110 : protected void doPost(HttpServletRequest req, HttpServletResponse res) 111 : throws ServletException, IOException { 112 0 : req.setCharacterEncoding(UTF_8.name()); 113 0 : String username = Strings.nullToEmpty(req.getParameter("username")).trim(); 114 0 : String password = Strings.nullToEmpty(req.getParameter("password")); 115 0 : String remember = Strings.nullToEmpty(req.getParameter("rememberme")); 116 0 : if (username.isEmpty() || password.isEmpty()) { 117 0 : sendForm(req, res, "Invalid username or password."); 118 0 : return; 119 : } 120 : 121 0 : AuthRequest areq = authRequestFactory.createForUser(username); 122 0 : areq.setPassword(password); 123 : 124 : AuthResult ares; 125 : try { 126 0 : ares = accountManager.authenticate(areq); 127 0 : } catch (AccountUserNameException e) { 128 0 : sendForm(req, res, e.getMessage()); 129 0 : return; 130 0 : } catch (AuthenticationUnavailableException e) { 131 0 : sendForm(req, res, "Authentication unavailable at this time."); 132 0 : return; 133 0 : } catch (AuthenticationFailedException e) { 134 : // This exception is thrown if the user provided wrong credentials, we don't need to log a 135 : // stacktrace for it. 136 0 : logger.atWarning().log("'%s' failed to sign in: %s", username, e.getMessage()); 137 0 : sendForm(req, res, "Invalid username or password."); 138 0 : return; 139 0 : } catch (AccountException e) { 140 0 : logger.atWarning().withCause(e).log("'%s' failed to sign in", username); 141 0 : sendForm(req, res, "Authentication failed."); 142 0 : return; 143 0 : } catch (RuntimeException e) { 144 0 : logger.atSevere().withCause(e).log("LDAP authentication failed"); 145 0 : sendForm(req, res, "Authentication unavailable at this time."); 146 0 : return; 147 0 : } 148 : 149 0 : StringBuilder dest = new StringBuilder(); 150 0 : dest.append(urlProvider.get(req)); 151 0 : dest.append(LoginUrlToken.getToken(req)); 152 : 153 0 : CacheHeaders.setNotCacheable(res); 154 0 : webSession.get().login(ares, "1".equals(remember)); 155 0 : res.sendRedirect(dest.toString()); 156 0 : } 157 : }