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 org.apache.wiki.api.core.Engine; 022import org.apache.wiki.api.core.Session; 023import org.apache.wiki.api.providers.AttachmentProvider; 024import org.apache.wiki.api.spi.Wiki; 025import org.apache.wiki.auth.NoSuchPrincipalException; 026import org.apache.wiki.auth.UserManager; 027import org.apache.wiki.auth.WikiPrincipal; 028import org.apache.wiki.auth.WikiSecurityException; 029import org.apache.wiki.auth.authorize.Group; 030import org.apache.wiki.auth.authorize.GroupManager; 031import org.apache.wiki.auth.user.UserDatabase; 032import org.apache.wiki.auth.user.UserProfile; 033import org.apache.wiki.i18n.InternationalizationManager; 034import org.apache.wiki.pages.PageManager; 035import org.apache.wiki.providers.FileSystemProvider; 036import org.apache.wiki.util.TextUtil; 037 038import javax.servlet.ServletConfig; 039import javax.servlet.http.HttpServletRequest; 040import java.io.File; 041import java.io.IOException; 042import java.io.OutputStream; 043import java.nio.file.Files; 044import java.text.MessageFormat; 045import java.util.Properties; 046import java.util.ResourceBundle; 047import java.util.Set; 048import java.util.stream.Collectors; 049 050/** 051 * Manages JSPWiki installation on behalf of <code>admin/Install.jsp</code>. The contents of this class were previously part of 052 * <code>Install.jsp</code>. 053 * 054 * @since 2.4.20 055 */ 056public class Installer { 057 058 public static final String ADMIN_ID = "admin"; 059 public static final String ADMIN_NAME = "Administrator"; 060 public static final String INSTALL_INFO = "Installer.Info"; 061 public static final String INSTALL_ERROR = "Installer.Error"; 062 public static final String INSTALL_WARNING = "Installer.Warning"; 063 public static final String APP_NAME = Engine.PROP_APPNAME; 064 public static final String STORAGE_DIR = AttachmentProvider.PROP_STORAGEDIR; 065 public static final String PAGE_DIR = FileSystemProvider.PROP_PAGEDIR; 066 public static final String WORK_DIR = Engine.PROP_WORKDIR; 067 public static final String ADMIN_GROUP = "Admin"; 068 public static final String PROPFILENAME = "jspwiki-custom.properties" ; 069 public static String TMP_DIR; 070 private final Session m_session; 071 private final File m_propertyFile; 072 private final Properties m_props; 073 private final Engine m_engine; 074 private final HttpServletRequest m_request; 075 private boolean m_validated; 076 077 public Installer( final HttpServletRequest request, final ServletConfig config ) { 078 // Get wiki session for this user 079 m_engine = Wiki.engine().find( config ); 080 m_session = Wiki.session().find( m_engine, request ); 081 082 // Get the file for properties 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 TMP_DIR = m_engine.getWikiProperties().getProperty( "jspwiki.workDir" ); 090 } 091 092 /** 093 * Returns <code>true</code> if the administrative user had been created previously. 094 * 095 * @return the result 096 */ 097 public boolean adminExists() { 098 // See if the admin user exists already 099 final UserManager userMgr = m_engine.getManager( UserManager.class ); 100 final UserDatabase userDb = userMgr.getUserDatabase(); 101 try { 102 userDb.findByLoginName( ADMIN_ID ); 103 return true; 104 } catch ( final NoSuchPrincipalException e ) { 105 return false; 106 } 107 } 108 109 /** 110 * Creates an administrative user and returns the new password. If the admin user exists, the password will be <code>null</code>. 111 * 112 * @return the password 113 */ 114 public String createAdministrator() throws WikiSecurityException { 115 if ( !m_validated ) { 116 throw new WikiSecurityException( "Cannot create administrator because one or more of the installation settings are invalid." ); 117 } 118 119 if ( adminExists() ) { 120 return null; 121 } 122 123 // See if the admin user exists already 124 final UserManager userMgr = m_engine.getManager( UserManager.class ); 125 final UserDatabase userDb = userMgr.getUserDatabase(); 126 String password = null; 127 128 try { 129 userDb.findByLoginName( ADMIN_ID ); 130 } catch( final NoSuchPrincipalException e ) { 131 // Create a random 12-character password 132 password = TextUtil.generateRandomPassword(); 133 final UserProfile profile = userDb.newProfile(); 134 profile.setLoginName( ADMIN_ID ); 135 profile.setFullname( ADMIN_NAME ); 136 profile.setPassword( password ); 137 userDb.save( profile ); 138 } 139 140 // Create a new admin group 141 final GroupManager groupMgr = m_engine.getManager( GroupManager.class ); 142 Group group; 143 try { 144 group = groupMgr.getGroup( ADMIN_GROUP ); 145 group.add( new WikiPrincipal( ADMIN_NAME ) ); 146 } catch( final NoSuchPrincipalException e ) { 147 group = groupMgr.parseGroup( ADMIN_GROUP, ADMIN_NAME, true ); 148 } 149 groupMgr.setGroup( m_session, group ); 150 151 return password; 152 } 153 154 /** 155 * Returns the properties as a "key=value" string separated by newlines 156 * @return the string 157 */ 158 public String getPropertiesList() { 159 final Set< String > keys = m_props.stringPropertyNames(); 160 return keys.stream().map( key -> key + " = " + m_props.getProperty( key ) + "\n" ).collect( Collectors.joining() ); 161 } 162 163 public String getPropertiesPath() { 164 return m_propertyFile.getAbsolutePath(); 165 } 166 167 /** 168 * Returns a property from the Engine's properties. 169 * @param key the property key 170 * @return the property value 171 */ 172 public String getProperty( final String key ) { 173 return m_props.getProperty( key ); 174 } 175 176 public void parseProperties () { 177 final ResourceBundle rb = ResourceBundle.getBundle( InternationalizationManager.CORE_BUNDLE, m_session.getLocale() ); 178 m_validated = false; 179 180 // Get application name 181 String nullValue = m_props.getProperty( APP_NAME, rb.getString( "install.installer.default.appname" ) ); 182 parseProperty( APP_NAME, nullValue ); 183 184 // Get work directory 185 nullValue = m_props.getProperty( WORK_DIR, TMP_DIR ); 186 parseProperty( WORK_DIR, nullValue ); 187 188 // Get page directory 189 nullValue = m_props.getProperty( PAGE_DIR, m_props.getProperty( WORK_DIR, TMP_DIR ) + File.separatorChar + "data" ); 190 parseProperty( PAGE_DIR, nullValue ); 191 192 // Set a few more default properties, for easy setup 193 m_props.setProperty( STORAGE_DIR, m_props.getProperty( PAGE_DIR ) ); 194 m_props.setProperty( PageManager.PROP_PAGEPROVIDER, "VersioningFileProvider" ); 195 } 196 197 public void saveProperties() { 198 final ResourceBundle rb = ResourceBundle.getBundle( InternationalizationManager.CORE_BUNDLE, m_session.getLocale() ); 199 // Write the file back to disk 200 try { 201 try( final OutputStream out = Files.newOutputStream( m_propertyFile.toPath() ) ) { 202 m_props.store( out, null ); 203 } 204 m_session.addMessage( INSTALL_INFO, MessageFormat.format(rb.getString("install.installer.props.saved"), m_propertyFile) ); 205 } catch( final IOException e ) { 206 final Object[] args = { e.getMessage(), m_props.toString() }; 207 m_session.addMessage( INSTALL_ERROR, MessageFormat.format( rb.getString( "install.installer.props.notsaved" ), args ) ); 208 } 209 } 210 211 public boolean validateProperties() { 212 final ResourceBundle rb = ResourceBundle.getBundle( InternationalizationManager.CORE_BUNDLE, m_session.getLocale() ); 213 m_session.clearMessages( INSTALL_ERROR ); 214 parseProperties(); 215 // sanitize pages, attachments and work directories 216 sanitizePath( PAGE_DIR ); 217 sanitizePath( STORAGE_DIR ); 218 sanitizePath( WORK_DIR ); 219 validateNotNull( PAGE_DIR, rb.getString( "install.installer.validate.pagedir" ) ); 220 validateNotNull( APP_NAME, rb.getString( "install.installer.validate.appname" ) ); 221 validateNotNull( WORK_DIR, rb.getString( "install.installer.validate.workdir" ) ); 222 223 if( m_session.getMessages( INSTALL_ERROR ).length == 0 ) { 224 m_validated = true; 225 } 226 return m_validated; 227 } 228 229 /** 230 * Sets a property based on the value of an HTTP request parameter. If the parameter is not found, a default value is used instead. 231 * 232 * @param param the parameter containing the value we will extract 233 * @param defaultValue the default to use if the parameter was not passed in the request 234 */ 235 private void parseProperty( final String param, final String defaultValue ) { 236 String value = m_request.getParameter( param ); 237 if( value == null ) { 238 value = defaultValue; 239 } 240 m_props.put( param, value ); 241 } 242 243 /** 244 * Simply sanitizes any path which contains backslashes (sometimes Windows users may have them) by expanding them to double-backslashes 245 * 246 * @param key the key of the property to sanitize 247 */ 248 private void sanitizePath( final String key ) { 249 String s = m_props.getProperty( key ); 250 s = TextUtil.replaceString(s, "\\", "\\\\" ); 251 s = s.trim(); 252 m_props.put( key, s ); 253 } 254 255 public void restoreUserValues() { 256 desanitizePath( PAGE_DIR ); 257 desanitizePath( STORAGE_DIR ); 258 desanitizePath( WORK_DIR ); 259 } 260 261 /** 262 * Simply removes sanitizations so values can be shown back to the user as they were entered 263 * 264 * @param key the key of the property to sanitize 265 */ 266 private void desanitizePath( final String key ) { 267 String s = m_props.getProperty( key ); 268 s = TextUtil.replaceString(s, "\\\\", "\\" ); 269 s = s.trim(); 270 m_props.put( key, s ); 271 } 272 273 private void validateNotNull( final String key, final String message ) { 274 final String value = m_props.getProperty( key ); 275 if ( value == null || value.isEmpty() ) { 276 m_session.addMessage( INSTALL_ERROR, message ); 277 } 278 } 279 280}