Author: ajaquith
Date: Tue Mar 17 02:30:12 2009
New Revision: 755088
URL: http://svn.apache.org/viewvc?rev=755088&view=rev
Log:
Many changes to EditActionBean and JSPs. Initial checkin of SpamProtect
annotation and matching JSP tag. This will be refined in future checkins. NO
unit tests yet.
Added:
incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamInterceptor.java
incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamProtect.java
Removed:
incubator/jspwiki/trunk/src/WebContent/templates/default/ConflictContent.jsp
Modified:
incubator/jspwiki/trunk/src/WebContent/Comment.jsp
incubator/jspwiki/trunk/src/WebContent/Edit.jsp
incubator/jspwiki/trunk/src/WebContent/WEB-INF/classes/CoreResources.properties
incubator/jspwiki/trunk/src/WebContent/WEB-INF/jspwiki.tld
incubator/jspwiki/trunk/src/WebContent/templates/default/DefaultLayout.jsp
incubator/jspwiki/trunk/src/WebContent/templates/default/editors/plain.jsp
incubator/jspwiki/trunk/src/java/org/apache/wiki/action/EditActionBean.java
incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamFilter.java
Modified: incubator/jspwiki/trunk/src/WebContent/Comment.jsp
URL:
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/WebContent/Comment.jsp?rev=755088&r1=755087&r2=755088&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/WebContent/Comment.jsp (original)
+++ incubator/jspwiki/trunk/src/WebContent/Comment.jsp Tue Mar 17 02:30:12 2009
@@ -20,7 +20,7 @@
<%@ page import="org.apache.wiki.util.TextUtil" %>
<%@ page import="org.apache.wiki.api.WikiPage" %>
<s:useActionBean beanclass="org.apache.wiki.action.EditActionBean"
event="comment" id="wikiActionBean" />
-<s:layout-component name="head.title">
+<s:layout-component name="headTitle">
<fmt:message key="comment.title.comment">
<fmt:param><wiki:Variable var="ApplicationName" /></fmt:param>
<fmt:param><wiki:PageName/></fmt:param>
Modified: incubator/jspwiki/trunk/src/WebContent/Edit.jsp
URL:
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/WebContent/Edit.jsp?rev=755088&r1=755087&r2=755088&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/WebContent/Edit.jsp (original)
+++ incubator/jspwiki/trunk/src/WebContent/Edit.jsp Tue Mar 17 02:30:12 2009
@@ -5,7 +5,7 @@
<s:layout-render name="/templates/default/DefaultLayout.jsp">
<%-- Page title should say Edit: + pagename --%>
- <s:layout-component name="head.title">
+ <s:layout-component name="headTitle">
<fmt:message key="edit.title.edit">
<fmt:param><wiki:Variable var="ApplicationName" /></fmt:param>
<fmt:param><wiki:PageName/></fmt:param>
Modified:
incubator/jspwiki/trunk/src/WebContent/WEB-INF/classes/CoreResources.properties
URL:
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/WebContent/WEB-INF/classes/CoreResources.properties?rev=755088&r1=755087&r2=755088&view=diff
==============================================================================
---
incubator/jspwiki/trunk/src/WebContent/WEB-INF/classes/CoreResources.properties
(original)
+++
incubator/jspwiki/trunk/src/WebContent/WEB-INF/classes/CoreResources.properties
Tue Mar 17 02:30:12 2009
@@ -308,3 +308,6 @@
preview=Preview
#Copied from src/WebContent/WEB-INF/classes/templates/default.properties..
Formerly named editor.plain.cancel.submit.
cancel=Cancel
+
+edit.conflict=Someone modified the page while you were editing it! The other
user's submitted text is shown below. Please merge it with with your text and
save the page again.
+conflictText=The other user's text:
Modified: incubator/jspwiki/trunk/src/WebContent/WEB-INF/jspwiki.tld
URL:
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/WebContent/WEB-INF/jspwiki.tld?rev=755088&r1=755087&r2=755088&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/WebContent/WEB-INF/jspwiki.tld (original)
+++ incubator/jspwiki/trunk/src/WebContent/WEB-INF/jspwiki.tld Tue Mar 17
02:30:12 2009
@@ -705,6 +705,13 @@
</tag>
<tag>
+ <name>SpamProtect</name>
+ <tagclass>org.apache.wiki.tags.SpamProtectTag</tagclass>
+ <bodycontent>empty</bodycontent>
+ <info>Injects hidden fields used to detect potential spam.</info>
+ </tag>
+
+ <tag>
<name>TabbedSection</name>
<tagclass>org.apache.wiki.tags.TabbedSectionTag</tagclass>
<bodycontent>JSP</bodycontent>
Modified:
incubator/jspwiki/trunk/src/WebContent/templates/default/DefaultLayout.jsp
URL:
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/WebContent/templates/default/DefaultLayout.jsp?rev=755088&r1=755087&r2=755088&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/WebContent/templates/default/DefaultLayout.jsp
(original)
+++ incubator/jspwiki/trunk/src/WebContent/templates/default/DefaultLayout.jsp
Tue Mar 17 02:30:12 2009
@@ -13,7 +13,7 @@
Stripes <s:layout-component name="foo"> elements. Named components
that can be overridden include:
- head.title : The HTML page title, which will be rendered
+ headTitle : The HTML page title, which will be rendered
in the <title> element. Default=wiki: pagename
stylesheet : Link tags to external stylesheets.
Default=blank
inlinecss : Inline stylesheets. Default=blank
@@ -40,7 +40,7 @@
Title: by default, use the "view page" title
--%>
- <s:layout-component name="head.title">
+ <s:layout-component name="headTitle">
<fmt:message key="view.title.view">
<fmt:param><wiki:Variable var="ApplicationName" /></fmt:param>
<fmt:param><wiki:PageName/></fmt:param>
Modified:
incubator/jspwiki/trunk/src/WebContent/templates/default/editors/plain.jsp
URL:
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/WebContent/templates/default/editors/plain.jsp?rev=755088&r1=755087&r2=755088&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/WebContent/templates/default/editors/plain.jsp
(original)
+++ incubator/jspwiki/trunk/src/WebContent/templates/default/editors/plain.jsp
Tue Mar 17 02:30:12 2009
@@ -2,21 +2,29 @@
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://jakarta.apache.org/jspwiki.tld" prefix="wiki" %>
<%@ taglib uri="http://stripes.sourceforge.net/stripes.tld" prefix="s" %>
-<%@ page import="org.apache.wiki.filters.SpamFilter" %>
<%@ page import="org.apache.wiki.util.TextUtil" %>
<%--
This is a plain editor for JSPWiki.
--%>
<div style="width:100%"> <%-- Required for IE6 on Windows --%>
+ <%-- Print any validation errors --%>
+ <s:errors />
+
<s:form beanclass="org.apache.wiki.action.EditActionBean" class="wikiform"
id="editform" method="post" acceptcharset="UTF-8"
enctype="application/x-www-form-urlencoded" >
+ <%-- If any conflicts, print the conflicting text here --%>
+ <c:if test="${not empty wikiActionBean.conflictText}">
+ <p>
+ <s:label for="conflictText" />
+ <s:textarea name="conflictText" readonly="true" />
+ </p>
+ </c:if>
+
<%-- Edit.jsp relies on these being found. So be careful, if you make
changes. --%>
<p id="submitbuttons">
<s:hidden name="page"><wiki:Variable var='pagename' /></s:hidden>
- <s:hidden name="<%=SpamFilter.getHashFieldName(request)%>"><c:out
value="${lastchange}" /></s:hidden>
- <%=SpamFilter.insertInputFields( pageContext )%>
<c:set var="saveTitle" scope="page"><fmt:message
key="editor.plain.save.title" /></c:set>
<wiki:CheckRequestContext context='edit'>
<s:submit name="save" accesskey="s" title="${saveTitle}" />
@@ -31,12 +39,7 @@
<c:set var="cancelTitle" scope="page"><fmt:message
key="editor.plain.cancel.title" /></c:set>
<s:submit name="cancel" accesskey="q" title="${cancelTitle}" />
</p>
-
- <%-- This following field is only for the SpamFilter to catch bots which
are just
- randomly filling all fields and submitting. Normal user should never
see this field,
- nor type anything in it. --%>
- <div style="display:none;">Authentication code: <input type="text"
name="<%=SpamFilter.getBotFieldName()%>" id="<%=SpamFilter.getBotFieldName()%>"
value="" /></div>
-
+
<%-- Fields for changenote, renaming etc. --%>
<table>
<tr>
@@ -158,6 +161,8 @@
</div>
<div id="livepreview"></div>
+ <%-- Spam detection fields --%>
+ <wiki:SpamProtect />
</s:form>
</div>
\ No newline at end of file
Modified:
incubator/jspwiki/trunk/src/java/org/apache/wiki/action/EditActionBean.java
URL:
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/action/EditActionBean.java?rev=755088&r1=755087&r2=755088&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/action/EditActionBean.java
(original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/action/EditActionBean.java
Tue Mar 17 02:30:12 2009
@@ -23,25 +23,24 @@
import java.io.IOException;
import java.security.Principal;
+import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import net.sourceforge.stripes.action.*;
import net.sourceforge.stripes.controller.LifecycleStage;
-import net.sourceforge.stripes.validation.LocalizableError;
-import net.sourceforge.stripes.validation.Validate;
-import net.sourceforge.stripes.validation.ValidationErrors;
+import net.sourceforge.stripes.validation.*;
+import org.apache.commons.lang.StringEscapeUtils;
import org.apache.wiki.*;
import org.apache.wiki.api.WikiException;
import org.apache.wiki.api.WikiPage;
import org.apache.wiki.auth.permissions.PagePermission;
import org.apache.wiki.content.PageNotFoundException;
import org.apache.wiki.filters.RedirectException;
-import org.apache.wiki.filters.SpamFilter;
+import org.apache.wiki.filters.SpamProtect;
import org.apache.wiki.htmltowiki.HtmlStringToWikiTranslator;
import org.apache.wiki.log.Logger;
import org.apache.wiki.log.LoggerFactory;
@@ -49,18 +48,22 @@
import org.apache.wiki.ui.stripes.HandlerPermission;
import org.apache.wiki.ui.stripes.WikiActionBeanContext;
import org.apache.wiki.ui.stripes.WikiRequestContext;
+import org.apache.wiki.util.TextUtil;
import org.apache.wiki.workflow.DecisionRequiredException;
import org.jdom.JDOMException;
+/**
+ * ActionBean that manages how users edit and comment on WikiPages.
+ */
@HttpCache( allow = false )
public class EditActionBean extends AbstractPageActionBean
{
+ private static final String LOCK_PREFIX = "lock-";
+
private static final Logger log = LoggerFactory.getLogger(
EditActionBean.class );
private String m_author = null;
- private String m_spamhash = null;
-
private String m_text = null;
private String m_changeNote = null;
@@ -69,6 +72,8 @@
private boolean m_captcha = false;
+ private String m_conflictText = null;
+
private boolean m_remember = true;
private String m_htmlPageText = null;
@@ -77,9 +82,13 @@
private String m_link = null;
+ private Date m_startTime = null;
+
/**
- * Event handler method that cancels any locks the user possesses for the
current wiki page,
- * and redirects the user to the {...@link ViewActionBean} "view" handler.
+ * Event handler method that cancels any locks the user possesses for the
+ * current wiki page, and redirects the user to the {...@link
ViewActionBean}
+ * "view" handler.
+ *
* @return the redirect
*/
@DontValidate
@@ -90,15 +99,15 @@
{
String pagereq = m_page.getName();
log.debug( "Cancelled editing " + pagereq );
-
+
// Cancel page lock
HttpSession session = getContext().getRequest().getSession();
WikiEngine engine = getContext().getEngine();
- PageLock lock = (PageLock) session.getAttribute( "lock-" + pagereq );
+ PageLock lock = (PageLock) session.getAttribute( LOCK_PREFIX + pagereq
);
if( lock != null )
{
engine.getPageManager().unlockPage( lock );
- session.removeAttribute( "lock-" + pagereq );
+ session.removeAttribute( LOCK_PREFIX + pagereq );
}
return new RedirectResolution( ViewActionBean.class ).addParameter(
"page", pagereq );
}
@@ -108,6 +117,9 @@
@WikiRequestContext( "comment" )
public Resolution comment()
{
+ // Set the editing start time
+ m_startTime = new Date();
+
return null;
}
@@ -130,54 +142,56 @@
@HandlesEvent( "edit" )
@HandlerPermission( permissionClass = PagePermission.class, target =
"${page.qualifiedName}", actions = PagePermission.EDIT_ACTION )
@WikiRequestContext( "edit" )
- public Resolution edit()
+ public Resolution edit() throws ProviderException
{
WikiActionBeanContext wikiContext = getContext();
HttpServletRequest request = wikiContext.getRequest();
HttpSession session = request.getSession();
Principal user = wikiContext.getCurrentUser();
- String pagereq = m_page.getName();
+ String pageName = m_page.getName();
- log.info( "Editing page " + pagereq + ". User=" + user.getName() + ",
host=" + request.getRemoteAddr() );
+ log.info( "Editing page " + pageName + ". User=" + user.getName() + ",
host=" + request.getRemoteAddr() );
- try
+ // If page is locked, make sure we tell the user
+ List<Message> messages = wikiContext.getMessages();
+ WikiEngine engine = wikiContext.getEngine();
+ PageManager mgr = engine.getPageManager();
+ PageLock lock = mgr.getCurrentLock( m_page );
+ if( lock != null )
{
- // If page is locked, make sure we tell the user
- List<Message> messages = wikiContext.getMessages();
- WikiEngine engine = wikiContext.getEngine();
- PageManager mgr = engine.getPageManager();
- PageLock lock = mgr.getCurrentLock( m_page );
- if( lock != null )
- {
- messages.add( new LocalizableMessage( "edit.locked",
lock.getLocker(), lock.getTimeLeft() ) );
- }
-
- // If user is not editing the latest one, tell user also
- ValidationErrors errors = getContext().getValidationErrors();
- WikiPage latest = engine.getPage( m_page.getName() );
- if( latest.getVersion() != m_page.getVersion() )
- {
- errors.addGlobalError( new LocalizableError( "edit.restoring",
m_page.getVersion() ) );
- }
-
- // Attempt to lock the page.
- lock = mgr.lockPage( m_page, user.getName() );
- if( lock != null )
- {
- session.setAttribute( "lock-" + pagereq, lock );
- }
-
- // Load the page text
- m_text = m_page.getContentAsString();
+ messages.add( new LocalizableMessage( "edit.locked",
lock.getLocker(), lock.getTimeLeft() ) );
+ }
- return new ForwardResolution( "/Edit.jsp" );
+ // If user is not editing the latest one, tell user also
+ ValidationErrors errors = getContext().getValidationErrors();
+ WikiPage latest;
+ try
+ {
+ latest = engine.getPage( m_page.getName() );
+ }
+ catch( PageNotFoundException e )
+ {
+ latest = m_page;
+ }
+ if( latest.getVersion() != m_page.getVersion() )
+ {
+ errors.addGlobalError( new LocalizableError( "edit.restoring",
m_page.getVersion() ) );
}
- catch( ProviderException e )
+
+ // Attempt to lock the page.
+ lock = mgr.lockPage( m_page, user.getName() );
+ if( lock != null )
{
- // This shouldn't be happening
- log.error("Unable to get page",e);
- return new ErrorResolution(
HttpServletResponse.SC_INTERNAL_SERVER_ERROR );
+ session.setAttribute( LOCK_PREFIX + pageName, lock );
}
+
+ // Load the page text
+ m_text = engine.getPureText( m_page );
+
+ // Set the editing start time
+ m_startTime = new Date();
+
+ return new ForwardResolution( "/Edit.jsp" );
}
/**
@@ -221,6 +235,16 @@
}
/**
+ * Returns the conflicting text for this page.
+ *
+ * @return the text
+ */
+ public String getConflictText()
+ {
+ return m_conflictText;
+ }
+
+ /**
* Returns the HTML page text.
*
* @return the HTML page text
@@ -262,6 +286,16 @@
}
/**
+ * Returns the time the user started editing the page.
+ *
+ * @return the start time
+ */
+ public Date getStartTime()
+ {
+ return m_startTime;
+ }
+
+ /**
* Returns the edited text.
*
* @return the text
@@ -272,32 +306,21 @@
}
/**
- * Initializes default values. Also looks up the correct spam hash field,
as determined by
- * {...@link SpamFilter#getHashFieldName(HttpServletRequest)}.
-
+ * Initializes default values that must be set in order for events to work
+ * properly. This method before after binding and validation of the
+ * ActionBean's other properties, to make sure that the values we want are
+ * bound. The values set includes the <code>author</code> property, which
+ * is set to the value passed in the request parameter <code>author</code>
+ * if the user is anonymous. In all other cases, the author is always set
to
+ * the name of the Principal returned by
+ * {...@link WikiSession#getUserPrincipal()}.
*/
@After( stages = LifecycleStage.BindingAndValidation )
public void initDefaultValues()
{
- HttpServletRequest request = getContext().getRequest();
-
- // Look up and set spam hash field name for this particular edit
- String hashParam = SpamFilter.getHashFieldName( request );
- m_spamhash = request.getParameter( hashParam );
- if( m_spamhash != null )
- {
- m_spamhash = m_spamhash.trim();
- }
-
// Set author: prefer authenticated/asserted principals first
WikiSession wikiSession = getContext().getWikiSession();
- if( wikiSession.isAsserted() || wikiSession.isAuthenticated() )
- {
- m_author = wikiSession.getUserPrincipal().getName();
- }
-
- // Otherwise, if author not bound, check session
- else if( m_author == null )
+ if( m_author == null || !wikiSession.isAnonymous() )
{
m_author = wikiSession.getUserPrincipal().getName();
}
@@ -317,9 +340,37 @@
return new ForwardResolution( "/Preview.jsp" );
}
+ /**
+ * Validation method that checks to see if another user has modified the
+ * page since the page editing action started. This method fires only when
+ * the <code>save</code> event is executed. The algorithm for detecting
+ * conflicts is simple: if the last-modified time on the current WikiPage
+ * (via {...@link #getPage()}) is later than the start time of the editing
+ * session ({...@link #getStartTime()}, it's a conflict. In that case, this
+ * method adds a validation error and calls {...@link
#setConflictText(String)}
+ * with the text of the page as modified by the other user.
+ */
+ @ValidationMethod( on = "save", when = ValidationState.NO_ERRORS )
+ public void validateNoConflicts() throws ProviderException
+ {
+ if( m_startTime.before( m_page.getLastModified() ) )
+ {
+ // Retrieve and escape the conflicting text
+ String conflictText = m_page.getContentAsString();
+ conflictText = StringEscapeUtils.escapeXml( conflictText );
+ conflictText = TextUtil.replaceString( conflictText, "\n", "<br
/>" );
+ m_conflictText = conflictText;
+
+ // Create a validation error
+ ValidationErrors errors = getContext().getValidationErrors();
+ errors.add( "text", new LocalizableError( "edit.conflict" ) );
+ }
+ }
+
@HandlesEvent( "save" )
@HandlerPermission( permissionClass = PagePermission.class, target =
"${page.qualifiedName}", actions = PagePermission.EDIT_ACTION )
@WikiRequestContext( "save" )
+ @SpamProtect
public Resolution save() throws WikiException
{
WikiSession wikiSession = getContext().getWikiSession();
@@ -332,33 +383,6 @@
log.info( "Saving page " + m_page.getName() + ". UserPrincipal=" +
wikiSession.getUserPrincipal().getName() + ", Author="
+ m_author + ", Host=" +
getContext().getRequest().getRemoteAddr() );
- // Check for session expiration
- Resolution r = SpamFilter.checkHash( this );
- if( r != null )
- {
- return r;
- }
-
- // FIXME: I am not entirely sure if the JSP page is the
- // best place to check for concurrent changes. It certainly
- // is the best place to show errors, though.
- String h = SpamFilter.getSpamHash( m_page, request );
-
- // Someone changed the page while we were editing it!
- if( !h.equals( m_spamhash ) )
- {
- log.info( "Page changed, warning user." );
- return new RedirectResolution( PageModifiedActionBean.class,
"conflict" ).addParameter( "page", pagereq );
- }
-
- //
- // We expire ALL locks at this moment, simply because someone has
- // already broken it.
- //
- PageLock lock = engine.getPageManager().getCurrentLock( m_page );
- engine.getPageManager().unlockPage( lock );
- session.removeAttribute( "lock-" + pagereq );
-
// Set author information and other metadata
WikiPage modifiedPage = (WikiPage) wikiContext.getPage().clone();
modifiedPage.setAuthor( m_author );
@@ -385,9 +409,11 @@
{
engine.saveText( wikiContext, m_text );
}
+ session.removeAttribute( LOCK_PREFIX +m_page.getName() );
}
catch( DecisionRequiredException ex )
{
+ session.removeAttribute( LOCK_PREFIX +m_page.getName() );
return new RedirectResolution( ViewActionBean.class, "view"
).addParameter( "page", "ApprovalRequiredForPageChanges" );
}
catch( RedirectException ex )
@@ -395,7 +421,6 @@
// Should work, but doesn't
wikiContext.getWikiSession().addMessage( ex.getMessage() ); //
FIXME:
session.setAttribute( "message", ex.getMessage() );
- session.setAttribute( SpamFilter.getHashFieldName( request ),
m_spamhash );
return new RedirectResolution( ex.getRedirect() ).flash( this );
}
@@ -427,6 +452,19 @@
}
/**
+ * Sets the conflicting text for this page, which was edited by another
+ * user. This property should not normally be set unless another user has
+ * saved the page since the start of the editing session.
+ *
+ * @param conflictText
+ */
+ @Validate( required = false )
+ public void setConflictText( String conflictText )
+ {
+ m_conflictText = conflictText;
+ }
+
+ /**
* Sets a flag indicating that CAPTCHA should be used for editing.
*
* @param captcha <code>true</code> if a CAPTCHA is in use;
@@ -501,6 +539,22 @@
}
/**
+ * Sets the start time the user began editing. Note that this parameter,
+ * when it is written to the page, will be encrypted so that it cannot be
+ * tampered with by the user. When the <code>save</code> event is
+ * executed, it will be decrypted and used to detect edit conflicts. This
+ * value is initialized to the current time when the
+ * {...@link #initDefaultValues()} method fires.
+ *
+ * @param date the start time
+ */
+ @Validate( required = true, encrypted = true )
+ public void setStartTime( Date date )
+ {
+ m_startTime = date;
+ }
+
+ /**
* Sets the edited text.
*
* @param text the text
Modified:
incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamFilter.java
URL:
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamFilter.java?rev=755088&r1=755087&r2=755088&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamFilter.java
(original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamFilter.java
Tue Mar 17 02:30:12 2009
@@ -34,6 +34,7 @@
import net.sf.akismet.Akismet;
import net.sourceforge.stripes.action.RedirectResolution;
import net.sourceforge.stripes.action.Resolution;
+import net.sourceforge.stripes.util.CryptoUtil;
import org.apache.commons.jrcs.diff.*;
import org.apache.commons.jrcs.diff.myers.MyersDiff;
@@ -46,6 +47,7 @@
import org.apache.wiki.api.WikiPage;
import org.apache.wiki.attachment.Attachment;
import org.apache.wiki.auth.user.UserProfile;
+import org.apache.wiki.content.PageNotFoundException;
import org.apache.wiki.log.Logger;
import org.apache.wiki.log.LoggerFactory;
import org.apache.wiki.providers.ProviderException;
@@ -199,7 +201,7 @@
private boolean m_stopAtFirstMatch = true;
- private static String c_hashName;
+ private static String c_hashFieldName;
private static long c_lastUpdate;
/** The HASH_DELAY value is a maximum amount of time that an user can keep
@@ -778,8 +780,17 @@
{
try
{
- WikiPage source = context.getEngine().getPage(
m_forbiddenWordsPage );
- Attachment att =
context.getEngine().getAttachmentManager().getAttachmentInfo( context,
m_blacklist );
+ WikiPage source = null;
+ Attachment att = null;
+ try
+ {
+ source = context.getEngine().getPage( m_forbiddenWordsPage );
+ att =
context.getEngine().getAttachmentManager().getAttachmentInfo( context,
m_blacklist );
+ }
+ catch( PageNotFoundException e )
+ {
+ // No worries
+ }
boolean rebuild = false;
@@ -1100,20 +1111,20 @@
if( hash == null )
{
- hash = c_hashName;
+ hash = c_hashFieldName;
request.getSession().setAttribute( "_hash", hash );
}
}
- if( c_hashName == null || c_lastUpdate < (System.currentTimeMillis() -
HASH_DELAY*60*60*1000) )
+ if( c_hashFieldName == null || c_lastUpdate <
(System.currentTimeMillis() - HASH_DELAY*60*60*1000) )
{
- c_hashName = getUniqueID().toLowerCase();
+ c_hashFieldName = getUniqueID().toLowerCase();
c_lastUpdate = System.currentTimeMillis();
}
- return hash != null ? hash : c_hashName;
+ return hash != null ? hash : c_hashFieldName;
}
/**
@@ -1133,21 +1144,65 @@
* @return <code>null</code> if hash is okay, or a RedirectResolution if
not.
* @since 3.0
*/
- public static final Resolution checkHash( WikiActionBean actionBean )
+ public static final Resolution validateSpamParams( WikiActionBean
actionBean )
{
WikiActionBeanContext context = actionBean.getContext();
- String hashName = getHashFieldName( context.getRequest() );
+ HttpServletRequest request = (HttpServletRequest)context.getRequest();
- if( context.getRequest().getParameter(hashName) == null )
+ // Recover the encrypted parameter and then validate the trap and
token fields
+ boolean spamParamsValid = false;
+ String encryptedParam = request.getParameter(
SpamInterceptor.REQ_SPAM_PARAM );
+ if ( encryptedParam != null )
+ {
+ String payload = CryptoUtil.decrypt( encryptedParam );
+ if ( payload != null )
+ {
+ String[] spamParams = payload.split( "\n" );
+ if ( spamParams.length != 2 )
+ {
+ String trapParam = request.getParameter( spamParams[0] );
+ String tokenParam = request.getParameter( spamParams[1] );
+
+ // Trap parameter should be blank/null
+ if ( trapParam == null || trapParam.length() == 0 )
+ {
+
+ // Token parameter should simply be the session ID
+ if ( tokenParam != null &&
request.getSession().getId().equals( tokenParam ))
+ {
+ // If we got here, everything validated ok!
+ spamParamsValid = true;
+ }
+ }
+ }
+ }
+ }
+
+ if( !spamParamsValid )
{
Change change = getChange( context, EditorManager.getEditedText(
context.getRequest() ) );
-
log( context, REJECT, "MissingHash", change.m_change );
-
return new RedirectResolution( ViewActionBean.class, "view"
).addParameter( "name", "SessionExpired");
}
-
return null;
+
+ }
+
+ public static Resolution validateUTF8Param( WikiActionBean actionBean )
+ {
+ WikiActionBeanContext context = actionBean.getContext();
+ HttpServletRequest request = context.getHttpRequest();
+ if ( request != null )
+ {
+ String utf8field = request.getParameter(
SpamInterceptor.REQ_ENCODING_CHECK );
+ if( utf8field != null && utf8field.equals("\u3041") )
+ {
+ return null;
+ }
+ }
+ String uid = log( context, REJECT, REASON_UTF8_TRAP,
request.getRemoteAddr() );
+ log.info("SPAM:UTF8Trap ("+uid+"). Wildly posting dumb bot
detected.");
+ return new RedirectResolution( ViewActionBean.class, "view"
).addParameter( "name", "SessionExpired");
}
/**
Added:
incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamInterceptor.java
URL:
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamInterceptor.java?rev=755088&view=auto
==============================================================================
---
incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamInterceptor.java
(added)
+++
incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamInterceptor.java
Tue Mar 17 02:30:12 2009
@@ -0,0 +1,53 @@
+package org.apache.wiki.filters;
+
+import net.sourceforge.stripes.action.Resolution;
+import net.sourceforge.stripes.controller.ExecutionContext;
+import net.sourceforge.stripes.controller.Interceptor;
+import net.sourceforge.stripes.controller.Intercepts;
+import net.sourceforge.stripes.controller.LifecycleStage;
+
+import org.apache.wiki.action.WikiActionBean;
+
+/**
+ * Stripes Interceptor that ensures that SpamFilter algorithms are applied to
+ * events annotated with the {...@link SpamProtect} annotation. This class
+ * processes form parameters generated by the
+ * {...@link org.apache.wiki.tags.SpamProtectTag} tag. It fires after the
+ * {...@link LifecycleStage#HandlerResolution} stage; that is, after
ActionBean and
+ * event handler resolution, but before parameter binding.
+ */
+...@intercepts( { LifecycleStage.HandlerResolution } )
+public class SpamInterceptor implements Interceptor
+{
+ /** Request parameter containing the UTF8 check. */
+ public static final String REQ_ENCODING_CHECK = "encodingcheck";
+
+ /** Request parameter containing the encoded payload. */
+ public static final String REQ_SPAM_PARAM = "____sp____";
+
+ /**
+ * Validates spam parameters contained in any requests targeting an
+ * ActionBean method annotated with the {...@link SpamProtect} annotation.
This
+ * simply delegates to {...@link
SpamFilter#validateSpamParams(WikiActionBean)}
+ * and {...@link SpamFilter#validateUTF8Param(WikiActionBean)} in
sequence, and
+ * returns any Resolutions they generate. If the targeted ActionBean event
+ * is not annotated, this method returns <code>null</code>.
+ */
+ public Resolution intercept( ExecutionContext context ) throws Exception
+ {
+ // Execute all other interceptors first
+ context.proceed();
+
+ // Validate spam token/trap params
+ WikiActionBean actionBean = (WikiActionBean) context.getActionBean();
+ Resolution r = SpamFilter.validateSpamParams( actionBean );
+
+ // Validate non-Latin1 param
+ if( r != null )
+ {
+ r = SpamFilter.validateUTF8Param( actionBean );
+ }
+ return r;
+ }
+
+}
Added: incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamProtect.java
URL:
http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamProtect.java?rev=755088&view=auto
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamProtect.java
(added)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/filters/SpamProtect.java
Tue Mar 17 02:30:12 2009
@@ -0,0 +1,24 @@
+/**
+ *
+ */
+package org.apache.wiki.filters;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation indicating that an event handler method should check that the
user
+ * has submitted a series of expected {...@link SpamFilter}-related parameters
+ * with the POST or GET. The SpamProtect annotation can be applied to either
+ * class or method targets. If the annotation applies to a Stripes event
handler
+ * method, the {...@link SpamInterceptor} will apply spam filtering heuristics
to
+ * just the annotated event. If the annotation applies to a class, all event
+ * handler methods will be filtered by default. Method-level annotations always
+ * override class-level annotations.
+ */
+...@documented
+...@inherited
+...@retention( value = RetentionPolicy.RUNTIME )
+...@target( { ElementType.METHOD, ElementType.TYPE } )
+public @interface SpamProtect
+{
+}