/* * Copyright 2013 gitblit.com. * * 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.gitblit.manager; import java.nio.charset.Charset; import java.security.Principal; import java.text.MessageFormat; import java.util.List; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.wicket.RequestCycle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.gitblit.Constants; import com.gitblit.Constants.AuthenticationType; import com.gitblit.IStoredSettings; import com.gitblit.Keys; import com.gitblit.models.UserModel; import com.gitblit.utils.Base64; import com.gitblit.utils.HttpUtils; import com.gitblit.utils.StringUtils; import com.gitblit.utils.X509Utils.X509Metadata; import com.gitblit.wicket.GitBlitWebSession; /** * The session manager handles user login & logout. * * @author James Moger * */ public class SessionManager implements ISessionManager { private final Logger logger = LoggerFactory.getLogger(getClass()); private final IStoredSettings settings; private final IRuntimeManager runtimeManager; private final IUserManager userManager; public SessionManager( IRuntimeManager runtimeManager, IUserManager userManager) { this.settings = runtimeManager.getSettings(); this.runtimeManager = runtimeManager; this.userManager = userManager; } @Override public IManager setup() { List services = settings.getStrings("realm.authenticationServices"); for (String service : services) { // TODO populate authentication services here } return this; } @Override public IManager stop() { return this; } /** * Authenticate a user based on HTTP request parameters. * * Authentication by X509Certificate is tried first and then by cookie. * * @param httpRequest * @return a user object or null */ @Override public UserModel authenticate(HttpServletRequest httpRequest) { return authenticate(httpRequest, false); } /** * Authenticate a user based on HTTP request parameters. * * Authentication by X509Certificate, servlet container principal, cookie, * and BASIC header. * * @param httpRequest * @param requiresCertificate * @return a user object or null */ @Override public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) { // try to authenticate by certificate boolean checkValidity = settings.getBoolean(Keys.git.enforceCertificateValidity, true); String [] oids = settings.getStrings(Keys.git.certificateUsernameOIDs).toArray(new String[0]); UserModel model = HttpUtils.getUserModelFromCertificate(httpRequest, checkValidity, oids); if (model != null) { // grab real user model and preserve certificate serial number UserModel user = userManager.getUserModel(model.username); X509Metadata metadata = HttpUtils.getCertificateMetadata(httpRequest); if (user != null) { flagWicketSession(AuthenticationType.CERTIFICATE); logger.debug(MessageFormat.format("{0} authenticated by client certificate {1} from {2}", user.username, metadata.serialNumber, httpRequest.getRemoteAddr())); return user; } else { logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted client certificate ({1}) authentication from {2}", model.username, metadata.serialNumber, httpRequest.getRemoteAddr())); } } if (requiresCertificate) { // caller requires client certificate authentication (e.g. git servlet) return null; } // try to authenticate by servlet container principal Principal principal = httpRequest.getUserPrincipal(); if (principal != null) { String username = principal.getName(); if (!StringUtils.isEmpty(username)) { boolean internalAccount = isInternalAccount(username); UserModel user = userManager.getUserModel(username); if (user != null) { // existing user flagWicketSession(AuthenticationType.CONTAINER); logger.debug(MessageFormat.format("{0} authenticated by servlet container principal from {1}", user.username, httpRequest.getRemoteAddr())); return user; } else if (settings.getBoolean(Keys.realm.container.autoCreateAccounts, false) && !internalAccount) { // auto-create user from an authenticated container principal user = new UserModel(username.toLowerCase()); user.displayName = username; user.password = Constants.EXTERNAL_ACCOUNT; userManager.updateUserModel(user); flagWicketSession(AuthenticationType.CONTAINER); logger.debug(MessageFormat.format("{0} authenticated and created by servlet container principal from {1}", user.username, httpRequest.getRemoteAddr())); return user; } else if (!internalAccount) { logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted servlet container authentication from {1}", principal.getName(), httpRequest.getRemoteAddr())); } } } // try to authenticate by cookie if (userManager.supportsCookies()) { UserModel user = authenticate(httpRequest.getCookies()); if (user != null) { flagWicketSession(AuthenticationType.COOKIE); logger.debug(MessageFormat.format("{0} authenticated by cookie from {1}", user.username, httpRequest.getRemoteAddr())); return user; } } // try to authenticate by BASIC final String authorization = httpRequest.getHeader("Authorization"); if (authorization != null && authorization.startsWith("Basic")) { // Authorization: Basic base64credentials String base64Credentials = authorization.substring("Basic".length()).trim(); String credentials = new String(Base64.decode(base64Credentials), Charset.forName("UTF-8")); // credentials = username:password final String[] values = credentials.split(":", 2); if (values.length == 2) { String username = values[0]; char[] password = values[1].toCharArray(); UserModel user = authenticate(username, password); if (user != null) { flagWicketSession(AuthenticationType.CREDENTIALS); logger.debug(MessageFormat.format("{0} authenticated by BASIC request header from {1}", user.username, httpRequest.getRemoteAddr())); return user; } else { logger.warn(MessageFormat.format("Failed login attempt for {0}, invalid credentials from {1}", username, httpRequest.getRemoteAddr())); } } } return null; } /** * Authenticate a user based on their cookie. * * @param cookies * @return a user object or null */ protected UserModel authenticate(Cookie[] cookies) { if (userManager.supportsCookies()) { if (cookies != null && cookies.length > 0) { for (Cookie cookie : cookies) { if (cookie.getName().equals(Constants.NAME)) { String value = cookie.getValue(); return userManager.authenticate(value.toCharArray()); } } } } return null; } protected void flagWicketSession(AuthenticationType authenticationType) { RequestCycle requestCycle = RequestCycle.get(); if (requestCycle != null) { // flag the Wicket session, if this is a Wicket request GitBlitWebSession session = GitBlitWebSession.get(); session.authenticationType = authenticationType; } } /** * Authenticate a user based on a username and password. * * @see IUserService.authenticate(String, char[]) * @param username * @param password * @return a user object or null */ @Override public UserModel authenticate(String username, char[] password) { if (StringUtils.isEmpty(username)) { // can not authenticate empty username return null; } String usernameDecoded = StringUtils.decodeUsername(username); String pw = new String(password); if (StringUtils.isEmpty(pw)) { // can not authenticate empty password return null; } // check to see if this is the federation user // if (canFederate()) { // if (usernameDecoded.equalsIgnoreCase(Constants.FEDERATION_USER)) { // List tokens = getFederationTokens(); // if (tokens.contains(pw)) { // return getFederationUser(); // } // } // } UserModel user = userManager.authenticate(usernameDecoded, password); // try registered external authentication providers if (user == null) { // for (AuthenticationService service : authenticationServices) { // if (service instanceof UsernamePasswordAuthenticationService) { // user = service.authenticate(usernameDecoded, password); // if (user != null) { // // user authenticated // user.accountType = service.getAccountType(); // return user; // } // } // } } return user; } /** * Sets a cookie for the specified user. * * @param response * @param user */ @Override public void setCookie(HttpServletResponse response, UserModel user) { GitBlitWebSession session = GitBlitWebSession.get(); boolean standardLogin = session.authenticationType.isStandard(); if (userManager.supportsCookies() && standardLogin) { Cookie userCookie; if (user == null) { // clear cookie for logout userCookie = new Cookie(Constants.NAME, ""); } else { // set cookie for login String cookie = userManager.getCookie(user); if (StringUtils.isEmpty(cookie)) { // create empty cookie userCookie = new Cookie(Constants.NAME, ""); } else { // create real cookie userCookie = new Cookie(Constants.NAME, cookie); userCookie.setMaxAge(Integer.MAX_VALUE); } } userCookie.setPath("/"); response.addCookie(userCookie); } } /** * Logout a user. * * @param user */ @Override public void logout(HttpServletResponse response, UserModel user) { setCookie(response, null); userManager.logout(user); } /** * Returns true if the username represents an internal account * * @param username * @return true if the specified username represents an internal account */ protected boolean isInternalAccount(String username) { return !StringUtils.isEmpty(username) && (username.equalsIgnoreCase(Constants.FEDERATION_USER) || username.equalsIgnoreCase(UserModel.ANONYMOUS.username)); } // protected UserModel getFederationUser() { // // the federation user is an administrator // UserModel federationUser = new UserModel(Constants.FEDERATION_USER); // federationUser.canAdmin = true; // return federationUser; // } }