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