From e51cb88762706c515a65627a83130b31903e352e Mon Sep 17 00:00:00 2001 From: Jacob Jonsson Date: Sat, 1 Oct 2016 01:40:03 +0200 Subject: [PATCH 1/7] Added config types and functions. Added functions and types for handling private pages. --- data/default.conf | 8 ++++++++ src/Network/Gitit/Config.hs | 2 ++ src/Network/Gitit/Types.hs | 4 +++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/data/default.conf b/data/default.conf index cd528f994..675d6b0d1 100644 --- a/data/default.conf +++ b/data/default.conf @@ -128,6 +128,14 @@ no-edit: Help # specifies pages that cannot be edited through the web interface. # Leave blank to allow every page to be edited. +private-pages: +# specifies pages that are considered private, i.e not accessible for +# anonymous users. This setting overrides any other require-authentication +# setting, visitors to private pages must be logged in. +# Full paths and wildcards are both available, meaning that +# /Dir/Page, /Dir/* and */Page will all blacklist +# the /Dir/Page page. + default-summary: # specifies text to be used in the change description if the author # leaves the "description" field blank. If default-summary is blank diff --git a/src/Network/Gitit/Config.hs b/src/Network/Gitit/Config.hs index 588046a08..834eee563 100644 --- a/src/Network/Gitit/Config.hs +++ b/src/Network/Gitit/Config.hs @@ -108,6 +108,7 @@ extractConfig cp = do cfFrontPage <- get cp "DEFAULT" "front-page" cfNoEdit <- get cp "DEFAULT" "no-edit" cfNoDelete <- get cp "DEFAULT" "no-delete" + cfPrivatePages <- get cp "DEFAULT" "private-pages" cfDefaultSummary <- get cp "DEFAULT" "default-summary" cfDeleteSummary <- get cp "DEFAULT" "delete-summary" cfDisableRegistration <- get cp "DEFAULT" "disable-registration" @@ -207,6 +208,7 @@ extractConfig cp = do , frontPage = cfFrontPage , noEdit = splitCommaList cfNoEdit , noDelete = splitCommaList cfNoDelete + , privatePages = splitCommaList cfPrivatePages , defaultSummary = cfDefaultSummary , deleteSummary = cfDeleteSummary , disableRegistration = cfDisableRegistration diff --git a/src/Network/Gitit/Types.hs b/src/Network/Gitit/Types.hs index aa23ec1e1..7ad47d774 100644 --- a/src/Network/Gitit/Types.hs +++ b/src/Network/Gitit/Types.hs @@ -159,7 +159,9 @@ data Config = Config { -- | Pages that cannot be edited via web noEdit :: [String], -- | Pages that cannot be deleted via web - noDelete :: [String], + noDelete :: [String], + -- | Pages that must cannot be edited by anonymous users. + privatePages :: [String], -- | Default summary if description left blank defaultSummary :: String, -- | Delete summary From 84ed1ddf79dbc190f135a3ea60ff02169d49615a Mon Sep 17 00:00:00 2001 From: Jacob Jonsson Date: Thu, 20 Oct 2016 00:55:49 +0200 Subject: [PATCH 2/7] Added a redirect handler. --- src/Network/Gitit/Framework.hs | 9 +++++++++ src/Network/Gitit/Types.hs | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Network/Gitit/Framework.hs b/src/Network/Gitit/Framework.hs index a86c2fa3b..5d231f246 100644 --- a/src/Network/Gitit/Framework.hs +++ b/src/Network/Gitit/Framework.hs @@ -24,9 +24,11 @@ module Network.Gitit.Framework ( , authenticateUserThat , authenticate , getLoggedInUser + , redirectToLogin -- * Combinators to exclude certain actions , unlessNoEdit , unlessNoDelete + , unlessPrivatePage -- * Guards for routing , guardCommand , guardPath @@ -99,6 +101,13 @@ authenticateUserThat predicate level handler = do else error "Not authorized." else handler +-- | Redirects a request to login view, used as a failure handler. +redirectToLogin :: Handler +redirectToLogin = do + rq <- askRq + let url = rqUri rq ++ rqQuery rq + tempRedirect ("/_login?" ++ urlEncodeVars [("destination", url)]) $ toResponse () + -- | Run the handler after setting @REMOTE_USER@ with the user from -- the session. withUserFromSession :: Handler -> Handler diff --git a/src/Network/Gitit/Types.hs b/src/Network/Gitit/Types.hs index 7ad47d774..42bca03ba 100644 --- a/src/Network/Gitit/Types.hs +++ b/src/Network/Gitit/Types.hs @@ -160,7 +160,7 @@ data Config = Config { noEdit :: [String], -- | Pages that cannot be deleted via web noDelete :: [String], - -- | Pages that must cannot be edited by anonymous users. + -- | Pages that cannot be viewed by anonymous users. privatePages :: [String], -- | Default summary if description left blank defaultSummary :: String, From 5a394f5547af14a493b2da25cf5a8f41e4506125 Mon Sep 17 00:00:00 2001 From: Jacob Jonsson Date: Thu, 20 Oct 2016 00:58:05 +0200 Subject: [PATCH 3/7] Added a handler predicate for private pages. Added a handler predicate, unlessPrivatePage, that takes two handlers as arguments and runs the first if the requested page is not listed as a private page, otherwise it runs the fallback handler. Also wired up the predicate with the default gitit settings. --- src/Network/Gitit.hs | 2 +- src/Network/Gitit/Framework.hs | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/Network/Gitit.hs b/src/Network/Gitit.hs index 3ad25f861..91cbfbf5c 100644 --- a/src/Network/Gitit.hs +++ b/src/Network/Gitit.hs @@ -142,7 +142,7 @@ wiki conf = do serveDirectory' static `mplus` serveDirectory' defaultStatic let debugHandler' = msum [debugHandler | debugMode conf] let handlers = debugHandler' `mplus` authHandler conf `mplus` - authenticate ForRead (msum wikiHandlers) + authenticate ForRead (unlessPrivatePage showPage redirectToLogin `mplus` msum wikiHandlers) let fs = filestoreFromConfig conf let ws = WikiState { wikiConfig = conf, wikiFileStore = fs } if compressResponses conf diff --git a/src/Network/Gitit/Framework.hs b/src/Network/Gitit/Framework.hs index 5d231f246..1bcf94b2e 100644 --- a/src/Network/Gitit/Framework.hs +++ b/src/Network/Gitit/Framework.hs @@ -195,6 +195,38 @@ unlessNoDelete responder fallback = withData $ \(params :: Params) -> do then withMessages ("Page cannot be deleted." : pMessages params) fallback else responder +-- | @unlessPrivatePage responder fallback@ runs @responder@ unless the +-- page is specified as private in configuration; in that case, runs +-- @fallback@ +unlessPrivatePage :: Handler + -> Handler + -> Handler +unlessPrivatePage responder fallback = + withData $ \(params :: Params) -> do + cfg <- getConfig + page <- getPage + let pps = privatePages cfg + if pageIsPrivate page pps + then withMessages ("Page is private, you must log in to view." : pMessages params) fallback + else responder + + where pageIsPrivate :: String -> [String] -> Bool + pageIsPrivate _ [] = False + pageIsPrivate p ps | wildcardExists ps = p `elem` ps || pageMatchWildcards p (filter (elem '*') ps) + | otherwise = p `elem` ps + + wildcardExists :: [String] -> Bool + wildcardExists = foldr (\s b -> ('*' `elem` s) || b) False + + pageMatchWildcards :: String -> [String] -> Bool + pageMatchWildcards p ps = any (isInfixOfAll p) (patterns ps) + + patterns :: [String] -> [[String]] + patterns = map (filter (not . null) . splitOn '*') + + isInfixOfAll :: String -> [String] -> Bool + isInfixOfAll p = foldr (\x -> (&&) (x `isInfixOf` p)) True + -- | Returns the current path (subtracting initial commands like @\/_edit@). getPath :: ServerMonad m => m String getPath = liftM (intercalate "/" . rqPaths) askRq From 24bd6ab5c1bf4375307dfd2125f0aaa6923749f0 Mon Sep 17 00:00:00 2001 From: Jacob Jonsson Date: Fri, 21 Oct 2016 22:12:44 +0200 Subject: [PATCH 4/7] Used an existing package for globbing. --- gitit.cabal | 3 ++- src/Network/Gitit/Framework.hs | 23 ++++------------------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/gitit.cabal b/gitit.cabal index 877c25a1f..815f4ccb4 100644 --- a/gitit.cabal +++ b/gitit.cabal @@ -172,7 +172,8 @@ Library network-uri >= 2.6, network >= 2.6 && < 3.2, network-bsd >= 2.8.1 && < 2.9, - doctemplates >= 0.7.1 + doctemplates >= 0.7.1, + Glob >= 0.7.9 if flag(plugins) exposed-modules: Network.Gitit.Interface build-depends: ghc, ghc-paths diff --git a/src/Network/Gitit/Framework.hs b/src/Network/Gitit/Framework.hs index 1bcf94b2e..bb84ed54f 100644 --- a/src/Network/Gitit/Framework.hs +++ b/src/Network/Gitit/Framework.hs @@ -72,6 +72,7 @@ import Skylighting (syntaxesByFilename, defaultSyntaxMap) import Data.Maybe (fromJust, fromMaybe) import Data.List (intercalate, isPrefixOf, isInfixOf) import System.FilePath ((<.>), takeExtension, takeFileName) +import qualified System.FilePath.Glob as G import Text.ParserCombinators.Parsec import Network.URL (decString, encString) import Network.URI (isUnescapedInURI) @@ -205,28 +206,12 @@ unlessPrivatePage responder fallback = withData $ \(params :: Params) -> do cfg <- getConfig page <- getPage - let pps = privatePages cfg - if pageIsPrivate page pps + let patterns = privatePages cfg + let pageMatchingPatterns = ((flip G.match page) . G.compile) + if ((not . null) patterns) && (any pageMatchingPatterns patterns) then withMessages ("Page is private, you must log in to view." : pMessages params) fallback else responder - where pageIsPrivate :: String -> [String] -> Bool - pageIsPrivate _ [] = False - pageIsPrivate p ps | wildcardExists ps = p `elem` ps || pageMatchWildcards p (filter (elem '*') ps) - | otherwise = p `elem` ps - - wildcardExists :: [String] -> Bool - wildcardExists = foldr (\s b -> ('*' `elem` s) || b) False - - pageMatchWildcards :: String -> [String] -> Bool - pageMatchWildcards p ps = any (isInfixOfAll p) (patterns ps) - - patterns :: [String] -> [[String]] - patterns = map (filter (not . null) . splitOn '*') - - isInfixOfAll :: String -> [String] -> Bool - isInfixOfAll p = foldr (\x -> (&&) (x `isInfixOf` p)) True - -- | Returns the current path (subtracting initial commands like @\/_edit@). getPath :: ServerMonad m => m String getPath = liftM (intercalate "/" . rqPaths) askRq From 5607b7cc045a985b83c69340defc6829e4762a72 Mon Sep 17 00:00:00 2001 From: Jacob Jonsson Date: Sat, 22 Oct 2016 00:57:55 +0200 Subject: [PATCH 5/7] Private pages are now viewable when logged in. --- src/Network/Gitit.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Network/Gitit.hs b/src/Network/Gitit.hs index 91cbfbf5c..08115d4c0 100644 --- a/src/Network/Gitit.hs +++ b/src/Network/Gitit.hs @@ -141,8 +141,9 @@ wiki conf = do let staticHandler = withExpiresHeaders $ serveDirectory' static `mplus` serveDirectory' defaultStatic let debugHandler' = msum [debugHandler | debugMode conf] + let privatePageHandler = unlessPrivatePage (authenticate ForRead showPage) (authenticate Never showPage) let handlers = debugHandler' `mplus` authHandler conf `mplus` - authenticate ForRead (unlessPrivatePage showPage redirectToLogin `mplus` msum wikiHandlers) + privatePageHandler `mplus` (msum wikiHandlers) let fs = filestoreFromConfig conf let ws = WikiState { wikiConfig = conf, wikiFileStore = fs } if compressResponses conf From e75f338ad507d2c65032703171d51e5486c8cc26 Mon Sep 17 00:00:00 2001 From: Jacob Jonsson Date: Sat, 22 Oct 2016 01:34:29 +0200 Subject: [PATCH 6/7] Removed red text that does not do what we want. --- src/Network/Gitit/Framework.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Network/Gitit/Framework.hs b/src/Network/Gitit/Framework.hs index bb84ed54f..13a9fa6bd 100644 --- a/src/Network/Gitit/Framework.hs +++ b/src/Network/Gitit/Framework.hs @@ -209,7 +209,7 @@ unlessPrivatePage responder fallback = let patterns = privatePages cfg let pageMatchingPatterns = ((flip G.match page) . G.compile) if ((not . null) patterns) && (any pageMatchingPatterns patterns) - then withMessages ("Page is private, you must log in to view." : pMessages params) fallback + then fallback else responder -- | Returns the current path (subtracting initial commands like @\/_edit@). From 75284b92b518392a82bea22b8fb7266f73a1a785 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Olivier?= Date: Mon, 8 Jan 2024 21:23:30 +0100 Subject: [PATCH 7/7] documenting what private pages are --- data/Help.page | 10 +++++++++- data/default.conf | 11 +++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/data/Help.page b/data/Help.page index 19e26453d..4dbb6688d 100644 --- a/data/Help.page +++ b/data/Help.page @@ -93,7 +93,7 @@ highlighted in yellow, and deletions will be crossed out with a horizontal line. Clicking on the description of changes will take you to the page as it existed after those changes. To revert the page to the revision you're currently looking at, just click the "revert" button at the bottom -of the page, then "Save". +of the page, then "Save". ## Deleting a page @@ -123,3 +123,11 @@ picture into a (markdown-formatted) page as follows: `![fido](fido.jpg)`. If you uploaded a PDF `projection.pdf`, you can insert a link to it using: `[projection](projection.pdf)`. +# Private content + +By default, wiki content is accessible to all users, whether authenticated +or not. However, it is possible to define pages or directories as private. +In this case, you'll need to be authenticated to access them. Only the +**content** of files will be inaccessible, the name of these files will +remain visible in "All pages" or in "Recent activity". + diff --git a/data/default.conf b/data/default.conf index 675d6b0d1..1c1bfb609 100644 --- a/data/default.conf +++ b/data/default.conf @@ -129,12 +129,11 @@ no-edit: Help # Leave blank to allow every page to be edited. private-pages: -# specifies pages that are considered private, i.e not accessible for -# anonymous users. This setting overrides any other require-authentication -# setting, visitors to private pages must be logged in. -# Full paths and wildcards are both available, meaning that -# /Dir/Page, /Dir/* and */Page will all blacklist -# the /Dir/Page page. +# specifies a comma-separated list of page paths that are considered +# private, i.e not accessible for anonymous users. This setting overrides +# any other require-authentication setting, visitors to private pages must +# be logged in. Full paths and wildcards are both available, meaning that +# Dir/Page, Dir/* and */Page will all blacklist the Dir/Page page. default-summary: # specifies text to be used in the change description if the author