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.WikiEngine; 034import org.apache.wiki.WikiSession; 035import org.apache.wiki.auth.NoSuchPrincipalException; 036import org.apache.wiki.auth.UserManager; 037import org.apache.wiki.auth.WikiPrincipal; 038import org.apache.wiki.auth.WikiSecurityException; 039import org.apache.wiki.auth.authorize.Group; 040import org.apache.wiki.auth.authorize.GroupManager; 041import org.apache.wiki.auth.user.UserDatabase; 042import org.apache.wiki.auth.user.UserProfile; 043import org.apache.wiki.i18n.InternationalizationManager; 044import org.apache.wiki.pages.PageManager; 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 STORAGE_DIR = BasicAttachmentProvider.PROP_STORAGEDIR; 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 file for properties 082 m_propertyFile = new File(TMP_DIR, PROPFILENAME); 083 m_props = new Properties(); 084 085 // Stash the request 086 m_request = request; 087 m_validated = false; 088 } 089 090 /** 091 * Returns <code>true</code> if the administrative user had 092 * been created previously. 093 * @return the result 094 */ 095 public boolean adminExists() 096 { 097 // See if the admin user exists already 098 UserManager userMgr = m_engine.getUserManager(); 099 UserDatabase userDb = userMgr.getUserDatabase(); 100 101 try 102 { 103 userDb.findByLoginName( ADMIN_ID ); 104 return true; 105 } 106 catch ( NoSuchPrincipalException e ) 107 { 108 return false; 109 } 110 } 111 112 /** 113 * Creates an administrative user and returns the new password. 114 * If the admin user exists, the password will be <code>null</code>. 115 * @return the password 116 * @throws WikiSecurityException 117 */ 118 public String createAdministrator() throws WikiSecurityException 119 { 120 if ( !m_validated ) 121 { 122 throw new WikiSecurityException( "Cannot create administrator because one or more of the installation settings are invalid." ); 123 } 124 125 if ( adminExists() ) 126 { 127 return null; 128 } 129 130 // See if the admin user exists already 131 UserManager userMgr = m_engine.getUserManager(); 132 UserDatabase userDb = userMgr.getUserDatabase(); 133 String password = null; 134 135 try 136 { 137 userDb.findByLoginName( ADMIN_ID ); 138 } 139 catch ( NoSuchPrincipalException e ) 140 { 141 // Create a random 12-character password 142 password = TextUtil.generateRandomPassword(); 143 UserProfile profile = userDb.newProfile(); 144 profile.setLoginName( ADMIN_ID ); 145 profile.setFullname( ADMIN_NAME ); 146 profile.setPassword( password ); 147 userDb.save( profile ); 148 } 149 150 // Create a new admin group 151 GroupManager groupMgr = m_engine.getGroupManager(); 152 Group group = null; 153 try 154 { 155 group = groupMgr.getGroup( ADMIN_GROUP ); 156 group.add( new WikiPrincipal( ADMIN_NAME ) ); 157 } 158 catch ( NoSuchPrincipalException e ) 159 { 160 group = groupMgr.parseGroup( ADMIN_GROUP, ADMIN_NAME, true ); 161 } 162 groupMgr.setGroup( m_session, group ); 163 164 return password; 165 } 166 167 /** 168 * Returns the properties as a "key=value" string separated by newlines 169 * @return the string 170 */ 171 public String getPropertiesList() 172 { 173 StringBuilder result = new StringBuilder(); 174 Set<String> keys = m_props.stringPropertyNames(); 175 for (String key:keys) { 176 result.append(key + " = " + m_props.getProperty(key) + "\n"); 177 } 178 return result.toString(); 179 } 180 181 public String getPropertiesPath() 182 { 183 return m_propertyFile.getAbsolutePath(); 184 } 185 186 /** 187 * Returns a property from the WikiEngine's properties. 188 * @param key the property key 189 * @return the property value 190 */ 191 public String getProperty( String key ) 192 { 193 return m_props.getProperty( key ); 194 } 195 196 public void parseProperties () throws Exception 197 { 198 ResourceBundle rb = ResourceBundle.getBundle( InternationalizationManager.CORE_BUNDLE, 199 m_session.getLocale() ); 200 m_validated = false; 201 202 203 // Get application name 204 String nullValue = m_props.getProperty( APP_NAME, rb.getString( "install.installer.default.appname" ) ); 205 parseProperty( APP_NAME, nullValue ); 206 207 // Get/sanitize page directory 208 nullValue = m_props.getProperty( PAGE_DIR, rb.getString( "install.installer.default.pagedir" ) ); 209 parseProperty( PAGE_DIR, nullValue ); 210 sanitizePath( PAGE_DIR ); 211 212 // Get/sanitize work directory 213 nullValue = m_props.getProperty( WORK_DIR, TMP_DIR ); 214 parseProperty( WORK_DIR, nullValue ); 215 sanitizePath( WORK_DIR ); 216 217 // Set a few more default properties, for easy setup 218 m_props.setProperty( STORAGE_DIR, m_props.getProperty( PAGE_DIR ) ); 219 m_props.setProperty( PageManager.PROP_PAGEPROVIDER, "VersioningFileProvider" ); 220 } 221 222 public void saveProperties() 223 { 224 ResourceBundle rb = ResourceBundle.getBundle( InternationalizationManager.CORE_BUNDLE, m_session.getLocale() ); 225 // Write the file back to disk 226 try 227 { 228 OutputStream out = null; 229 try 230 { 231 out = new FileOutputStream( m_propertyFile ); 232 m_props.store( out, null ); 233 } 234 finally 235 { 236 if ( out != null ) 237 { 238 out.close(); 239 } 240 } 241 m_session.addMessage( INSTALL_INFO, MessageFormat.format(rb.getString("install.installer.props.saved"), m_propertyFile) ); 242 } 243 catch( IOException e ) 244 { 245 Object[] args = { e.getMessage(), m_props.toString() }; 246 m_session.addMessage( INSTALL_ERROR, MessageFormat.format( rb.getString( "install.installer.props.notsaved" ), args ) ); 247 } 248 } 249 250 public boolean validateProperties() throws Exception 251 { 252 ResourceBundle rb = ResourceBundle.getBundle( InternationalizationManager.CORE_BUNDLE, m_session.getLocale() ); 253 m_session.clearMessages( INSTALL_ERROR ); 254 parseProperties(); 255 validateNotNull( PAGE_DIR, rb.getString( "install.installer.validate.pagedir" ) ); 256 validateNotNull( APP_NAME, rb.getString( "install.installer.validate.appname" ) ); 257 validateNotNull( WORK_DIR, rb.getString( "install.installer.validate.workdir" ) ); 258 259 if ( m_session.getMessages( INSTALL_ERROR ).length == 0 ) 260 { 261 m_validated = true; 262 } 263 return m_validated; 264 } 265 266 /** 267 * Sets a property based on the value of an HTTP request parameter. 268 * If the parameter is not found, a default value is used instead. 269 * @param param the parameter containing the value we will extract 270 * @param defaultValue the default to use if the parameter was not passed 271 * in the request 272 */ 273 private void parseProperty( String param, String defaultValue ) 274 { 275 String value = m_request.getParameter( param ); 276 if ( value == null ) 277 { 278 value = defaultValue; 279 } 280 m_props.put(param, value); 281 } 282 283 /** 284 * Simply sanitizes any path which contains backslashes (sometimes Windows 285 * users may have them) by expanding them to double-backslashes 286 * @param key the key of the property to sanitize 287 */ 288 private void sanitizePath( String key ) 289 { 290 String s = m_props.getProperty( key ); 291 s = TextUtil.replaceString(s, "\\", "\\\\" ); 292 s = s.trim(); 293 m_props.put( key, s ); 294 } 295 296 private void validateNotNull( String key, String message ) 297 { 298 String value = m_props.getProperty( key ); 299 if ( value == null || value.length() == 0 ) 300 { 301 m_session.addMessage( INSTALL_ERROR, message ); 302 } 303 } 304 305}