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.log4j.Logger;
022import org.apache.log4j.NDC;
023import org.apache.wiki.WikiContext;
024import org.apache.wiki.WikiEngine;
025import org.apache.wiki.WikiSession;
026import org.apache.wiki.auth.SessionMonitor;
027import org.apache.wiki.auth.WikiSecurityException;
028import org.apache.wiki.tags.WikiTagBase;
029
030import javax.servlet.Filter;
031import javax.servlet.FilterChain;
032import javax.servlet.FilterConfig;
033import javax.servlet.ServletContext;
034import javax.servlet.ServletException;
035import javax.servlet.ServletRequest;
036import javax.servlet.ServletResponse;
037import javax.servlet.http.HttpServletRequest;
038import javax.servlet.http.HttpServletRequestWrapper;
039import java.io.IOException;
040import java.io.PrintWriter;
041
042/**
043 * Filter that verifies that the {@link org.apache.wiki.WikiEngine} is running, and
044 * sets the authentication status for the user's WikiSession. Each HTTP request
045 * processed by this filter is wrapped by a {@link WikiRequestWrapper}. The wrapper's
046 * primary responsibility is to return the correct <code>userPrincipal</code> and
047 * <code>remoteUser</code> for authenticated JSPWiki users (whether 
048 * authenticated by container or by JSPWiki's custom system).
049 * The wrapper's other responsibility is to incorporate JSPWiki built-in roles
050 * into the role-checking algorithm for {@link  HttpServletRequest#isUserInRole(String)}.
051 * Just before the request is wrapped, the method {@link org.apache.wiki.auth.AuthenticationManager#login(HttpServletRequest)} executes;
052 * this method contains all of the logic needed to grab any user login credentials set 
053 * by the container or by cookies.
054 *  
055 *
056 */
057public class WikiServletFilter implements Filter
058{
059    protected static final Logger log = Logger.getLogger( WikiServletFilter.class );
060    protected WikiEngine m_engine = null;
061
062    /**
063     *  Creates a Wiki Servlet Filter.
064     */
065    public WikiServletFilter()
066    {
067        super();
068    }
069
070    /**
071     * Initializes the WikiServletFilter.
072     * 
073     * @param config The FilterConfig.
074     * @throws ServletException If a WikiEngine cannot be started.
075     */
076    public void init( FilterConfig config ) throws ServletException
077    {
078        ServletContext context = config.getServletContext();
079
080        // TODO REMOVEME when resolving JSPWIKI-129
081        if( System.getSecurityManager() != null )
082        {
083            context.log( "== JSPWIKI WARNING ==   : This container is running with a security manager. JSPWiki does not yet really support that right now. See issue JSPWIKI-129 for details and information on how to proceed." );
084        }
085
086        m_engine = WikiEngine.getInstance( context, null );
087    }
088
089    /**
090     * Destroys the WikiServletFilter.
091     */
092    public void destroy()
093    {
094    }
095
096    /**
097    * Checks that the WikiEngine is running ok, wraps the current
098    * HTTP request, and sets the correct authentication state for the users's
099    * WikiSession. First, the method {@link org.apache.wiki.auth.AuthenticationManager#login(HttpServletRequest)}
100    * executes, which sets the authentication state. Then, the request is wrapped with a
101    * {@link WikiRequestWrapper}.
102    * @param request the current HTTP request object
103    * @param response the current HTTP response object
104    * @param chain The Filter chain passed down.
105    * @throws ServletException if {@link org.apache.wiki.auth.AuthenticationManager#login(HttpServletRequest)} fails for any reason
106    * @throws IOException If writing to the servlet response fails. 
107    */
108    public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain ) throws IOException, ServletException
109    {
110        //
111        //  Sanity check; it might be true in some conditions, but we need to know where.
112        //
113        if( chain == null )
114        {
115            throw new ServletException("FilterChain is null, even if it should not be.  Please report this to the jspwiki development team.");
116        }
117        
118        if( m_engine == null )
119        {
120            PrintWriter out = response.getWriter();
121            out.print("<!DOCTYPE html><html lang=\"en\"><head><title>Fatal problem with JSPWiki</title></head>");
122            out.print("<body>");
123            out.print("<h1>JSPWiki has not been started</h1>");
124            out.print("<p>JSPWiki is not running.  This is probably due to a configuration error in your jspwiki.properties file, ");
125            out.print("or a problem with your servlet container.  Please double-check everything before issuing a bug report ");
126            out.print("at jspwiki.apache.org.</p>");
127            out.print("<p>We apologize for the inconvenience.  No, really, we do.  We're trying to ");
128            out.print("JSPWiki as easy as we can, but there is only so much we have time to test ");
129            out.print("platforms.</p>");
130            out.print( "<p>Please go to the <a href='Install.jsp'>installer</a> to continue.</p>" );
131            out.print("</body></html>");
132            return;
133        }   
134        
135        // If we haven't done so, wrap the request
136        HttpServletRequest httpRequest = ( HttpServletRequest )request;
137        
138        // Set the character encoding
139        httpRequest.setCharacterEncoding( m_engine.getContentEncoding().displayName() );
140        
141        if ( !isWrapped( request ) )
142        {
143            // Prepare the WikiSession
144            try
145            {
146                m_engine.getAuthenticationManager().login( httpRequest );
147                WikiSession wikiSession = SessionMonitor.getInstance( m_engine ).find( httpRequest.getSession() );
148                httpRequest = new WikiRequestWrapper( m_engine, httpRequest );
149                if ( log.isDebugEnabled() )
150                {
151                    log.debug( "Executed security filters for user=" + wikiSession.getLoginPrincipal().getName() + ", path=" + httpRequest.getRequestURI() );
152                }
153            }
154            catch ( WikiSecurityException e )
155            {
156                throw new ServletException( e );
157            }
158        }
159
160        try
161        {
162            NDC.push( m_engine.getApplicationName()+":"+httpRequest.getRequestURL() );
163            
164            chain.doFilter( httpRequest, response );
165        }
166        finally
167        {
168            NDC.pop();
169            NDC.remove();
170        }
171
172    }
173
174    /**
175     *  Figures out the wiki context from the request.  This method does not create the
176     *  context if it does not exist.
177     *  
178     *  @param request The request to examine
179     *  @return A valid WikiContext value (or null, if the context could not be located).
180     */
181    protected WikiContext getWikiContext( ServletRequest  request )
182    {
183        HttpServletRequest httpRequest = (HttpServletRequest) request;
184    
185        WikiContext ctx = (WikiContext) httpRequest.getAttribute( WikiTagBase.ATTR_CONTEXT );
186        
187        return ctx;
188    }
189
190    /** 
191     * Determines whether the request has been previously wrapped with a WikiRequestWrapper. 
192     * We find the wrapper by recursively unwrapping successive request wrappers, if they have been supplied.
193     * @param request the current HTTP request
194     * @return <code>true</code> if the request has previously been wrapped;
195     * <code>false</code> otherwise
196     */
197    private boolean isWrapped( ServletRequest request )
198    {
199        while ( !(request instanceof WikiRequestWrapper )
200            && request != null
201            && request instanceof HttpServletRequestWrapper )
202        {
203            request = ((HttpServletRequestWrapper) request).getRequest();
204        }
205        return request instanceof WikiRequestWrapper ? true : false;
206    }
207
208}