/* Copyright (c) 2001 - 2009 TOPP - www.openplans.org. All rights reserved. * This code is licensed under the GPL 2.0 license, availible at the root * application directory. */ package org.geoserver.ftp; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.io.FilenameUtils; import org.apache.ftpserver.ftplet.Authentication; import org.apache.ftpserver.ftplet.AuthenticationFailedException; import org.apache.ftpserver.ftplet.Authority; import org.apache.ftpserver.ftplet.FtpException; import org.apache.ftpserver.ftplet.User; import org.apache.ftpserver.ftplet.UserManager; import org.apache.ftpserver.usermanager.UsernamePasswordAuthentication; import org.apache.ftpserver.usermanager.impl.BaseUser; import org.apache.ftpserver.usermanager.impl.ConcurrentLoginPermission; import org.apache.ftpserver.usermanager.impl.WritePermission; import org.geoserver.config.GeoServerDataDirectory; import org.geotools.util.logging.Logging; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.userdetails.UserDetailsService; /** * Maps GeoServer users to Apache's FTP Server {@link User}s. * <p> * <h2>User home directory</h2> * If the logged in user has administrative privileges, the home directory is set to the geoserver * data root directory (e.g {@code <gs data dir>/data}). For non admin users, the home directory is * set to a subdirectory of the geoserver data root directory called the same than the user name * (e.g. {@code <gs data dir>/data/incoming/<user name>}). * </p> * * @author aaime * @author groldan */ public class GSFTPUserManager implements org.apache.ftpserver.ftplet.UserManager { private static final Logger LOGGER = Logging.getLogger(GSFTPUserManager.class); /** * The role given to the administrators */ private static final String ADMIN_ROLE = "ROLE_ADMINISTRATOR"; /** * The default user */ private static final String DEFAULT_USER = "admin"; /** * The default password */ private static final String DEFAULT_PASSWORD = "geoserver"; private GeoServerDataDirectory dataDir; private AuthenticationManager authManager; public GSFTPUserManager(GeoServerDataDirectory dataDir) { this.dataDir = dataDir; } public void setAuthenticationManager(AuthenticationManager authManager) { this.authManager = authManager; } /** * @param ftpAuthRequest * one of {@link org.apache.ftpserver.usermanager.AnonymousAuthentication} or * {@link org.apache.ftpserver.usermanager.UsernamePasswordAuthentication} * @throws AuthenticationFailedException * if given an {@code AnonymousAuthentication}, or an invalid/disabled user * credentials * @see UserManager#authenticate(Authentication) */ public User authenticate(final Authentication ftpAuthRequest) throws AuthenticationFailedException { if (!(ftpAuthRequest instanceof UsernamePasswordAuthentication)) { throw new AuthenticationFailedException(); } final UsernamePasswordAuthentication upa = (UsernamePasswordAuthentication) ftpAuthRequest; final String principal = upa.getUsername(); final String credentials = upa.getPassword(); org.springframework.security.core.Authentication gsAuth = new UsernamePasswordAuthenticationToken( principal, credentials); try { gsAuth = authManager.authenticate(gsAuth); } catch (org.springframework.security.core.AuthenticationException authEx) { throw new AuthenticationFailedException(authEx); } try { // gather the user BaseUser user = getUserByName(principal); user.setPassword(credentials); // is the user enabled? if (!user.getEnabled()) { throw new AuthenticationFailedException(); } // scary message for admins if the username/password has not // been changed if (DEFAULT_USER.equals(user.getName()) && DEFAULT_PASSWORD.equals(credentials)) { LOGGER.log(Level.SEVERE, "The default admin/password combination has not been " + "modified, this makes the embedded FTP server an " + "open file host for everybody to use!!!"); } final File dataRoot = dataDir.findOrCreateDataRoot(); // enable only admins and non anonymous users boolean isGSAdmin = false; for (GrantedAuthority authority : gsAuth.getAuthorities()) { final String userRole = authority.getAuthority(); if (ADMIN_ROLE.equals(userRole)) { isGSAdmin = true; break; } } final File homeDirectory; if (isGSAdmin) { homeDirectory = dataRoot; } else { /* * This resolves the user's home directory to data/incoming/<user name> but does not * create the directory if it does not already exist. That is left to when the user * is authenticated, check the authenticate() method above. */ homeDirectory = new File(new File(dataRoot, "incoming"), user.getName()); } String normalizedPath = homeDirectory.getAbsolutePath(); normalizedPath = FilenameUtils.normalize(normalizedPath); user.setHomeDirectory(normalizedPath); if (!homeDirectory.exists()) { LOGGER.fine("Creating FTP home directory for user " + user.getName() + " at " + normalizedPath); homeDirectory.mkdirs(); } return user; } catch (AuthenticationFailedException e) { throw e; } catch (Exception e) { LOGGER.log(Level.INFO, "FTP authentication failure", e); throw new AuthenticationFailedException(e); } } /** * @throws FtpException * always, operation not supported. * @see org.apache.ftpserver.ftplet.UserManager#delete(java.lang.String) */ public void delete(String username) throws FtpException { throw new FtpException("No custom user handling on this instance"); } /** * @see org.apache.ftpserver.ftplet.UserManager#doesExist(java.lang.String) */ public boolean doesExist(String username) throws FtpException { return true; } /** * @see org.apache.ftpserver.ftplet.UserManager#getAdminName() */ public String getAdminName() throws FtpException { throw new FtpException("No custom user handling on this instance"); } /** * @see org.apache.ftpserver.ftplet.UserManager#getAllUserNames() */ public String[] getAllUserNames() throws FtpException { throw new FtpException("No custom user handling on this instance"); } /** * Maps a GeoServer user to an ftp {@link User} by means of the provided Spring Security's * {@link UserDetailsService}. * <p> * The user's home directory is set to the root geoserver data dir in the case of administrators * or to {@code <data dir>/incoming/<user name>} in case of non administrators. * * @see org.apache.ftpserver.ftplet.UserManager#getUserByName(java.lang.String) */ public BaseUser getUserByName(String username) throws FtpException { // basic ftp user setup BaseUser user = new BaseUser(); user.setName(username); user.setPassword(null); user.setEnabled(true); // allow writing List<Authority> authorities = new ArrayList<Authority>(); authorities.add(new WritePermission()); authorities.add(new ConcurrentLoginPermission(Integer.MAX_VALUE, Integer.MAX_VALUE)); user.setAuthorities(authorities); return user; } /** * @return {@code false} * @see org.apache.ftpserver.ftplet.UserManager#isAdmin(java.lang.String) */ public boolean isAdmin(final String username) throws FtpException { return false; } /** * @throws FtpException * always, operation not supported * @see org.apache.ftpserver.ftplet.UserManager#save(org.apache.ftpserver.ftplet.User) */ public void save(User user) throws FtpException { throw new FtpException("No custom user handling on this instance"); } }