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