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