{-# LANGUAGE CPP #-}
module Hackage.Security.JSON (
DeserializationError(..)
, validate
, verifyType
, MonadKeys(..)
, addKeys
, withKeys
, lookupKey
, readKeyAsId
, ReadJSON_Keys_Layout
, ReadJSON_Keys_NoLayout
, ReadJSON_NoKeys_NoLayout
, runReadJSON_Keys_Layout
, runReadJSON_Keys_NoLayout
, runReadJSON_NoKeys_NoLayout
, parseJSON_Keys_Layout
, parseJSON_Keys_NoLayout
, parseJSON_NoKeys_NoLayout
, readJSON_Keys_Layout
, readJSON_Keys_NoLayout
, readJSON_NoKeys_NoLayout
, WriteJSON
, runWriteJSON
, renderJSON
, renderJSON_NoLayout
, writeJSON
, writeJSON_NoLayout
, writeKeyAsId
, module Hackage.Security.Util.JSON
) where
import Control.Arrow (first, second)
import Control.Exception
import Control.Monad.Except
import Control.Monad.Reader
import Data.Functor.Identity
import Data.Typeable (Typeable)
import qualified Data.ByteString.Lazy as BS.L
import Hackage.Security.Key
import Hackage.Security.Key.Env (KeyEnv)
import Hackage.Security.TUF.Layout.Repo
import Hackage.Security.Util.JSON
import Hackage.Security.Util.Path
import Hackage.Security.Util.Pretty
import Hackage.Security.Util.Some
import Text.JSON.Canonical
import qualified Hackage.Security.Key.Env as KeyEnv
data DeserializationError =
DeserializationErrorMalformed String
| DeserializationErrorSchema String
| DeserializationErrorUnknownKey KeyId
| DeserializationErrorValidation String
| DeserializationErrorFileType String String
deriving (Typeable)
#if MIN_VERSION_base(4,8,0)
deriving instance Show DeserializationError
instance Exception DeserializationError where displayException = pretty
#else
instance Show DeserializationError where show = pretty
instance Exception DeserializationError
#endif
instance Pretty DeserializationError where
pretty (DeserializationErrorMalformed str) =
"Malformed: " ++ str
pretty (DeserializationErrorSchema str) =
"Schema error: " ++ str
pretty (DeserializationErrorUnknownKey kId) =
"Unknown key: " ++ keyIdString kId
pretty (DeserializationErrorValidation str) =
"Invalid: " ++ str
pretty (DeserializationErrorFileType actualType expectedType) =
"Expected file of type " ++ show expectedType
++ " but got file of type " ++ show actualType
validate :: MonadError DeserializationError m => String -> Bool -> m ()
validate _ True = return ()
validate msg False = throwError $ DeserializationErrorValidation msg
verifyType :: (ReportSchemaErrors m, MonadError DeserializationError m)
=> JSValue -> String -> m ()
verifyType enc expectedType = do
actualType <- fromJSField enc "_type"
unless (actualType == expectedType) $
throwError $ DeserializationErrorFileType actualType expectedType
class (ReportSchemaErrors m, MonadError DeserializationError m) => MonadKeys m where
localKeys :: (KeyEnv -> KeyEnv) -> m a -> m a
askKeys :: m KeyEnv
readKeyAsId :: MonadKeys m => JSValue -> m (Some PublicKey)
readKeyAsId (JSString kId) = lookupKey (KeyId kId)
readKeyAsId val = expected' "key ID" val
addKeys :: MonadKeys m => KeyEnv -> m a -> m a
addKeys keys = localKeys (KeyEnv.union keys)
withKeys :: MonadKeys m => KeyEnv -> m a -> m a
withKeys keys = localKeys (const keys)
lookupKey :: MonadKeys m => KeyId -> m (Some PublicKey)
lookupKey kId = do
keyEnv <- askKeys
case KeyEnv.lookup kId keyEnv of
Just key -> return key
Nothing -> throwError $ DeserializationErrorUnknownKey kId
newtype ReadJSON_Keys_Layout a = ReadJSON_Keys_Layout {
unReadJSON_Keys_Layout :: ExceptT DeserializationError (Reader (RepoLayout, KeyEnv)) a
}
deriving ( Functor
, Applicative
, Monad
, MonadError DeserializationError
)
newtype ReadJSON_Keys_NoLayout a = ReadJSON_Keys_NoLayout {
unReadJSON_Keys_NoLayout :: ExceptT DeserializationError (Reader KeyEnv) a
}
deriving ( Functor
, Applicative
, Monad
, MonadError DeserializationError
)
newtype ReadJSON_NoKeys_NoLayout a = ReadJSON_NoKeys_NoLayout {
unReadJSON_NoKeys_NoLayout :: Except DeserializationError a
}
deriving ( Functor
, Applicative
, Monad
, MonadError DeserializationError
)
instance ReportSchemaErrors ReadJSON_Keys_Layout where
expected str mgot = throwError $ expectedError str mgot
instance ReportSchemaErrors ReadJSON_Keys_NoLayout where
expected str mgot = throwError $ expectedError str mgot
instance ReportSchemaErrors ReadJSON_NoKeys_NoLayout where
expected str mgot = throwError $ expectedError str mgot
expectedError :: Expected -> Maybe Got -> DeserializationError
expectedError str mgot = DeserializationErrorSchema msg
where
msg = case mgot of
Nothing -> "Expected " ++ str
Just got -> "Expected " ++ str ++ " but got " ++ got
instance MonadReader RepoLayout ReadJSON_Keys_Layout where
ask = ReadJSON_Keys_Layout $ fst `liftM` ask
local f act = ReadJSON_Keys_Layout $ local (first f) act'
where
act' = unReadJSON_Keys_Layout act
instance MonadKeys ReadJSON_Keys_Layout where
askKeys = ReadJSON_Keys_Layout $ snd `liftM` ask
localKeys f act = ReadJSON_Keys_Layout $ local (second f) act'
where
act' = unReadJSON_Keys_Layout act
instance MonadKeys ReadJSON_Keys_NoLayout where
askKeys = ReadJSON_Keys_NoLayout $ ask
localKeys f act = ReadJSON_Keys_NoLayout $ local f act'
where
act' = unReadJSON_Keys_NoLayout act
runReadJSON_Keys_Layout :: KeyEnv
-> RepoLayout
-> ReadJSON_Keys_Layout a
-> Either DeserializationError a
runReadJSON_Keys_Layout keyEnv repoLayout act =
runReader (runExceptT (unReadJSON_Keys_Layout act)) (repoLayout, keyEnv)
runReadJSON_Keys_NoLayout :: KeyEnv
-> ReadJSON_Keys_NoLayout a
-> Either DeserializationError a
runReadJSON_Keys_NoLayout keyEnv act =
runReader (runExceptT (unReadJSON_Keys_NoLayout act)) keyEnv
runReadJSON_NoKeys_NoLayout :: ReadJSON_NoKeys_NoLayout a
-> Either DeserializationError a
runReadJSON_NoKeys_NoLayout act =
runExcept (unReadJSON_NoKeys_NoLayout act)
parseJSON_Keys_Layout :: FromJSON ReadJSON_Keys_Layout a
=> KeyEnv
-> RepoLayout
-> BS.L.ByteString
-> Either DeserializationError a
parseJSON_Keys_Layout keyEnv repoLayout bs =
case parseCanonicalJSON bs of
Left err -> Left (DeserializationErrorMalformed err)
Right val -> runReadJSON_Keys_Layout keyEnv repoLayout (fromJSON val)
parseJSON_Keys_NoLayout :: FromJSON ReadJSON_Keys_NoLayout a
=> KeyEnv
-> BS.L.ByteString
-> Either DeserializationError a
parseJSON_Keys_NoLayout keyEnv bs =
case parseCanonicalJSON bs of
Left err -> Left (DeserializationErrorMalformed err)
Right val -> runReadJSON_Keys_NoLayout keyEnv (fromJSON val)
parseJSON_NoKeys_NoLayout :: FromJSON ReadJSON_NoKeys_NoLayout a
=> BS.L.ByteString
-> Either DeserializationError a
parseJSON_NoKeys_NoLayout bs =
case parseCanonicalJSON bs of
Left err -> Left (DeserializationErrorMalformed err)
Right val -> runReadJSON_NoKeys_NoLayout (fromJSON val)
readJSON_Keys_Layout :: ( FsRoot root
, FromJSON ReadJSON_Keys_Layout a
)
=> KeyEnv
-> RepoLayout
-> Path root
-> IO (Either DeserializationError a)
readJSON_Keys_Layout keyEnv repoLayout fp = do
withFile fp ReadMode $ \h -> do
bs <- BS.L.hGetContents h
evaluate $ parseJSON_Keys_Layout keyEnv repoLayout bs
readJSON_Keys_NoLayout :: ( FsRoot root
, FromJSON ReadJSON_Keys_NoLayout a
)
=> KeyEnv
-> Path root
-> IO (Either DeserializationError a)
readJSON_Keys_NoLayout keyEnv fp = do
withFile fp ReadMode $ \h -> do
bs <- BS.L.hGetContents h
evaluate $ parseJSON_Keys_NoLayout keyEnv bs
readJSON_NoKeys_NoLayout :: ( FsRoot root
, FromJSON ReadJSON_NoKeys_NoLayout a
)
=> Path root
-> IO (Either DeserializationError a)
readJSON_NoKeys_NoLayout fp = do
withFile fp ReadMode $ \h -> do
bs <- BS.L.hGetContents h
evaluate $ parseJSON_NoKeys_NoLayout bs
newtype WriteJSON a = WriteJSON {
unWriteJSON :: Reader RepoLayout a
}
deriving ( Functor
, Applicative
, Monad
, MonadReader RepoLayout
)
runWriteJSON :: RepoLayout -> WriteJSON a -> a
runWriteJSON repoLayout act = runReader (unWriteJSON act) repoLayout
renderJSON :: ToJSON WriteJSON a => RepoLayout -> a -> BS.L.ByteString
renderJSON repoLayout = renderCanonicalJSON . runWriteJSON repoLayout . toJSON
renderJSON_NoLayout :: ToJSON Identity a => a -> BS.L.ByteString
renderJSON_NoLayout = renderCanonicalJSON . runIdentity . toJSON
writeJSON :: ToJSON WriteJSON a => RepoLayout -> Path Absolute -> a -> IO ()
writeJSON repoLayout fp = writeLazyByteString fp . renderJSON repoLayout
writeJSON_NoLayout :: ToJSON Identity a => Path Absolute -> a -> IO ()
writeJSON_NoLayout fp = writeLazyByteString fp . renderJSON_NoLayout
writeKeyAsId :: Some PublicKey -> JSValue
writeKeyAsId = JSString . keyIdString . someKeyId