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 }