001 /* 002 Licensed to the Apache Software Foundation (ASF) under one 003 or more contributor license agreements. See the NOTICE file 004 distributed with this work for additional information 005 regarding copyright ownership. The ASF licenses this file 006 to you under the Apache License, Version 2.0 (the 007 "License"); you may not use this file except in compliance 008 with the License. You may obtain a copy of the License at 009 010 http://www.apache.org/licenses/LICENSE-2.0 011 012 Unless required by applicable law or agreed to in writing, 013 software distributed under the License is distributed on an 014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 KIND, either express or implied. See the License for the 016 specific language governing permissions and limitations 017 under the License. 018 */ 019 package org.apache.wiki.ui; 020 021 import java.io.File; 022 import java.io.FileOutputStream; 023 import java.io.IOException; 024 import java.io.OutputStream; 025 import java.text.MessageFormat; 026 import java.util.Properties; 027 import java.util.ResourceBundle; 028 import java.util.Set; 029 030 import javax.servlet.ServletConfig; 031 import javax.servlet.ServletContext; 032 import javax.servlet.http.HttpServletRequest; 033 034 import org.apache.wiki.PageManager; 035 import org.apache.wiki.WikiEngine; 036 import org.apache.wiki.WikiSession; 037 import org.apache.wiki.auth.*; 038 import org.apache.wiki.auth.authorize.Group; 039 import org.apache.wiki.auth.authorize.GroupManager; 040 import org.apache.wiki.auth.user.UserDatabase; 041 import org.apache.wiki.auth.user.UserProfile; 042 import org.apache.wiki.i18n.InternationalizationManager; 043 import org.apache.wiki.providers.BasicAttachmentProvider; 044 import org.apache.wiki.providers.FileSystemProvider; 045 import org.apache.wiki.util.TextUtil; 046 047 /** 048 * Manages JSPWiki installation on behalf of <code>admin/Install.jsp</code>. 049 * The contents of this class were previously part of <code>Install.jsp</code>. 050 * 051 * @since 2.4.20 052 */ 053 public class Installer 054 { 055 public static final String ADMIN_ID = "admin"; 056 public static final String ADMIN_NAME = "Administrator"; 057 public static final String INSTALL_INFO = "Installer.Info"; 058 public static final String INSTALL_ERROR = "Installer.Error"; 059 public static final String INSTALL_WARNING = "Installer.Warning"; 060 public static final String APP_NAME = WikiEngine.PROP_APPNAME; 061 public static final String BASE_URL = WikiEngine.PROP_BASEURL; 062 public static final String STORAGE_DIR = BasicAttachmentProvider.PROP_STORAGEDIR; 063 public static final String LOG_FILE = "log4j.appender.FileLog.File"; 064 public static final String PAGE_DIR = FileSystemProvider.PROP_PAGEDIR; 065 public static final String WORK_DIR = WikiEngine.PROP_WORKDIR; 066 public static final String ADMIN_GROUP = "Admin"; 067 public static final String PROPFILENAME = "jspwiki-custom.properties" ; 068 public static final String TMP_DIR = System.getProperty("java.io.tmpdir"); 069 private final WikiSession m_session; 070 private final File m_propertyFile; 071 private final Properties m_props; 072 private final WikiEngine m_engine; 073 private HttpServletRequest m_request; 074 private boolean m_validated; 075 076 public Installer( HttpServletRequest request, ServletConfig config ) throws IOException { 077 // Get wiki session for this user 078 m_engine = WikiEngine.getInstance( config ); 079 m_session = WikiSession.getWikiSession( m_engine, request ); 080 081 // Get the servlet context, and file for properties 082 ServletContext context = config.getServletContext(); 083 m_propertyFile = new File(TMP_DIR, PROPFILENAME); 084 m_props = new Properties(); 085 086 // Stash the request 087 m_request = request; 088 m_validated = false; 089 } 090 091 /** 092 * Returns <code>true</code> if the administrative user had 093 * been created previously. 094 * @return the result 095 */ 096 public boolean adminExists() 097 { 098 // See if the admin user exists already 099 UserManager userMgr = m_engine.getUserManager(); 100 UserDatabase userDb = userMgr.getUserDatabase(); 101 102 try 103 { 104 userDb.findByLoginName( ADMIN_ID ); 105 return true; 106 } 107 catch ( NoSuchPrincipalException e ) 108 { 109 return false; 110 } 111 } 112 113 /** 114 * Creates an administrative user and returns the new password. 115 * If the admin user exists, the password will be <code>null</code>. 116 * @return the password 117 * @throws WikiSecurityException 118 */ 119 public String createAdministrator() throws WikiSecurityException 120 { 121 if ( !m_validated ) 122 { 123 throw new WikiSecurityException( "Cannot create administrator because one or more of the installation settings are invalid." ); 124 } 125 126 if ( adminExists() ) 127 { 128 return null; 129 } 130 131 // See if the admin user exists already 132 UserManager userMgr = m_engine.getUserManager(); 133 UserDatabase userDb = userMgr.getUserDatabase(); 134 String password = null; 135 136 try 137 { 138 userDb.findByLoginName( ADMIN_ID ); 139 } 140 catch ( NoSuchPrincipalException e ) 141 { 142 // Create a random 12-character password 143 password = TextUtil.generateRandomPassword(); 144 UserProfile profile = userDb.newProfile(); 145 profile.setLoginName( ADMIN_ID ); 146 profile.setFullname( ADMIN_NAME ); 147 profile.setPassword( password ); 148 userDb.save( profile ); 149 } 150 151 // Create a new admin group 152 GroupManager groupMgr = m_engine.getGroupManager(); 153 Group group = null; 154 try 155 { 156 group = groupMgr.getGroup( ADMIN_GROUP ); 157 group.add( new WikiPrincipal( ADMIN_NAME ) ); 158 } 159 catch ( NoSuchPrincipalException e ) 160 { 161 group = groupMgr.parseGroup( ADMIN_GROUP, ADMIN_NAME, true ); 162 } 163 groupMgr.setGroup( m_session, group ); 164 165 return password; 166 } 167 168 /** 169 * Returns the properties as a "key=value" string separated by newlines 170 * @return the string 171 */ 172 public String getPropertiesList() 173 { 174 StringBuilder result = new StringBuilder(); 175 Set<String> keys = m_props.stringPropertyNames(); 176 for (String key:keys) { 177 result.append(key + " = " + m_props.getProperty(key) + "\n"); 178 } 179 return result.toString(); 180 } 181 182 public String getPropertiesPath() 183 { 184 return m_propertyFile.getAbsolutePath(); 185 } 186 187 /** 188 * Returns a property from the WikiEngine's properties. 189 * @param key the property key 190 * @return the property value 191 */ 192 public String getProperty( String key ) 193 { 194 return m_props.getProperty( key ); 195 } 196 197 @SuppressWarnings("deprecation") 198 public void parseProperties () throws Exception 199 { 200 ResourceBundle rb = ResourceBundle.getBundle( InternationalizationManager.CORE_BUNDLE, 201 m_session.getLocale() ); 202 m_validated = false; 203 204 205 // Get application name 206 String nullValue = m_props.getProperty( APP_NAME, rb.getString( "install.installer.default.appname" ) ); 207 parseProperty( APP_NAME, nullValue ); 208 209 // Get/sanitize base URL 210 nullValue = m_request.getRequestURL().toString(); 211 nullValue = nullValue.substring( 0, nullValue.lastIndexOf('/') )+"/"; 212 parseProperty( BASE_URL, nullValue ); 213 sanitizeURL( BASE_URL ); 214 215 // Get/sanitize page directory 216 nullValue = m_props.getProperty( PAGE_DIR, rb.getString( "install.installer.default.pagedir" ) ); 217 parseProperty( PAGE_DIR, nullValue ); 218 sanitizePath( PAGE_DIR ); 219 220 // Get/sanitize log directory 221 nullValue = m_props.getProperty( LOG_FILE, TMP_DIR + File.separator + "jspwiki.log" ); 222 parseProperty( LOG_FILE, nullValue ); 223 sanitizePath( LOG_FILE ); 224 225 // Get/sanitize work directory 226 nullValue = m_props.getProperty( WORK_DIR, TMP_DIR ); 227 parseProperty( WORK_DIR, nullValue ); 228 sanitizePath( WORK_DIR ); 229 230 // Get/sanitize security property 231 nullValue = m_props.getProperty( AuthenticationManager.PROP_SECURITY, AuthenticationManager.SECURITY_JAAS ); 232 parseProperty( AuthenticationManager.PROP_SECURITY, nullValue ); 233 234 // Set a few more default properties, for easy setup 235 m_props.setProperty( STORAGE_DIR, m_props.getProperty( PAGE_DIR ) ); 236 m_props.setProperty( PageManager.PROP_PAGEPROVIDER, "VersioningFileProvider" ); 237 } 238 239 public void saveProperties() 240 { 241 ResourceBundle rb = ResourceBundle.getBundle( InternationalizationManager.CORE_BUNDLE, m_session.getLocale() ); 242 // Write the file back to disk 243 try 244 { 245 OutputStream out = null; 246 try 247 { 248 out = new FileOutputStream( m_propertyFile ); 249 m_props.store( out, null ); 250 } 251 finally 252 { 253 if ( out != null ) 254 { 255 out.close(); 256 } 257 } 258 m_session.addMessage( INSTALL_INFO, MessageFormat.format(rb.getString("install.installer.props.saved"), m_propertyFile) ); 259 } 260 catch( IOException e ) 261 { 262 Object[] args = { e.getMessage(), m_props.toString() }; 263 m_session.addMessage( INSTALL_ERROR, MessageFormat.format( rb.getString( "install.installer.props.notsaved" ), args ) ); 264 } 265 } 266 267 public boolean validateProperties() throws Exception 268 { 269 ResourceBundle rb = ResourceBundle.getBundle( InternationalizationManager.CORE_BUNDLE, m_session.getLocale() ); 270 m_session.clearMessages( INSTALL_ERROR ); 271 parseProperties(); 272 validateNotNull( BASE_URL, rb.getString( "install.installer.validate.baseurl" ) ); 273 validateNotNull( PAGE_DIR, rb.getString( "install.installer.validate.pagedir" ) ); 274 validateNotNull( APP_NAME, rb.getString( "install.installer.validate.appname" ) ); 275 validateNotNull( WORK_DIR, rb.getString( "install.installer.validate.workdir" ) ); 276 validateNotNull( LOG_FILE, rb.getString( "install.installer.validate.logfile" ) ); 277 278 if ( m_session.getMessages( INSTALL_ERROR ).length == 0 ) 279 { 280 m_validated = true; 281 } 282 return m_validated; 283 } 284 285 /** 286 * Sets a property based on the value of an HTTP request parameter. 287 * If the parameter is not found, a default value is used instead. 288 * @param param the parameter containing the value we will extract 289 * @param defaultValue the default to use if the parameter was not passed 290 * in the request 291 */ 292 private void parseProperty( String param, String defaultValue ) 293 { 294 String value = m_request.getParameter( param ); 295 if ( value == null ) 296 { 297 value = defaultValue; 298 } 299 m_props.put(param, value); 300 } 301 302 /** 303 * Simply sanitizes any path which contains backslashes (sometimes Windows 304 * users may have them) by expanding them to double-backslashes 305 * @param key the key of the property to sanitize 306 */ 307 private void sanitizePath( String key ) 308 { 309 String s = m_props.getProperty( key ); 310 s = TextUtil.replaceString(s, "\\", "\\\\" ); 311 s = s.trim(); 312 m_props.put( key, s ); 313 } 314 315 /** 316 * Simply sanitizes any URL which contains backslashes (sometimes Windows 317 * users may have them) 318 * @param key the key of the property to sanitize 319 */ 320 private void sanitizeURL( String key ) 321 { 322 String s = m_props.getProperty( key ); 323 s = TextUtil.replaceString( s, "\\", "/" ); 324 s = s.trim(); 325 if (!s.endsWith("/")) 326 { 327 s = s + "/"; 328 } 329 m_props.put( key, s ); 330 } 331 332 private void validateNotNull( String key, String message ) 333 { 334 String value = m_props.getProperty( key ); 335 if ( value == null || value.length() == 0 ) 336 { 337 m_session.addMessage( INSTALL_ERROR, message ); 338 } 339 } 340 341 }