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