hails-0.11.0.0: Hails/Database/Structured.hs
{-# LANGUAGE Trustworthy #-}
{-# LANGUAGE FlexibleContexts,
MultiParamTypeClasses,
FunctionalDependencies,
FlexibleInstances,
ScopedTypeVariables,
TypeSynonymInstances #-}
{- |
This module exports classes 'DCRecord' and 'DCLabeledRecord' that
provide a way for Hails applications to interact with persistent data
more easily. Specifically, it provides a way to work with Haskell
types as opposed to \"unstructured\" 'Document's.
-}
module Hails.Database.Structured ( DCRecord(..)
, findAll, findAllP
, DCLabeledRecord(..)
, toLabeledDocument, fromLabeledDocument
, toLabeledDocumentP, fromLabeledDocumentP
) where
import Data.Monoid (mappend, mempty)
import Control.Monad (liftM)
import LIO
import LIO.DCLabel
import Hails.Data.Hson
import Hails.PolicyModule
import Hails.Database.Core
import Hails.Database.Query
import Hails.Database.TCB
-- | Class for converting from \"structured\" records to documents
-- (and vice versa). Minimal definition consists of 'toDocument',
-- 'fromDocument', and 'recordCollection'. All database operations
-- performed on the collection defined by 'recordCollection'.
class DCRecord a where
-- | Convert a document to a record
fromDocument :: Monad m => Document -> m a
-- | Convert a record to a document
toDocument :: a -> Document
-- | Get the collection name for the record
recordCollection :: a -> CollectionName
-- | Find an object with matching value for the given key. If the
-- object does not exist or cannot be read (its label is above the
-- clearance), this returns 'Nothing'.
findBy :: (BsonVal v, MonadDB m)
=> CollectionName -> FieldName -> v -> m (Maybe a)
-- | Find an object with given query
findWhere :: MonadDB m => Query -> m (Maybe a)
-- | Insert a record into the database
insertRecord :: MonadDB m => a -> m ObjectId
-- | Update a record in the database
saveRecord :: MonadDB m => a -> m ()
-- | Same as 'findBy', but uses privileges.
findByP :: (BsonVal v, MonadDB m)
=> DCPriv -> CollectionName -> FieldName -> v -> m (Maybe a)
-- | Same as 'findWhere', but uses privileges.
findWhereP :: MonadDB m => DCPriv -> Query -> m (Maybe a)
-- | Same as 'insertRecord', but uses privileges.
insertRecordP :: MonadDB m => DCPriv -> a -> m ObjectId
-- | Same as 'saveRecord', but uses privileges.
saveRecordP :: MonadDB m => DCPriv -> a -> m ()
--
-- Default definitions
--
--
findBy = findByP mempty
--
findWhere = findWhereP mempty
--
insertRecord = insertRecordP mempty
--
saveRecord = saveRecordP mempty
--
insertRecordP p r = liftDB $ do
insertP p (recordCollection r) $ toDocument r
--
saveRecordP p r = liftDB $ do
saveP p (recordCollection r) $ toDocument r
--
findByP p cName k v =
findWhereP p (select [k -: v] cName)
--
findWhereP p query = liftDB $ do
mldoc <- findOneP p query
c <- liftLIO $ getClearance
case mldoc of
Just ldoc | canFlowToP p (labelOf ldoc) c ->
fromDocument `liftM` (liftLIO $ unlabelP p ldoc)
_ -> return Nothing
-- --
-- deleteByP p policy colName k v =
-- deleteWhereP p policy (select [k =: v] colName)
-- --
-- deleteWhereP p policy sel = do
-- -- Find with only supplied privileges
-- mdoc <- findWhereP p policy $ select (selector sel) (coll sel)
-- -- User underlying privileges as well:
-- p' <- getPrivileges
-- res <- withDB policy $ deleteOneP (p' `mappend` p) sel
-- case res of
-- Right _ -> return mdoc
-- _ -> return Nothing
-- --
-- | Find all records that satisfy the query and can be read, subject
-- to the current clearance.
findAll :: (DCRecord a, MonadDB m) => Query -> m [a]
findAll = findAllP mempty
-- | Same as 'findAll', but uses privileges.
findAllP :: (DCRecord a, MonadDB m)
=> DCPriv -> Query -> m [a]
findAllP p query = liftDB $ do
cursor <- findP p query
cursorToRecords cursor []
where cursorToRecords cur docs = do
mldoc <- nextP p cur
case mldoc of
Just ldoc -> do
c <- liftLIO getClearance
if canFlowTo (labelOf ldoc) c
then do md <- fromDocument `liftM` (liftLIO $ unlabelP p ldoc)
cursorToRecords cur $ maybe docs (:docs) md
else cursorToRecords cur docs
_ -> return $ reverse docs
-- | Class used by a policy module to translate a labeled record to a
-- labeled document. Since the insert and save functions use the
-- policy module\'s privileges, only the policy module should be
-- allowed to create an instance of this class. Thus, we leverage the
-- fact that the value constructor for a 'PolicyModule' is not exposed
-- to untrusted code and require the policy module to create such a
-- value in 'endorseInstance'. The minimal implementation needs to
-- define 'endorseInstance'.
class (PolicyModule pm, DCRecord a) => DCLabeledRecord pm a | a -> pm where
-- | Insert a labeled record into the database.
insertLabeledRecord :: MonadDB m => DCLabeled a -> m ObjectId
-- | Insert a labeled record into the database
saveLabeledRecord :: MonadDB m => DCLabeled a -> m ()
-- | Same as 'insertLabeledRecord', but using explicit privileges.
insertLabeledRecordP :: MonadDB m => DCPriv -> DCLabeled a -> m ObjectId
-- | Same as 'saveLabeledRecord', but using explicit privileges.
saveLabeledRecordP :: MonadDB m => DCPriv -> DCLabeled a -> m ()
-- | Endorse the implementation of this instance. Note that this is
-- reduced to WHNF to catch invalid instances that use 'undefined'.
--
-- Example implementation:
--
-- > endorseInstance _ = MyPolicyModuleTCB {- May leave other values undefined -}
endorseInstance :: DCLabeled a -> pm
--
-- Default definitions for insert/save
--
--
insertLabeledRecord lrec = insertLabeledRecordP mempty lrec
--
saveLabeledRecord lrec = saveLabeledRecordP mempty lrec
--
insertLabeledRecordP p lrec = liftDB $ do
let cName = recordCollection (forceType lrec)
ldoc <- toLabeledDocumentP p lrec
insertP p cName ldoc
--
saveLabeledRecordP p lrec = liftDB $ do
let cName = recordCollection (forceType lrec)
ldoc <- toLabeledDocumentP p lrec
saveP p cName ldoc
-- | Convert labeled record to labeled document.
toLabeledDocument :: (MonadDB m, DCLabeledRecord pm a)
=> DCLabeled a
-> m (DCLabeled Document)
toLabeledDocument = toLabeledDocumentP mempty
-- | Uses the policy modules\'s privileges to convert a labeled record
-- to a labeled document, if the policy module created an instance of
-- 'DCLabeledRecord'.
toLabeledDocumentP :: (MonadDB m, DCLabeledRecord pm a)
=> DCPriv
-> DCLabeled a -- ^ Labeled record
-> m (DCLabeled Document)
toLabeledDocumentP p' lr = liftDB $ do
pmPriv' <- dbActionPriv `liftM` getActionStateTCB
liftLIO $ do
-- Fail if not endorsed:
pmPriv <- (evaluate . endorseInstance $ lr) >> return pmPriv'
`catch` (\(_ :: SomeException) -> return mempty)
let p = p' `mappend` pmPriv
scopeClearance $ do
-- raise clearance:
clr <- getClearance
setClearanceP p $ clr `lub` (p %% True)
--
r <- unlabelP p lr
lcur <- getLabel
let lres = downgradeP p lcur `lub` (labelOf lr)
labelP p lres $ toDocument r
-- | Convert labeled document to labeled record
fromLabeledDocument :: forall m pm a. (MonadDB m, DCLabeledRecord pm a)
=> DCLabeled Document
-> m (DCLabeled a)
fromLabeledDocument = fromLabeledDocumentP mempty
-- | Uses the policy modules\'s privileges to convert a labeled document
-- to a labeled record, if the policy module created an instance of
-- 'DCLabeledRecord'.
fromLabeledDocumentP :: forall m pm a. (MonadDB m, DCLabeledRecord pm a)
=> DCPriv
-> DCLabeled Document
-> m (DCLabeled a)
fromLabeledDocumentP p' ldoc = liftDB $ do
pmPriv' <- dbActionPriv `liftM` getActionStateTCB
-- Fail if not endorsed:
pmPriv <- liftLIO $ (evaluate . endorseInstance $ fake) >> return pmPriv'
`catch` (\(_ :: SomeException) -> return mempty)
let p = p' `mappend` pmPriv
liftLIO $ scopeClearance $ do
-- raise clearance:
clr <- getClearance
setClearanceP p $ clr `lub` (p %% True)
-- get at the document
doc <- liftLIO $ unlabelP p ldoc
lcur <- liftLIO $ getLabel
let lres = downgradeP p lcur `lub` (labelOf ldoc)
rec <- fromDocument doc
labelP p lres rec
where fake :: DCLabeled a
fake = undefined
--
-- Misc helpers
--
-- | Get the type of a 'DCLabeled' value
forceType :: DCLabeled a -> a
forceType = undefined