diff --git a/hledger/Hledger/Cli/Anon.hs b/hledger/Hledger/Cli/Anon.hs new file mode 100644 index 000000000..09b791e4d --- /dev/null +++ b/hledger/Hledger/Cli/Anon.hs @@ -0,0 +1,52 @@ +{-| + +Instances for anonymizing sensitive data in various types. + +Note that there is no clear way to anonymize numbers. + +-} + +module Hledger.Cli.Anon + ( Anon(..) + , anonAccount + ) +where + +import Control.Arrow (first) +import Data.Hashable (hash) +import Data.Word (Word32) +import Numeric (showHex) +import qualified Data.Text as T + +import Hledger.Data + +class Anon a where + -- | Consistent converter to structure with sensitive data anonymized + anon :: a -> a + +instance Anon Journal where + -- Apply the anonymisation transformation on a journal after finalisation + anon j = j { jtxns = map anon . jtxns $ j + , jparseparentaccounts = map anonAccount $ jparseparentaccounts j + , jparsealiases = [] -- already applied + , jdeclaredaccounts = map (first anon) $ jdeclaredaccounts j + } + +instance Anon Posting where + anon p = p { paccount = anonAccount . paccount $ p + , pcomment = T.empty + , ptransaction = fmap anon . ptransaction $ p -- Note that this will be overriden + , poriginal = anon <$> poriginal p + } + +instance Anon Transaction where + anon txn = txnTieKnot $ txn { tpostings = map anon . tpostings $ txn + , tdescription = anon . tdescription $ txn + , tcomment = T.empty + } + +-- | Anonymize account name preserving hierarchy +anonAccount :: AccountName -> AccountName +anonAccount = T.intercalate (T.pack ":") . map anon . T.splitOn (T.pack ":") + +instance Anon T.Text where anon = T.pack . flip showHex "" . (fromIntegral :: Int -> Word32) . hash diff --git a/hledger/Hledger/Cli/Utils.hs b/hledger/Hledger/Cli/Utils.hs index 2f74e2efb..15d5bceb4 100644 --- a/hledger/Hledger/Cli/Utils.hs +++ b/hledger/Hledger/Cli/Utils.hs @@ -31,14 +31,11 @@ where import Control.Exception as C import Control.Monad -import Data.Hashable (hash) import Data.List import Data.Maybe import qualified Data.Text as T import qualified Data.Text.IO as T import Data.Time (Day, addDays) -import Data.Word -import Numeric import Safe (readMay) import System.Console.CmdArgs import System.Directory (getModificationTime, getDirectoryContents, copyFile) @@ -54,6 +51,7 @@ import System.Time (ClockTime(TOD)) import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds) import Hledger.Cli.CliOptions +import Hledger.Cli.Anon import Hledger.Data import Hledger.Read import Hledger.Reports @@ -98,27 +96,9 @@ pivotByOpts opts = -- | Apply the anonymisation transformation on a journal, if option is present anonymiseByOpts :: CliOpts -> Journal -> Journal anonymiseByOpts opts = - case maybestringopt "anon" . rawopts_ $ opts of - Just _ -> anonymise - Nothing -> id - --- | Apply the anonymisation transformation on a journal -anonymise :: Journal -> Journal -anonymise j - = let - pAnons p = p { paccount = T.intercalate (T.pack ":") . map anon . T.splitOn (T.pack ":") . paccount $ p - , pcomment = T.empty - , ptransaction = fmap tAnons . ptransaction $ p - , poriginal = pAnons <$> poriginal p - } - tAnons txn = txn { tpostings = map pAnons . tpostings $ txn - , tdescription = anon . tdescription $ txn - , tcomment = T.empty - } - in - j { jtxns = map tAnons . jtxns $ j } - where - anon = T.pack . flip showHex "" . (fromIntegral :: Int -> Word32) . hash + if anon_ . inputopts_ $ opts + then anon + else id -- | Generate periodic transactions from all periodic transaction rules in the journal. -- These transactions are added to the in-memory Journal (but not the on-disk file). diff --git a/hledger/hledger.cabal b/hledger/hledger.cabal index 74a0844c3..2e63579e9 100644 --- a/hledger/hledger.cabal +++ b/hledger/hledger.cabal @@ -4,7 +4,7 @@ cabal-version: 1.12 -- -- see: https://github.com/sol/hpack -- --- hash: cfbd7109f5527399580a07f847fc8f5d01951caffc3667948b6348f82183be52 +-- hash: 8f2e354491d77c4334484836a2b1f0f95f6810abf49fffeaebeb19d37132a15a name: hledger version: 1.15.99 @@ -109,6 +109,7 @@ library Hledger.Cli.CliOptions Hledger.Cli.DocFiles Hledger.Cli.Utils + Hledger.Cli.Anon Hledger.Cli.Version Hledger.Cli.Commands Hledger.Cli.Commands.Accounts diff --git a/hledger/package.yaml b/hledger/package.yaml index 43040272a..927f12d1d 100644 --- a/hledger/package.yaml +++ b/hledger/package.yaml @@ -155,6 +155,7 @@ library: - Hledger.Cli.CliOptions - Hledger.Cli.DocFiles - Hledger.Cli.Utils + - Hledger.Cli.Anon - Hledger.Cli.Version - Hledger.Cli.Commands - Hledger.Cli.Commands.Accounts diff --git a/tests/journal/anon.test b/tests/journal/anon.test new file mode 100644 index 000000000..9c2ac5c9d --- /dev/null +++ b/tests/journal/anon.test @@ -0,0 +1,35 @@ + +# Input for the following tests: + +account assets +account expenses +alias tips=expenses:tips + +2019-01-01 (receipt) ; signed + (assets) 2 + +2019-02-01 borrow + (liabilities) 1 + (tips) 3 + +# Basic tests on accounts + +$ hledger -f- print --anon +> !/assets|liabilities|expenses|tips/ + +$ hledger -f- reg --anon +> !/assets|liabilities|expenses|tips/ + +$ hledger -f- bal --anon +> !/assets|liabilities|expenses|tips/ + +$ hledger -f- accounts --anon +> !/assets|liabilities|expenses|tips/ + +# Basic tests on descriptions and comments + +$ hledger -f- print --anon +> !/borrow|signed/ + +$ hledger -f- reg --anon +> !/borrow/