diff --git a/README.markdown b/README.markdown index d3db4b98c..82a66472b 100644 --- a/README.markdown +++ b/README.markdown @@ -326,14 +326,26 @@ setting of `math` in the configuration file: Restricting access ------------------ -If you want to limit account creation on your wiki, the easiest way to do this -is to provide an `access-question` in your configuration file. (See the commented -default configuration file.) Nobody will be able to create an account without -knowing the answer to the access question. +If you want to limit account creation or anonymous edits on your wiki, the +easiest way to do this is to provide an `access-question` in your configuration +file (see the commented default configuration file). Nobody will be able to +create an account or make an anonymous edit without knowing the answer to the +access question. Note that anonymous edits are only enabled when +`require-authentication: none`, otherwise `access-question` only manifests itself +in account creation. Another approach is to use HTTP authentication. (See the config file comments on `authentication-method`.) +If you only want to restrict bots from creating accounts or editing pages +anonymously, you can use one or more of the following methods: + +1. Use the reCAPTCHA service to stop bots from creating accounts (see + `use-recaptcha` configuraiton variable). Currently, `use-recaptcha` does not + restrict anonymous edits. +2. Use the `access-question` configuration variables to frame an access question + that only humans can answer. + Authentication through github ----------------------------- diff --git a/data/default.conf b/data/default.conf index 334593cf2..a9c6e7748 100644 --- a/data/default.conf +++ b/data/default.conf @@ -196,12 +196,17 @@ recaptcha-public-key: access-question: access-question-answers: # specifies a question that users must answer when they attempt to create -# an account, along with a comma-separated list of acceptable answers. -# This can be used to institute a rudimentary password for signing up as -# a user on the wiki, or as an alternative to reCAPTCHA. +# an account or edit a page anonymously, along with a comma-separated list +# of acceptable answers. This can be used to institute a rudimentary +# password for signing up as a user on the wiki, or as an alternative to +# reCAPTCHA. # Example: # access-question: What is the code given to you by Ms. X? # access-question-answers: RED DOG, red dog +# Another example which shows how it could be used as an alternative to +# reCAPTCHA: +# access-question: how many legs in a tripod? +# access-question-answers: three, Three, 3 rpx-domain: rpx-key: diff --git a/src/Network/Gitit/Handlers.hs b/src/Network/Gitit/Handlers.hs index c3642c193..6756c8fec 100644 --- a/src/Network/Gitit/Handlers.hs +++ b/src/Network/Gitit/Handlers.hs @@ -67,7 +67,7 @@ import qualified Control.Exception as E import System.FilePath import Network.Gitit.State import Text.XHtml hiding ( (), dir, method, password, rev ) -import qualified Text.XHtml as X ( method ) +import qualified Text.XHtml as X ( method, password ) import Data.List (intercalate, intersperse, delete, nub, sortBy, find, isPrefixOf, inits, sort, (\\)) import Data.List.Split (wordsBy) import Data.Maybe (fromMaybe, mapMaybe, isJust, catMaybes) @@ -501,6 +501,7 @@ editPage' params = do fs <- getFileStore page <- getPage cfg <- getConfig + mbUser <- getLoggedInUser let getRevisionAndText = E.catch (do c <- liftIO $ retrieve fs (pathForPage page $ defaultExtension cfg) rev -- even if pRevision is set, we return revId of latest @@ -529,12 +530,19 @@ editPage' params = do then [strAttr "readonly" "yes", strAttr "style" "color: gray"] else [] + let accessQ = case mbUser of + Just _ -> noHtml + Nothing -> case accessQuestion cfg of + Nothing -> noHtml + Just (prompt, _) -> label ! [thefor "accessCode"] << prompt +++ br +++ + X.password "accessCode" +++ br base' <- getWikiBase let editForm = gui (base' ++ urlForPage page) ! [identifier "editform"] << [ sha1Box , textarea ! (readonly ++ [cols "80", name "editedText", identifier "editedText"]) << raw , br + , accessQ , label ! [thefor "logMsg"] << "Description of changes:" , br , textfield "logMsg" ! (readonly ++ [value (logMsg `orIfNull` defaultSummary cfg) ]) @@ -630,39 +638,47 @@ updatePage = withData $ \(params :: Params) -> do Just b -> applyPreCommitPlugins b let logMsg = pLogMsg params `orIfNull` defaultSummary cfg let oldSHA1 = pSHA1 params + let accessCode = pAccessCode params + let isValidAccessCode = case mbUser of + Just _ -> True + Nothing -> case accessQuestion cfg of + Nothing -> True + Just (_, answers) -> accessCode `elem` answers fs <- getFileStore base' <- getWikiBase if null . filter (not . isSpace) $ logMsg then withMessages ["Description cannot be empty."] editPage - else do - when (length editedText > fromIntegral (maxPageSize cfg)) $ - error "Page exceeds maximum size." - -- check SHA1 in case page has been modified, merge - modifyRes <- if null oldSHA1 - then liftIO $ create fs (pathForPage page $ defaultExtension cfg) - (Author user email) logMsg editedText >> - return (Right ()) - else do - expireCachedFile (pathForPage page $ defaultExtension cfg) `mplus` return () - liftIO $ E.catch (modify fs (pathForPage page $ defaultExtension cfg) - oldSHA1 (Author user email) logMsg - editedText) - (\e -> if e == Unchanged - then return (Right ()) - else E.throwIO e) - case modifyRes of - Right () -> seeOther (base' ++ urlForPage page) $ toResponse $ p << "Page updated" - Left (MergeInfo mergedWithRev conflicts mergedText) -> do - let mergeMsg = "The page has been edited since you checked it out. " ++ - "Changes from revision " ++ revId mergedWithRev ++ - " have been merged into your edits below. " ++ - if conflicts - then "Please resolve conflicts and Save." - else "Please review and Save." - editPage' $ - params{ pEditedText = Just mergedText, - pSHA1 = revId mergedWithRev, - pMessages = [mergeMsg] } + else if not isValidAccessCode + then withMessages ["Access code is invalid."] editPage + else do + when (length editedText > fromIntegral (maxPageSize cfg)) $ + error "Page exceeds maximum size." + -- check SHA1 in case page has been modified, merge + modifyRes <- if null oldSHA1 + then liftIO $ create fs (pathForPage page $ defaultExtension cfg) + (Author user email) logMsg editedText >> + return (Right ()) + else do + expireCachedFile (pathForPage page $ defaultExtension cfg) `mplus` return () + liftIO $ E.catch (modify fs (pathForPage page $ defaultExtension cfg) + oldSHA1 (Author user email) logMsg + editedText) + (\e -> if e == Unchanged + then return (Right ()) + else E.throwIO e) + case modifyRes of + Right () -> seeOther (base' ++ urlForPage page) $ toResponse $ p << "Page updated" + Left (MergeInfo mergedWithRev conflicts mergedText) -> do + let mergeMsg = "The page has been edited since you checked it out. " ++ + "Changes from revision " ++ revId mergedWithRev ++ + " have been merged into your edits below. " ++ + if conflicts + then "Please resolve conflicts and Save." + else "Please review and Save." + editPage' $ + params{ pEditedText = Just mergedText, + pSHA1 = revId mergedWithRev, + pMessages = [mergeMsg] } indexPage :: Handler indexPage = do