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    }