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}