jspwiki-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ajaqu...@apache.org
Subject svn commit: r755088 - in /incubator/jspwiki/trunk/src: WebContent/ WebContent/WEB-INF/ WebContent/WEB-INF/classes/ WebContent/templates/default/ WebContent/templates/default/editors/ java/org/apache/wiki/action/ java/org/apache/wiki/filters/
Date Tue, 17 Mar 2009 02:30:13 GMT
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
+{
+}



Mime
View raw message