-- | -- Module: ConfCrypt.Validation -- Copyright: (c) 2018 Chris Coffey -- (c) 2018 CollegeVine -- License: MIT -- Maintainer: Chris Coffey -- Stability: experimental -- Portability: portable module ConfCrypt.Validation ( -- * Rule validation runAllRules, -- ** Individual rules parameterTypesMatchSchema, logMissingSchemas, logMissingParameters ) where import ConfCrypt.Types import ConfCrypt.Encryption (decryptValue, MonadDecrypt) import Control.Monad.Except (runExcept, catchError) import Control.Monad.Reader (MonadReader, ask) import Data.Char (isDigit) import Data.Maybe (isNothing) import qualified Data.Text as T import qualified Data.Map as M -- | Apply all validation rules, accumulating the errors across rules. runAllRules :: (Monad m, MonadDecrypt m key, MonadReader (ConfCryptFile, key) m) => m [T.Text] runAllRules = do (ccf, privateKey) <- ask a <- parameterTypesMatchSchema privateKey ccf b <- logMissingSchemas ccf c <- logMissingParameters ccf pure $ filter (not . T.null) $ a <> b <> c -- | For each (Schema, Parameter) pair, confirm that the parameter's value type matches the schema. parameterTypesMatchSchema :: (Monad m, MonadDecrypt m key) => key -> ConfCryptFile -> m [T.Text] parameterTypesMatchSchema key ConfCryptFile {parameters} = traverse decryptAndCompare parameters where decryptAndCompare Parameter {paramName, paramValue, paramType} = catchError (runRule paramType paramName =<< decryptValue key paramValue) (const $ pure ("Error: Could not decrypt " <> paramName)) runRule paramType paramName val = case paramType of Nothing -> pure "" Just CInt | all isDigit $ T.unpack val -> pure "" Just CBoolean | T.toLower val == "true" || T.toLower val == "false" -> pure "" Just CString | not (T.null val) -> pure "" Just CString | T.null val -> pure $ "Warning: "<> paramName <> " is empty" Just pt -> pure $ "Error: "<> paramName <> " does not match the schema type " <> typeToOutputString pt -- | Raise an error if there are parameters without a schema logMissingSchemas :: Monad m => ConfCryptFile -> m [T.Text] logMissingSchemas ConfCryptFile {parameters} = traverse logMissingSchema parameters where logMissingSchema Parameter {paramName, paramType} | isNothing paramType = pure $ "Error: " <> paramName <> " does not have a schema" | otherwise = pure "" -- | Raise an error if there are schema without a parameter logMissingParameters :: Monad m => ConfCryptFile -> m [T.Text] logMissingParameters ConfCryptFile {fileContents} = traverse logMissingParameter . M.toList $ M.filterWithKey (\k _ -> isSchema k) fileContents where isSchema (SchemaLine _) = True isSchema _ = False paramForName name (ParameterLine ParamLine {pName}) = name == pName paramForName name _ = False logMissingParameter (SchemaLine Schema {sName}, _) | M.null $ M.filterWithKey (\k _ -> paramForName sName k) fileContents = pure $ "Error: no matching parameter for schema "<> sName | otherwise = pure "" logMissingParameter _ = pure ""