packages feed

lio-0.9.0.1: examples/LambdaChair/LambdaChair/TCB.hs

{-# LANGUAGE Unsafe #-}
{-# LANGUAGE OverloadedStrings,
             MultiParamTypeClasses,
             GeneralizedNewtypeDeriving,
             ScopedTypeVariables #-}

{- | 

Basic review system API.

Must compile LIO with @--flags="toLabeled"@.

This is a prototype/toy implementation.  A more serious implementation
will be implemented using the Hails framework.

-}

module LambdaChair.TCB (
  --- * Admin actions
    runReviewDC
  , emptyReviewState
  , addUser
  , addPaper
  , addConflict
  , addAssignment
  , asUser
  --- * User actions
  , findPaper
  , retrievePaper, readPaper
  , retrieveReview, readReview
  , appendToReview
  , reviewDCPutStrLn 
  -- TCB
  , printUsersTCB
  , printReviewsTCB
  , reviewDCPutStrLnTCB 
  , dcPutStrLnTCB 
  ) where

import Prelude hiding (catch)
import Control.Monad
import Control.Exception (SomeException, ErrorCall(..))
import Control.Monad.Trans.Class
import Control.Monad.Trans.State
import Data.Maybe
import Data.List

import LIO
import LIO.TCB
import LIO.Privs.TCB
import LIO.LIORef
import LIO.LIORef.TCB (readLIORefTCB)
import LIO.DCLabel
import LIO.DCLabel.Privs.TCB

import qualified Data.ByteString.Char8 as C


type ErrorStr  = String

-- | Class with sideffectful show
class DCShowTCB s where
  dcShowTCB :: s -> DC String

-- | Print to standard output
dcPutStrLnTCB :: String -> DC ()
dcPutStrLnTCB = ioTCB . putStrLn

-- | Read from standard input
dcGetLineTCB :: DC String
dcGetLineTCB = ioTCB getLine

-- | A name
type Name      = String
-- | A password
type Password  = String
-- | Paper/Rewview content
type Content   = String
-- | A review log
type ReviewLog = String

-- | Paper id
type Id     = Int
-- | A paper is just a wrapper for its contents
data Paper  = Paper  Content
-- | A review is just a wrapper for its contents
data Review = Review ReviewLog

-- | A user contains a name, password a list of conflicting papers and
-- list of papers to review (i.e., assignments).
data User = User { name :: Name
                 , password :: Password
                 , conflicts :: [Id]
                 , assignments :: [Id] }

instance Eq User where
  u1 == u2 = name u1 == name u2

instance DCShowTCB User where
  dcShowTCB u = do
    return $  "Name: " ++ (name u) ++ "\n"
           ++ "Password: " ++ (password u) ++ "\n"
           ++ "Conflicts: " ++ (show . conflicts $ u) ++ "\n"
           ++ "Assignments: " ++ (show . assignments $ u)

-- | Areview entry contains the paper id, paper contents and review
-- log.
data ReviewEnt =  ReviewEnt { paperId :: Id
                            , paper   :: DCRef Paper
                            , review  :: DCRef Review }

instance Eq ReviewEnt where
  r1 == r2 = paperId r1 == paperId r2

instance DCShowTCB ReviewEnt where
  dcShowTCB r = do
    (Paper pap)  <- readLIORefTCB (paper r)
    (Review rev) <- readLIORefTCB (review r)
    return $  "ID:" ++ (show . paperId $ r)
           ++ "\nPaper:" ++ pap
           ++ "\nReviews:" ++ rev


-- Internal state of the 'ReviewDC' monad.
data ReviewState = ReviewState { users :: [User]
                               , reviewEntries :: [ReviewEnt]
                               , curUser :: Maybe Name }

-- | Emtpy state.
emptyReviewState :: ReviewState
emptyReviewState = ReviewState [] [] Nothing

-- | Monad in which all review actions are executed.
newtype ReviewDC a = ReviewDC (StateT ReviewState DC a)
  deriving (Monad)

-- | Lift a 'DC' into thew 'ReviewDC' monad.
instance MonadLIO DCLabel ReviewDC where
  liftLIO = ReviewDC . lift

-- | Get internal state
get' :: ReviewDC ReviewState
get' = ReviewDC . StateT $ \s -> return (s,s)

-- | Update internal state
put' :: ReviewState -> ReviewDC ()
put' s = ReviewDC . StateT $ \_ -> return ((),s)

-- | Execute a review action
runReviewDC :: ReviewDC a -> ReviewState -> DC (a, ReviewState)
runReviewDC (ReviewDC m) s = runStateT m s

--

-- | Get all users
getUsers :: ReviewDC [User]
getUsers = users `liftM` get'

-- | Get all review entries
getReviews :: ReviewDC [ReviewEnt]
getReviews = reviewEntries `liftM` get'

-- | Get priviliges
getCurUserName :: ReviewDC (Maybe Name)
getCurUserName = curUser `liftM` get'

-- | Get current user name
getCurUser :: ReviewDC (Maybe User)
getCurUser = do
  n <- getCurUserName
  maybe (return Nothing) findUser n

-- | Get priviliges of the user executing the action
getPrivs :: ReviewDC DCPriv
getPrivs = do 
  u <- getCurUser
  return $ maybe noPriv (mintTCB . dcPrivDesc . name) u

-- | Updat users
putUsers :: [User] -> ReviewDC ()
putUsers us = do
  rs <- getReviews
  u <- getCurUserName
  put' $ ReviewState us rs u

-- | Update reviews
putReviews :: [ReviewEnt] -> ReviewDC ()
putReviews rs = do
  us <- getUsers
  u <- getCurUserName
  put' $ ReviewState us rs u

-- | Set current user
putCurUserName :: Name -> ReviewDC ()
putCurUserName u = do
  us <- getUsers
  rs <- getReviews
  put' $ ReviewState us rs (Just u)

-- | Remove current user
clearCurUserName :: ReviewDC ()
clearCurUserName = do
  us <- getUsers
  rs <- getReviews
  put' $ ReviewState us rs Nothing

-- | Find review entry by id
findReview :: Id -> ReviewDC (Maybe ReviewEnt)
findReview pId = do
  reviews <- getReviews
  return $ find (\e -> paperId e == pId) reviews

-- | Find user by name
findUser :: Name -> ReviewDC (Maybe User)
findUser n = do
  us <- getUsers
  return $ find (\u -> name u == n) us

-- | Add new (fresh) user
addUser :: Name -> Password -> ReviewDC ()
addUser n p = do
  u <- findUser n
  unless (isJust u) $ do
    let newUser = User { name = n
                       , password = p
                       , conflicts = []
                       , assignments = [] }
    us <- getUsers
    putUsers (newUser:us)

-- | Add conflicting paper to user
addConflict :: Name -> Id -> ReviewDC ()
addConflict n i = do
  usr <- findUser n
  pap <- findReview i
  case (usr, pap) of
    (Just u, Just _) -> 
      if i `elem` (assignments u)
        then return ()
        else do let u' = u { conflicts = i : (conflicts u)}
                usrs <- getUsers
                putUsers $ u' : (filter (/= u) usrs)
    _ -> return ()

-- | Assign a paper for the user to review
addAssignment :: Name -> Id -> ReviewDC ()
addAssignment n i = do
  usr <- findUser n
  pap <- findReview i
  case (usr, pap) of
    (Just u, Just _) -> 
      if i `elem` (conflicts u)
        then return ()
        else do let u' = u { assignments = i : (assignments u)}
                usrs <- getUsers
                putUsers $ u' : (filter (/= u) usrs)
    _ -> return ()

-- | Print users
printUsersTCB :: ReviewDC ()
printUsersTCB = do
 us <- getUsers
 mapM (liftLIO . dcShowTCB) us >>=
   reviewDCPutStrLnTCB . (intercalate "\n--\n")

-- ^ Print papers and reviews
printReviewsTCB :: ReviewDC ()
printReviewsTCB = do
 reviews <- getReviews
 mapM (liftLIO . dcShowTCB) reviews >>=
   reviewDCPutStrLnTCB . (intercalate "\n--\n")

-- ^ Create new paper given id and content
newReviewEnt :: Id -> Content -> ReviewDC ReviewEnt
newReviewEnt pId content = do
  let p1 = toComponent $ "Paper" ++ (show pId)
      r1 = toComponent $ "Review" ++ (show pId)
      pLabel = dcLabel dcTrue p1 
      rLabel = dcLabel r1 r1 
      privs = mintTCB $ dcPrivDesc (p1 /\ r1)
  liftLIO $ do
    rPaper  <- newLIORefP privs pLabel (Paper content)
    rReview <- newLIORefP privs rLabel (Review "")
    return $ ReviewEnt pId rPaper rReview

-- ^ Adda new paper to be reviewed
addPaper :: Content -> ReviewDC Id
addPaper content = do
  reviews <- getReviews
  let pId = 1 + (length reviews)
  ent <- newReviewEnt pId content
  putReviews (ent:reviews)
  return pId


-- ^ Given a paper number return the paper
retrievePaper :: Id -> ReviewDC (Either ErrorStr Content)
retrievePaper pId = do
  mu <- getCurUser
  case mu of
    Nothing -> return $ Left "Need to be logged in"
    Just u -> do
      mRev <- findReview pId 
      case mRev of 
        Nothing -> return $ Left "Invalid Id"
        Just rev -> let as = assignments u
                        priv = mintTCB . fromList $ map id2cat as
                    in  doReadPaper priv rev
       where doReadPaper priv rev = liftLIO $ do
                 (Paper lPaper) <- readLIORefP priv (paper rev)
                 return (Right lPaper)
             id2cat i = [principal . C.pack $ "Review"++(show i)]

-- ^ Given a paper number print the paper
-- NOTE: in the paper, the functionality of @readPaper@ corresponds to
-- that of @retrievePaper@; here, we print out the content.
readPaper :: Id -> ReviewDC ()
readPaper i = retrievePaper i >>= \r -> reviewDCPutStrLn $ show r


-- ^ Given a paper/review number return the review, if the entry exists
retrieveReview :: Id -> ReviewDC (Either ErrorStr Content)
retrieveReview pId = do
  mRev <- findReview pId 
  case mRev of 
    Nothing -> return $ Left "Invalid Id"
    Just rev -> do mu <- getCurUser
                   case mu of
                    Nothing -> return $ Left "Must login first"
                    Just _ -> doReadReview rev
   where doReadReview rev = liftLIO $ do
             (Review r) <- readLIORef (review rev)
             return (Right r)

-- ^ Given a paper/review number print the review, if the entry exists
readReview :: Id -> ReviewDC ()
readReview i = retrieveReview i >>= \r -> reviewDCPutStrLn $ show r

-- ^ Computer the label of the output' channel
getOutputChLbl :: ReviewDC (DCLabel) 
getOutputChLbl = do
  mu <- getCurUser
  case mu of
    Nothing -> liftLIO $ throwLIO (ErrorCall "No user is logged in.")
    Just u -> do
      as <- getReviews >>= return . map paperId -- all reviews
      let cs = conflicts u -- conflicting reviews
          c_cat = map id2conf_cat (cs) -- conflicting categories
          nc_cat = map id2cat (as \\ cs) -- noconflicting categories
      return $ dcLabel (fromList $ c_cat ++ nc_cat) dcTrue
        where id2cat i = [ principal . C.pack $ "Review"++(show i)]
              id2conf_cat i = [ principal . C.pack $ "Review" ++ (show i)
                              , principal $ "CONFLICT" ]
          
-- ^ Print if the current label flows to the output channel label, i.e.,
-- there is no conflict of interest.
dcPutStrLn :: DCLabel -> Content -> DC ()
dcPutStrLn lo cont = do
  l <- getLabel
  if l `canFlowTo` lo
    then dcPutStrLnTCB cont
    else throwLIO . ErrorCall $ "Trying to print conflicting review:\n" ++
                               (show l) ++ " [/= " ++ (show lo)

-- ^ Main printing function. Print to a labeled output channel.
reviewDCPutStrLn :: String -> ReviewDC ()
reviewDCPutStrLn s = do 
  l <- getOutputChLbl
  liftLIO $ dcPutStrLn l $ "-> "++ s

-- | Print line to standard output
reviewDCPutStrLnTCB :: String -> ReviewDC ()
reviewDCPutStrLnTCB = liftLIO . dcPutStrLnTCB

-- ^ Given a paper number and review content append to the current review.
appendToReview :: Id -> Content -> ReviewDC (Either ErrorStr ())
appendToReview pId content = do
  mRev <- findReview pId 
  case mRev of 
    Nothing -> return $ Left "Invalid Id"
    Just rev -> do privs <- getPrivs
                   _ <- doWriteReview privs rev
                   return $ Right ()
   where doWriteReview privs rev = liftLIO $ do
           toLabeledP privs top $ do
             (Review rs) <- readLIORef (review rev)
             -- restrict writes: 
             writeLIORef (review rev) (Review (rs++content))

-- ^ Set the current label to the assignments
assign2curLabel :: [Id] -> ReviewDC() 
assign2curLabel as = liftLIO $ do
  let l = dcLabel dcTrue (fromList $ map id2cat as)
  setLabelP allPrivTCB l
        where id2cat i = [principal . C.pack $ "Review"++(show i)]
  
-- ^ Safely execute untrusted code
safeExecTCB :: ReviewDC () -> ReviewDC ()
safeExecTCB m = do
  s <- get'
  s' <- liftLIO $ do
    cc <- getClearance
    cl <- getLabel
    (_, s') <- (runReviewDC m s) `catchLIO` 
                  (\(_::SomeException) -> do
                        dcPutStrLnTCB "-> ERROR: IFC violated\n"
                        return ((), s))
    setClearanceP allPrivTCB cc
    setLabelP allPrivTCB cl
    return s'
  put' s'

-- ^ Execute on behalf of user
asUser :: Name -> ReviewDC a -> ReviewDC ()
asUser n m = do
  putCurUserName n
  mu <- getCurUser
  case mu of
    Nothing -> return ()
    Just u -> do
      reviewDCPutStrLnTCB $ "| Hi, "++ (name u)++".\n| Password>"
      p <- liftLIO dcGetLineTCB
      if p /= (password u)
        then reviewDCPutStrLnTCB "| Failed, try again" >> asUser n m
        else do
          reviewDCPutStrLnTCB $ "| Executing on behalf of "++(name u)++"...\n"
          safeExecTCB $ assign2curLabel (assignments u) >> m >> return ()
          clearCurUserName

-- | Given a paper prefix return either an error string, if the paper cannot be
-- found or the paper id.
findPaper :: String -> ReviewDC (Either ErrorStr Id)
findPaper s = do
  revs <- getReviews
  res <- mapM (simpleMatch s) revs >>= return . find isJust
  case res of
    Nothing -> return . Left $ "Could not find paper"
    Just i -> return . Right $ fromJust i
      -- ^ Fing paper by checking for prefix
    where simpleMatch :: String -> ReviewEnt -> ReviewDC (Maybe Id)
          simpleMatch m ent = do
            (Paper pap)  <- liftLIO $ readLIORefTCB (paper ent)
            if m `isPrefixOf` pap
              then return . Just $ paperId ent
              else return Nothing