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