001/* 002 003 Licensed to the Apache Software Foundation (ASF) under one 004 or more contributor license agreements. See the NOTICE file 005 distributed with this work for additional information 006 regarding copyright ownership. The ASF licenses this file 007 to you under the Apache License, Version 2.0 (the 008 "License"); you may not use this file except in compliance 009 with the License. You may obtain a copy of the License at 010 011 http://www.apache.org/licenses/LICENSE-2.0 012 013 Unless required by applicable law or agreed to in writing, 014 software distributed under the License is distributed on an 015 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 KIND, either express or implied. See the License for the 017 specific language governing permissions and limitations 018 under the License. 019 */ 020package org.apache.wiki.ajax; 021 022import org.apache.commons.lang3.StringUtils; 023import org.apache.logging.log4j.LogManager; 024import org.apache.logging.log4j.Logger; 025import org.apache.wiki.api.core.Engine; 026import org.apache.wiki.api.spi.Wiki; 027import org.apache.wiki.auth.AuthorizationManager; 028import org.apache.wiki.auth.permissions.PagePermission; 029import org.apache.wiki.util.TextUtil; 030 031import jakarta.servlet.ServletConfig; 032import jakarta.servlet.ServletException; 033import jakarta.servlet.http.HttpServlet; 034import jakarta.servlet.http.HttpServletRequest; 035import jakarta.servlet.http.HttpServletResponse; 036import java.io.IOException; 037import java.security.Permission; 038import java.util.ArrayList; 039import java.util.Arrays; 040import java.util.List; 041import java.util.Map; 042import java.util.concurrent.ConcurrentHashMap; 043import org.apache.wiki.api.core.Session; 044import org.apache.wiki.auth.SessionMonitor; 045import org.apache.wiki.event.WikiEventManager; 046import org.apache.wiki.event.WikiSecurityEvent; 047import org.apache.wiki.search.DefaultSearchManager; 048import org.apache.wiki.security.EventUtil; 049 050 051/** 052 * This provides a simple ajax servlet for handling /ajax/<ClassName> requests. HttpServlet classes need to be registered using 053 * {@link WikiAjaxDispatcherServlet#registerServlet(WikiAjaxServlet)} 054 * 055 * @since 2.10.2-svn12 056 */ 057public class WikiAjaxDispatcherServlet extends HttpServlet { 058 059 private static final long serialVersionUID = 1L; 060 private static final Map< String, AjaxServletContainer > ajaxServlets = new ConcurrentHashMap<>(); 061 private static final Logger LOG = LogManager.getLogger( WikiAjaxDispatcherServlet.class.getName() ); 062 private String PATH_AJAX = "/ajax/"; 063 private Engine m_engine; 064 065 /** 066 * {@inheritDoc} 067 * 068 * This sets the AjaxPath to "/ajax/" as configured in "jspwiki.ajax.url.prefix". 069 * Note: Do not change this without also changing the web.xml file. 070 */ 071 @Override 072 public void init( final ServletConfig config ) throws ServletException { 073 super.init( config ); 074 m_engine = Wiki.engine().find( config ); 075 PATH_AJAX = "/" + TextUtil.getStringProperty( m_engine.getWikiProperties(), "jspwiki.ajax.url.prefix", "ajax" ) + "/"; 076 LOG.info( "WikiAjaxDispatcherServlet initialized." ); 077 } 078 079 /** 080 * Register a {@link WikiAjaxServlet} using the servlet mapping as the alias 081 */ 082 public static void registerServlet( final WikiAjaxServlet servlet ) { 083 registerServlet( servlet.getServletMapping(), servlet ); 084 } 085 086 /** 087 * Register a {@link WikiAjaxServlet} with a specific alias, and default permission {@link PagePermission#VIEW}. 088 */ 089 public static void registerServlet( final String alias, final WikiAjaxServlet servlet ) { 090 registerServlet( alias, servlet, PagePermission.VIEW ); 091 } 092 093 /** 094 * Regster a {@link WikiAjaxServlet} given an alias, the servlet, and the permission. 095 * This creates a temporary bundle object called {@link WikiAjaxDispatcherServlet.AjaxServletContainer} 096 * 097 * @param alias the uri link to this servlet 098 * @param servlet the servlet being registered 099 * @param perm the permission required to execute the servlet. 100 */ 101 public static void registerServlet( final String alias, final WikiAjaxServlet servlet, final Permission perm ) { 102 LOG.info( "WikiAjaxDispatcherServlet registering " + alias + "=" + servlet + " perm=" + perm ); 103 ajaxServlets.put( alias, new AjaxServletContainer( alias, servlet, perm ) ); 104 } 105 106 /** 107 * Calls {@link #performAction} 108 */ 109 @Override 110 public void doPost( final HttpServletRequest req, final HttpServletResponse res ) throws IOException, ServletException { 111 performAction( req, res ); 112 } 113 114 /** 115 * Calls {@link #performAction} 116 */ 117 @Override 118 public void doGet( final HttpServletRequest req, final HttpServletResponse res ) throws IOException, ServletException { 119 performAction( req, res ); 120 } 121 122 /** 123 * The main method which get the requestURI "/ajax/<ServletName>", gets the {@link #getServletName} and finds the servlet using 124 * {@link #findServletByName}. It then calls {@link WikiAjaxServlet#service} method. 125 * 126 * @param req the inbound request 127 * @param res the outbound response 128 * @throws IOException if WikiEngine's content encoding is valid 129 * @throws ServletException if no registered servlet can be found 130 */ 131 private void performAction( final HttpServletRequest req, final HttpServletResponse res ) throws IOException, ServletException { 132 final String path = req.getRequestURI(); 133 final String servletName = getServletName( path ); 134 if( servletName != null) { 135 final AjaxServletContainer container = findServletContainer( servletName ); 136 if( container != null ) { 137 final WikiAjaxServlet servlet = container.servlet; 138 Session wikiSession = SessionMonitor.getInstance(m_engine).find(req.getSession()); 139 if ( validatePermission( req, container ) ) { 140 res.setCharacterEncoding( m_engine.getContentEncoding().displayName() ); 141 final String actionName = AjaxUtil.getNextPathPart( req.getRequestURI(), servlet.getServletMapping() ); 142 if (!(servlet instanceof DefaultSearchManager.PluginSearch)) { 143 final String xsrfToken = req.getParameter("X-XSRF-TOKEN"); 144 if (!wikiSession.antiCsrfToken().equals(xsrfToken)) { 145 res.sendError(400, "X-XSRF-TOKEN missing or invalid."); 146 WikiEventManager.fireEvent(this, 147 EventUtil.applyFrom(new WikiSecurityEvent(wikiSession, WikiSecurityEvent.ACCESS_DENIED, 148 req.getUserPrincipal(), "X-XSRF-TOKEN missing or invalid."), req)); 149 return; 150 } 151 } 152 LOG.debug( "actionName=" + actionName ); 153 final String params = req.getParameter( "params" ); 154 LOG.debug( "params=" + params ); 155 List< String > paramValues = new ArrayList<>(); 156 if( params != null ) { 157 if( StringUtils.isNotBlank( params ) ) { 158 paramValues = Arrays.asList( params.trim().split( "," ) ); 159 } 160 } 161 servlet.service( req, res, actionName, paramValues ); 162 } else { 163 LOG.warn("Servlet container " + container + " not authorised. Permission required."); 164 WikiEventManager.fireEvent(this, 165 EventUtil.applyFrom(new WikiSecurityEvent(wikiSession, WikiSecurityEvent.ACCESS_DENIED, 166 req.getUserPrincipal(), container.permission), req)); 167 res.sendError(400, "Denied."); 168 return; 169 } 170 } else { 171 LOG.warn( "No registered class for servletName=" + servletName + " in path=" + path ); 172 res.sendError(400, "Unknown Service."); 173 //should this be an auditable event? 174 //throw new ServletException( "No registered class for servletName=" + servletName ); 175 } 176 } 177 } 178 179 /** 180 * Validate the permission of the {@link WikiAjaxServlet} using the {@link AuthorizationManager#checkPermission} 181 * 182 * @param req the servlet request 183 * @param container the container info of the servlet 184 * @return true if permission is valid 185 */ 186 private boolean validatePermission( final HttpServletRequest req, final AjaxServletContainer container ) { 187 final Engine e = Wiki.engine().find( req.getSession().getServletContext(), null ); 188 boolean valid = false; 189 if( container != null ) { 190 valid = e.getManager( AuthorizationManager.class ).checkPermission( Wiki.session().find( e, req ), container.permission ); 191 } 192 return valid; 193 } 194 195 /** 196 * Get the ServletName from the requestURI "/ajax/<ServletName>", using {@link AjaxUtil#getNextPathPart}. 197 * 198 * @param path The requestURI, which must contains "/ajax/<ServletName>" in the path 199 * @return The ServletName for the requestURI, or null 200 * @throws ServletException if the path is invalid 201 */ 202 public String getServletName( final String path ) throws ServletException { 203 return AjaxUtil.getNextPathPart( path, PATH_AJAX ); 204 } 205 206 /** 207 * Find the {@link AjaxServletContainer} as registered in {@link #registerServlet}. 208 * 209 * @param servletAlias the name of the servlet from {@link #getServletName} 210 * @return The first servlet found, or null. 211 */ 212 private AjaxServletContainer findServletContainer( final String servletAlias ) { 213 return ajaxServlets.get( servletAlias ); 214 } 215 216 /** 217 * Find the {@link WikiAjaxServlet} given the servletAlias that it was registered with. 218 * 219 * @param servletAlias the value provided to {@link #registerServlet} 220 * @return the {@link WikiAjaxServlet} given the servletAlias that it was registered with. 221 */ 222 public WikiAjaxServlet findServletByName( final String servletAlias ) { 223 final AjaxServletContainer container = ajaxServlets.get( servletAlias ); 224 if( container != null ) { 225 return container.servlet; 226 } 227 return null; 228 } 229 230 private static class AjaxServletContainer { 231 232 final String alias; 233 final WikiAjaxServlet servlet; 234 final Permission permission; 235 236 public AjaxServletContainer( final String alias, final WikiAjaxServlet servlet, final Permission permission ) { 237 this.alias = alias; 238 this.servlet = servlet; 239 this.permission = permission; 240 } 241 242 @Override 243 public String toString() { 244 return getClass().getSimpleName() + " " + alias + "=" + servlet.getClass().getSimpleName() + " permission=" + permission; 245 } 246 247 } 248 249}