-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | Multi-app web platform framework -- -- The rise of web platforms and their associated apps represents -- a new way of developing and deploying software. Sites such as Facebook -- and Yammer are no longer written by a single entity, but rather are -- freely extended by third-party developers offering competing features -- to users. -- -- Allowing an app to access more user data allows developers to build -- more compelling products. It also opens the door to accidental or -- malicious breaches of user privacy. In the case of a website like -- Facebook, exposing access to a user's private messages would allow an -- external developer to build a search feature. Exciting! But, another -- developer can take advantage of this feature to build an app that -- mines private messages for credit card numbers, ad keywords, or other -- sensitive data. -- -- Frameworks such as Ruby on Rails, Django, Yesod, etc. are geared -- towards building monolithic web sites. And, they are great for this! -- However, they are not designed for websites that integrate third-party -- code, and thus lack a good mechanism for building such multi-app -- platforms without sacrificing a user's security or an app's -- functionality. -- -- Hails is explicitly designed for building web platforms, where -- it is expected that a site will comprise many mutually-distrustful -- components written by a variety of entities. We built Hails around two -- core design principles. -- --
-- X-Hails-Label = { isPublic: true }
--
--
-- or
--
--
-- X-Hails-Label = { isPublic: false, label : ["http://google.com:80", "alice"] }
--
guardSensitiveResp :: Middleware
-- | Remove anything from the response that could cause inadvertant
-- declasification. Currently this only removes the Set-Cookie
-- header.
sanitizeResp :: Middleware
-- | Catch all exceptions thrown by middleware and return 500.
catchAllExceptions :: Middleware
-- | Exports basic HTTP client functions inside the DC Monad.
-- Computations are allowed to communicate over HTTP as long as they can
-- read and write to a labeled origin. An origin is associated with two
-- labels. When writing, the origin has a label of the form <
-- "scheme://authority", |True >, where scheme is either
-- 'http' or 'https', and authority is the domain name or IP
-- address used in the request and port number of the connection. In
-- other words, the secrecy component contains the origin information,
-- while the integrity component is the same as that of public data. When
-- reading, the origin has a label of the form < |True,
-- "scheme://authority" >.
--
-- This means that DC computations can export data if the current
-- label is not higher than that of the labeled origin, and read data
-- that is no more trustworthy than that of the origin. Practically, this
-- means that untrusted computation can export data so long as the they
-- have not observed any data more sensitive than the label of the target
-- domain. Reading (which also occurs on every request/write) further
-- raises the current label to the join of the current label and origin.
--
-- For example, suppose some piece of data, myLoc, has the
-- label:
--
--
-- aliceLocL = dcLabel ("alice" /\ "http://maps.googleapis.com:80") dcTrue
--
--
-- created as:
--
-- -- myLoc <- labelP alicePriv aliceLocL "3101 24th Street, San Francisco, CA" ---- -- Then, untrusted code (with initial label set to public) running on -- behalf of "alice" , may perform the following operation: -- --
-- let mapBase = "http://maps.googleapis.com/maps/api/geocode/json?sensor=false" -- aliceLoc <- unlabelP alicePriv myLoc -- resp <- simpleGetHttp $ mapBase ++ "&address=" ++ aliceLoc ---- -- In this case the unlabelP will raise the current label to the -- label: -- --
-- < "http://maps.googleapis.com:80", |True > ---- -- by exercising "alice"s privilges. Directly, the simpleHttp will -- be permitted. However, if -- --
-- let mapBase = "http://maps.evilalternatives.org/geocode/json?sensor=false" ---- -- an exception will be thrown since the current label does not flow to -- the label of mapBase. -- -- This module uses 'http-conduit' as the underlying client, we recommend -- looking at the Network.HTTP.Conduit documentation on how to -- construct Requests. Here, we highlight some important details: -- --
-- req <- parseUrl mapBase
-- resp <- simpleGetHttp $ req { checkStatus = \s@(Status sci _) hs ->
-- if 200 <= sci && sci < 300
-- then Nothing
-- else Just $ toException $ StatusCodeException s hs }
--
module Hails.HttpClient
-- | Reques type, wrapper for the conduit Request.
type Request = Request (ResourceT IO)
-- | HTTP request method, eg GET, POST.
method :: Request m -> Method
-- | Whether to use HTTPS (ie, SSL).
secure :: Request m -> Bool
host :: Request m -> ByteString
port :: Request m -> Int
-- | Everything from the host to the query string.
path :: Request m -> ByteString
queryString :: Request m -> ByteString
-- | Custom HTTP request headers
--
-- As already stated in the introduction, the Content-Length and Host
-- headers are set automatically by this module, and shall not be added
-- to requestHeaders.
--
-- Moreover, the Accept-Encoding header is set implicitly to gzip for
-- convenience by default. This behaviour can be overridden if needed, by
-- setting the header explicitly to a different value. In order to omit
-- the Accept-Header altogether, set it to the empty string "". If you
-- need an empty Accept-Header (i.e. requesting the identity encoding),
-- set it to a non-empty white-space string, e.g. " ". See RFC 2616
-- section 14.3 for details about the semantics of the Accept-Header
-- field. If you request a content-encoding not supported by this module,
-- you will have to decode it yourself (see also the decompress
-- field).
--
-- Note: Multiple header fields with the same field-name will result in
-- multiple header fields being sent and therefore it's the
-- responsibility of the client code to ensure that the rules from RFC
-- 2616 section 4.2 are honoured.
requestHeaders :: Request m -> RequestHeaders
requestBody :: Request m -> RequestBody m
-- | If True, a chunked and/or gzipped body will not be decoded.
-- Use with caution.
rawBody :: Request m -> Bool
-- | How many redirects to follow when getting a resource. 0 means follow
-- no redirects. Default value: 10.
redirectCount :: Request m -> Int
-- | Check the status code. Note that this will run after all redirects are
-- performed. Default: return a StatusCodeException on non-2XX
-- responses.
checkStatus :: Request m -> Status -> ResponseHeaders -> CookieJar -> Maybe SomeException
-- | Predicate to specify whether gzipped data should be decompressed on
-- the fly (see alwaysDecompress and
-- browserDecompress). Default: browserDecompress.
decompress :: Request m -> ContentType -> Bool
-- | A response sent by the app.
data Response
Response :: Status -> ResponseHeaders -> ByteString -> Response
-- | Response status
respStatus :: Response -> Status
-- | Response headers
respHeaders :: Response -> ResponseHeaders
-- | Response body
respBody :: Response -> ByteString
-- | Convert a URL into a Request.
--
-- This defaults some of the values in Request, such as setting
-- method to GET and requestHeaders to [].
parseUrl :: String -> DC Request
-- | Add a Basic Auth header (with the specified user name and password) to
-- the given Request. Ignore error handling:
--
-- applyBasicAuth user pass $ fromJust $ parseUrl url
applyBasicAuth :: ByteString -> ByteString -> Request m -> Request m
-- | Perform a simple HTTP(S) request.
simpleHttp :: Request -> DC Response
-- | Same as simpleHttp, but uses privileges.
simpleHttpP :: DCPriv -> Request -> DC Response
-- | Simple HTTP GET request.
simpleGetHttp :: String -> DC Response
-- | Simple HTTP GET request.
simpleGetHttpP :: DCPriv -> String -> DC Response
-- | Simple HTTP HEAD request.
simpleHeadHttp :: String -> DC Response
-- | Simple HTTP HEAD request.
simpleHeadHttpP :: DCPriv -> String -> DC Response
data HttpException :: *
StatusCodeException :: Status -> ResponseHeaders -> CookieJar -> HttpException
InvalidUrlException :: String -> String -> HttpException
-- | List of encountered responses containing redirects in reverse
-- chronological order; including last redirect, which triggered the
-- exception and was not followed.
TooManyRedirects :: [Response ByteString] -> HttpException
-- | Response containing unparseable redirect.
UnparseableRedirect :: Response ByteString -> HttpException
TooManyRetries :: HttpException
HttpParserException :: String -> HttpException
HandshakeFailed :: HttpException
OverlongHeaders :: HttpException
ResponseTimeout :: HttpException
-- | host/port
FailedConnectionException :: String -> Int -> HttpException
ExpectedBlankAfter100Continue :: HttpException
InvalidStatusLine :: ByteString -> HttpException
InvalidHeader :: ByteString -> HttpException
InternalIOException :: IOException -> HttpException
-- | host/port
ProxyConnectException :: ByteString -> Int -> Either ByteString HttpException -> HttpException
NoResponseDataReceived :: HttpException
TlsException :: SomeException -> HttpException
instance Exception e => Failure e (LIO DCLabel)
-- | This module defines some convenience functions for creating responses.
module Hails.Web.Responses
-- | Creates a 200 (OK) Response with the given content-type and
-- resposne body
ok :: ContentType -> ByteString -> Response
-- | Creates a 200 (OK) Response with content-type "text/html" and
-- the given resposne body
okHtml :: ByteString -> Response
-- | Given a URL returns a 301 (Moved Permanently) Response
-- redirecting to that URL.
movedTo :: String -> Response
-- | Given a URL returns a 303 (See Other) Response redirecting to
-- that URL.
redirectTo :: String -> Response
-- | Returns a 400 (Bad Request) Response.
badRequest :: Response
-- | Returns a 401 (Authorization Required) Response requiring basic
-- authentication in the given realm.
requireBasicAuth :: String -> Response
-- | Returns a 403 (Forbidden) Response.
forbidden :: Response
-- | Returns a 404 (Not Found) Response.
notFound :: Response
-- | Returns a 500 (Server Error) Response.
serverError :: Response
-- | Conceptually, a route is function that, given an HTTP request, may
-- return an action (something that would return a response for the
-- client if run). Routes can be concatenated--where each route is
-- evaluated until one matches--and nested. Routes are expressed through
-- the Routeable type class. runRoute transforms an
-- instance of Routeable to a function from Request to a
-- monadic action (in the ResourceT monad) that returns a
-- Maybe Response. The return type was chosen to be monadic
-- so routing decisions can depend on side-effects (e.g. a random number
-- or counter for A/B testing, IP geolocation lookup etc').
module Hails.Web.Router
-- | Routeable types can be converted into a route function using
-- runRoute. If the route is matched it returns a Response,
-- otherwise Nothing.
--
-- In general, Routeables are data-dependant (on the
-- Request), but don't have to be. For example, Application
-- is an instance of Routeable that always returns a
-- Response:
--
-- -- instance Routeable Application where -- runRoute app req = app req >>= return . Just --class Routeable r runRoute :: Routeable r => r -> RouteHandler -- | Converts any Routeable into an Application that can be -- passed directly to a WAI server. mkRouter :: Routeable r => r -> Application -- | Synonym for RouteM, the common case where the data parameter is -- '()'. type Route = RouteM () -- | The RouteM type is a basic instance of Routeable that -- simply holds the routing function and an arbitrary additional data -- parameter. In most cases this paramter is simply '()', hence we have a -- synonym for RouteM '()' called Route. The power -- is derived from the instances of Monad and Monoid, which -- allow the simple construction of complex routing rules using either -- lists (Monoid) or do-notation. Moreover, because of it's simple -- type, any Routeable can be used as a Route (using -- routeAll or by applying it to runRoute), making it -- possible to leverage the monadic or monoid syntax for any -- Routeable. -- -- Commonly, route functions that construct a Route only inspect -- the Request and other parameters. For example, routeHost -- looks at the hostname: -- --
-- routeHost :: Routeable r => S.ByteString -> r -> Route -- routeHost host route = Route func () -- where func req = if host == serverName req -- then runRoute route req -- else return Nothing ---- -- However, because the result of a route is in the ResourceT -- monad, routes have all the power of an Application and can make -- state-dependant decisions. For example, it is trivial to implement a -- route that succeeds for every other request (perhaps for A/B testing): -- --
-- routeEveryOther :: (Routeable r1, Routeable r2) -- => MVar Int -> r1 -> r2 -> Route -- routeEveryOther counter r1 r2 = Route func () -- where func req = do -- i liftIO . modifyMVar $ \i - -- let i' = i+1 -- in return (i', i') -- if i mod 2 == 0 -- then runRoute r1 req -- else runRoute r2 req --data RouteM a Route :: RouteHandler -> a -> RouteM a -- | A route that always matches (useful for converting a Routeable -- into a Route). routeAll :: Routeable r => r -> Route -- | Matches on the hostname from the Request. The route only -- successeds on exact matches. routeHost :: Routeable r => ByteString -> r -> Route -- | Matches if the path is empty. Note that this route checks that -- pathInfo is empty, so it works as expected when nested under -- namespaces or other routes that pop the pathInfo list. routeTop :: Routeable r => r -> Route -- | Matches on the HTTP request method (e.g. GET, POST, -- PUT) routeMethod :: Routeable r => StdMethod -> r -> Route -- | Routes the given URL pattern. Patterns can include directories as well -- as variable patterns (prefixed with :) to be added to -- queryString (see routeVar) -- --
-- module SimpleFrank (server) where
--
-- import Data.String
-- import Data.Maybe
-- import Control.Monad
--
-- import LIO
-- import Hails.HttpServer.Types
-- import Hails.Web
-- import qualified Hails.Web.Frank as F
--
-- server :: Application
-- server = mkRouter $ do
-- F.get "/users" $ do
-- req <- request >>= unlabel
-- return $ okHtml $ fromString $
-- "Welcome Home " ++ (show $ serverName req)
-- F.get "/users/:id" $ do
-- userId <- fromMaybe "" `liftM` queryParam "id"
-- return $ ok "text/json" $ fromString $
-- "{\"myid\": " ++ (show userId) ++ "}"
-- F.put "/user/:id" $ do
-- ...
--
--
-- With hails, you can directly run this:
--
-- -- hails --app=SimpleFrank ---- -- And, with curl, you can now checkout your page: -- --
-- $ curl localhost:8080/users
-- Welcome Home "localhost"
--
-- $ curl localhost:8080/users/123
-- {"myid": "123"}
--
-- $ ...
--
module Hails.Web.Frank
-- | Matches the GET method on the given URL pattern
get :: Routeable r => ByteString -> r -> Route
-- | Matches the POST method on the given URL pattern
post :: Routeable r => ByteString -> r -> Route
-- | Matches the PUT method on the given URL pattern
put :: Routeable r => ByteString -> r -> Route
-- | Matches the DELETE method on the given URL pattern
delete :: Routeable r => ByteString -> r -> Route
-- | Matches the OPTIONS method on the given URL pattern
options :: Routeable r => ByteString -> r -> Route
-- | REST is a DSL for creating routes using RESTful HTTP verbs. See
-- http://en.wikipedia.org/wiki/Representational_state_transfer
--
-- For example, an app handling users may define a REST controller as:
--
--
-- module SimpleREST (server) where
--
-- import Data.String
-- import Data.Maybe
-- import Control.Monad
--
-- import LIO
-- import Hails.HttpServer.Types
-- import Hails.Web
-- import qualified Hails.Web.REST as REST
--
-- server :: Application
-- server = mkRouter $ routeName "users" $ do
-- REST.index $ do
-- req <- request >>= unlabel
-- return $ okHtml $ fromString $
-- "Welcome Home " ++ (show $ serverName req)
-- REST.show $ do
-- userId <- fromMaybe "" `liftM` queryParam "id"
-- return $ ok "text/json" $ fromString $
-- "{\"myid\": " ++ (show userId) ++ "}"
-- ...
--
--
-- With hails, you can directly run this:
--
-- -- hails --app=SimpleREST ---- -- And, with curl, you can now checkout your page: -- --
-- $ curl localhost:8080/users
-- Welcome Home "localhost"
--
-- $ curl localhost:8080/users/123
-- {"myid": "123"}
--
-- $ ...
--
module Hails.Web.REST
-- | Monad used to encode a REST controller incrementally. The return type
-- is not used, hence always '()'.
type RESTController = RESTControllerM ()
-- | GET /
index :: Routeable r => r -> RESTController
-- | GET /:id
show :: Routeable r => r -> RESTController
-- | POST /
create :: Routeable r => r -> RESTController
-- | PUT /:id
update :: Routeable r => r -> RESTController
-- | DELETE /:id
delete :: Routeable r => r -> RESTController
-- | GET /:id/edit
edit :: Routeable r => r -> RESTController
-- | GET /new
new :: Routeable r => r -> RESTController
instance Routeable (RESTControllerM a)
instance Routeable RESTControllerState
-- | This module exports a definition of a Controller, which is
-- simply a DC action with the Labeled HTTP Request
-- in the environment (i.e., it is a Reader monad).
module Hails.Web.Controller
-- | A controller is simply a reader monad atop DC with the
-- Labeled Request as the environment.
type Controller = ReaderT ControllerState DC
data ControllerState
ControllerState :: DCLabeled Request -> ControllerState
csRequest :: ControllerState -> DCLabeled Request
-- | Get the underlying request.
request :: Controller (DCLabeled Request)
-- | Get a request header
requestHeader :: HeaderName -> Controller (Maybe ByteString)
-- | Extract the body in the request (after unlabeling it).
body :: Controller ByteString
-- | Get the query parameter mathing the supplied variable name.
queryParam :: ByteString -> Controller (Maybe ByteString)
-- | Produce a response.
respond :: Routeable r => r -> Controller r
-- | Redirect back acording to the referer header. If the header is not
-- present redirect to root (i.e., /).
redirectBack :: Controller Response
-- | Redirect back acording to the referer header. If the header is not
-- present return the given response.
redirectBackOr :: Response -> Controller Response
instance Routeable (Controller Response)
instance MonadLIO DCLabel Controller
-- | This module exports a type corresponding to user's in Hails and some
-- helper functions.
module Hails.Web.User
-- | User name.
type UserName = Text
-- | Get the current user.
getHailsUser :: Controller (Maybe UserName)
-- | Execute action with the current user's name. Otherwise, request that
-- the user authenticate.
withUserOrDoAuth :: (UserName -> Controller Response) -> Controller Response
-- | This module re-exports the routing and controller modules. See each
-- module for their corresponding documentation.
--
-- Though you can implement a controller using the methods supplied by
-- this module (actually, Hails.Web.Router), we recommend using
-- the DSLs provided by Hails.Web.Frank or Hails.Web.REST.
module Hails.Web
-- | This module exports the type for a Hails BSON document,
-- HsonDoc. A Hails document is akin to Data.Bson's
-- documents, but differs in two ways. First, Hails restricts the number
-- of types to a subset of BSON's (see BsonVal). This
-- restriction is primarily due to the fact that many of the BSON types
-- are redundant and not used (at least within Hails). Second, Hails
-- allows for documents to contain policy-labeled values.
--
-- Policy labeled values (PolicyLabeled) are permitted only at the
-- "top-level" of a document. (This is primarily done to keep
-- policy-specification simple and may change in the future.)
-- Consequently to allow for nested documents and documents containing an
-- array of values we separate top-level fields (HsonField), that
-- may contain policy labeled values, from potentially-nested fields
-- (BsonField). A top-level field HsonField is thus either
-- a BsonField or a PolicyLabled value.
--
-- To keep the TCB compact, this module does not export the combinators
-- used to create documents in a friendly fashion. See
-- Hails.Data.Hson for the safe external API.
--
-- Credit: Much of this code is based on/reuses Data.Bson.
module Hails.Data.Hson.TCB
-- | A top-level document containing HsonFields.
type HsonDocument = [HsonField]
-- | A (possibly top-)level document containing BsonFields.
type BsonDocument = [BsonField]
-- | The name of a field.
type FieldName = Text
-- | A field containing a named HsonValue
data HsonField
HsonField :: !FieldName -> HsonValue -> HsonField
-- | A field containing a named BsonValue
data BsonField
BsonField :: !FieldName -> BsonValue -> BsonField
-- | An HsonValue is a top-level value that may either be a
-- BsonValue or a policy labeled value. The separation of values
-- into BsonValue and HsonValue is solely due to the
-- restriction that policy-labeled values may only occur at the top level
-- and BsonValues may be nested (e.g. using BsonArray and
-- BsonDoc).
data HsonValue
-- | Bson value
HsonValue :: BsonValue -> HsonValue
-- | Policy labeled value
HsonLabeled :: PolicyLabeled -> HsonValue
-- | A BsonValue is a subset of BSON (Data.Bson) values.
-- Note that a BsonValue cannot contain any labeled values; all
-- labeled values occur in a document as HsonValues.
-- Correspondingly, BsonValues may be arbitrarily nested.
data BsonValue
-- | Float value
BsonFloat :: Double -> BsonValue
-- | String value
BsonString :: Text -> BsonValue
-- | Inner document
BsonDoc :: BsonDocument -> BsonValue
-- | List of values
BsonArray :: [BsonValue] -> BsonValue
-- | Binary blob value
BsonBlob :: Binary -> BsonValue
-- | Object Id value
BsonObjId :: ObjectId -> BsonValue
-- | Boolean value
BsonBool :: Bool -> BsonValue
-- | Time stamp value
BsonUTC :: UTCTime -> BsonValue
-- | The NULL value
BsonNull :: BsonValue
-- | 32-bit integer
BsonInt32 :: Int32 -> BsonValue
-- | 64-bit integer
BsonInt64 :: Int64 -> BsonValue
-- | A PolicyLabeled value can be either an unlabeled value for
-- which the policy needs to be applied (NeedPolicyTCB), or an
-- already labeled value (HasPolicyTCB). PolicyLabeled
-- is a partially-opaque type; code should not be able to inspect the
-- value of an unlabeleda value, but may inspect an already labeled
-- value.
data PolicyLabeled
-- | Policy was not applied
NeedPolicyTCB :: BsonValue -> PolicyLabeled
-- | Policy applied
HasPolicyTCB :: (DCLabeled BsonValue) -> PolicyLabeled
-- | A BSON ObjectID is a 12-byte value consisting of a 4-byte timestamp
-- (seconds since epoch), a 3-byte machine id, a 2-byte process id, and a
-- 3-byte counter. Note that the timestamp and counter fields must be
-- stored big endian unlike the rest of BSON. This is because they are
-- compared byte-by-byte and we want to ensure a mostly increasing order.
data ObjectId :: *
Oid :: Word32 -> Word64 -> ObjectId
-- | Arbitrary binary blob
newtype Binary
Binary :: S8 -> Binary
unBinary :: Binary -> S8
-- | Strict ByeString
type S8 = ByteString
-- | Convert a top-level document (i.e., HsonDocument) to a
-- Data.Bson Document. This is the primary marshall-out
-- function. All PolicyLabeled values are marshalled out as
-- Data.Bson UserDefined values. This means that the
-- UserDefined type is reserved and exposing it as a type in
-- BsonValue would potentially lead to vulnerabilities in which
-- labeled values can be marshalled in from well-crafted ByteStrings.
-- Moreover, untrusted code should not have access to this function;
-- having such access would allow it to inspect the serialized labeled
-- values and thus violate IFC.
hsonDocToDataBsonDocTCB :: HsonDocument -> Document
-- | Convert Data.Bson Document to a HsonDocument.
-- This is the top-level function that marshalls BSON documents to Hails
-- documents. This function assumes that all documents have been
-- marshalled out using hsonDocToDataBsonDocTCB. Otherwise, the
-- PolicyLabled values that are created from the document may be
-- forged.
dataBsonDocToHsonDocTCB :: Document -> HsonDocument
-- | Convert a BsonDocument to a Data.Bson Document.
bsonDocToDataBsonDocTCB :: BsonDocument -> Document
-- | Convert a Data.Bson Value to a HsonValue. See
-- dataBsonDocToHsonDocTCB.
dataBsonValueToHsonValueTCB :: Value -> HsonValue
-- | Hails internal prefix that is used to serialized labeled values.
add__hails_prefix :: FieldName -> FieldName
instance Typeable Binary
instance Typeable BsonValue
instance Typeable BsonField
instance Typeable PolicyLabeled
instance Typeable HsonValue
instance Typeable HsonField
instance Show Binary
instance Read Binary
instance Eq Binary
instance Ord Binary
instance Eq BsonValue
instance Ord BsonValue
instance Eq BsonField
instance Ord BsonField
instance Eq HsonValue
instance Ord HsonValue
instance Eq HsonField
instance Ord HsonField
instance Show PolicyLabeled
instance Ord PolicyLabeled
instance Eq PolicyLabeled
-- | This module exports the type for a Hails BSON document,
-- HsonDoc and related classes for creating such documents. A
-- Hails document is similar to Data.Bson's documents, but differs
-- in two ways. First, Hails restricts the number of types to a subset of
-- BSON's (see BsonVal). This restriction is primarily due to the
-- fact that many of the BSON types are redundant and not used (at least
-- within Hails). Second, Hails allows for documents to contain
-- policy-labeled values.
--
-- Policy labeled values (PolicyLabeled) are permitted only at the
-- "top-level" of a document. (This is primarily done to keep
-- policy-specification simple and may change in the future.)
-- Consequently to allow for nested documents and documents containing an
-- array of values we separate top-level fields (HsonField), that
-- may contain policy labeled values, from potentially-nested fields
-- (BsonField). A top-level field HsonField is thus either
-- a BsonField or a PolicyLabled value.
--
-- Example:
--
--
-- module Main (x, y) where
--
-- import Data.Text (Text)
--
-- import LIO.DCLabel
-- import LIO.Labeled.TCB (labelTCB)
-- import Hails.Data.Hson
--
-- -- | Create document, verbose approach
-- x :: HsonDocument
-- x = [ "myInt" =: BsonInt32 42
-- , "nested" =: BsonDoc [ "flag" =: BsonBool True]
-- , "secret" =: (HsonLabeled $ hasPolicy (labelTCB dcPub (BsonString "hi")))
-- ]
--
-- -- | Create same document, clean approach
-- y :: HsonDocument
-- y = [ "myInt" -: (42 :: Int)
-- , "nested" -: ([ "flag" -: True] :: BsonDocument)
-- , "secret" -: labelTCB dcPub (toBsonValue ("hi" :: Text))
-- ]
--
--
-- Both x and y with -XOverloadedStrings:
--
-- -- [myInt -: 42,nested -: [flag -: True],secret -: HsonLabeled] --module Hails.Data.Hson -- | A top-level document containing HsonFields. type HsonDocument = [HsonField] -- | Synonym for HsonDocument type Document = HsonDocument -- | A (possibly top-)level document containing BsonFields. type BsonDocument = [BsonField] -- | Class used to implement operatoins on documents that return -- HsonValues or BsonValues. The main role of this function -- is to impose the functional dependency between values and fields. As a -- consequence looking up and getting valueAt in a -- HsonDocument (resp. BsonDocument) will return a -- HsonValue (resp. BsonValue). This eliminates the need to -- specify the end type of very query, but forces the programmer to cast -- between Hson and Bson values. class Field v f => DocOps v f | v -> f, f -> v where look n doc = case find ((== n) . fieldName) doc of { Just v -> fieldValue v _ -> fail $ "look: Not found " ++ show n } valueAt n = runIdentity . look n look :: (DocOps v f, Field v f, Monad m) => FieldName -> [f] -> m v valueAt :: (DocOps v f, Field v f) => FieldName -> [f] -> v serialize :: DocOps v f => [f] -> ByteString -- | Class used to implement operations on documents that return Haskell -- values (as opposed to HsonValue or BsonValue). class DocValOps d v where at n = runIdentity . lookup n lookup :: (DocValOps d v, Monad m) => FieldName -> d -> m v at :: DocValOps d v => FieldName -> d -> v -- | Only include fields specified. include :: IsField f => [FieldName] -> [f] -> [f] -- | Exclude fields specified. exclude :: IsField f => [FieldName] -> [f] -> [f] -- | Merge documents with preference given to first one when both have the -- same field name. merge :: IsField f => [f] -> [f] -> [f] -- | Convert BsonDocument to HsonDocument bsonDocToHsonDoc :: BsonDocument -> HsonDocument -- | Convert BsonField to HsonField bsonFieldToHsonField :: BsonField -> HsonField -- | Returns true if the document is composed solely of BsonValues. -- This function is useful when converting from HsonDocument to -- BsonDocument. isBsonDoc :: HsonDocument -> Bool -- | This is a relaxed version of hsonDocToBsonDocStrict that only -- converts fields containing BsonValues. In other words, the -- PolicyLabeled values are dropped. hsonDocToBsonDoc :: HsonDocument -> BsonDocument -- | Convert an HsonDocument to a BsonDocument. If any of the -- fields contain PolicyLabeled values (i.e., are -- HsonLabeled values) this function fails, otherwise it -- returns the converted document. To check for failure use -- isBsonDoc. hsonDocToBsonDocStrict :: Monad m => HsonDocument -> m BsonDocument -- | Convert a labeled request to a labeled document. Values of fields that -- have a name that ends with [] are converted to arrays and the -- suffix [] is stripped from the name. labeledRequestToHson :: MonadDC m => DCLabeled Request -> m (DCLabeled HsonDocument) -- | The name of a field. type FieldName = Text -- | A field containing a named HsonValue data HsonField HsonField :: !FieldName -> HsonValue -> HsonField -- | A field containing a named BsonValue data BsonField BsonField :: !FieldName -> BsonValue -> BsonField -- | Class for retrieving the name of a field. class IsField f fieldName :: IsField f => f -> FieldName -- | Class used to define fields. class (IsField f, Show v, Show f) => Field v f (=:) :: Field v f => FieldName -> v -> f fieldValue :: (Field v f, Monad m) => f -> m v -- | Class used to define fields. class (Show v, Show f) => GenField v f (-:) :: GenField v f => FieldName -> v -> f -- | An HsonValue is a top-level value that may either be a -- BsonValue or a policy labeled value. The separation of values -- into BsonValue and HsonValue is solely due to the -- restriction that policy-labeled values may only occur at the top level -- and BsonValues may be nested (e.g. using BsonArray and -- BsonDoc). data HsonValue -- | Bson value HsonValue :: BsonValue -> HsonValue -- | Policy labeled value HsonLabeled :: PolicyLabeled -> HsonValue -- | Class used to (de)construct HsonValues class (Typeable a, Show a) => HsonVal a toHsonValue :: HsonVal a => a -> HsonValue fromHsonValue :: (HsonVal a, Monad m) => HsonValue -> m a -- | A BsonValue is a subset of BSON (Data.Bson) values. -- Note that a BsonValue cannot contain any labeled values; all -- labeled values occur in a document as HsonValues. -- Correspondingly, BsonValues may be arbitrarily nested. data BsonValue -- | Float value BsonFloat :: Double -> BsonValue -- | String value BsonString :: Text -> BsonValue -- | Inner document BsonDoc :: BsonDocument -> BsonValue -- | List of values BsonArray :: [BsonValue] -> BsonValue -- | Binary blob value BsonBlob :: Binary -> BsonValue -- | Object Id value BsonObjId :: ObjectId -> BsonValue -- | Boolean value BsonBool :: Bool -> BsonValue -- | Time stamp value BsonUTC :: UTCTime -> BsonValue -- | The NULL value BsonNull :: BsonValue -- | 32-bit integer BsonInt32 :: Int32 -> BsonValue -- | 64-bit integer BsonInt64 :: Int64 -> BsonValue -- | Class used to (de)construct BsonValues class (Typeable a, Show a) => BsonVal a toBsonValue :: BsonVal a => a -> BsonValue fromBsonValue :: (BsonVal a, Monad m) => BsonValue -> m a -- | A PolicyLabeled value can be either an unlabeled value for -- which the policy needs to be applied (NeedPolicyTCB), or an -- already labeled value (HasPolicyTCB). PolicyLabeled -- is a partially-opaque type; code should not be able to inspect the -- value of an unlabeleda value, but may inspect an already labeled -- value. data PolicyLabeled -- | Create a policy labeled value given an unlabeled HsonValue. needPolicy :: BsonValue -> PolicyLabeled -- | Create a policy labeled value a labeled HsonValue. hasPolicy :: DCLabeled BsonValue -> PolicyLabeled -- | Get the policy labeled value, only if the policy has been applied. getPolicyLabeled :: Monad m => PolicyLabeled -> m (DCLabeled BsonValue) -- | Arbitrary binary blob newtype Binary Binary :: S8 -> Binary unBinary :: Binary -> S8 -- | A BSON ObjectID is a 12-byte value consisting of a 4-byte timestamp -- (seconds since epoch), a 3-byte machine id, a 2-byte process id, and a -- 3-byte counter. Note that the timestamp and counter fields must be -- stored big endian unlike the rest of BSON. This is because they are -- compared byte-by-byte and we want to ensure a mostly increasing order. data ObjectId :: * Oid :: Word32 -> Word64 -> ObjectId -- | Create a fresh ObjectId. genObjectId :: MonadDC m => m ObjectId instance [overlap ok] HsonVal (DCLabeled BsonValue) instance [overlap ok] Show (DCLabeled BsonValue) instance [overlap ok] HsonVal PolicyLabeled instance [overlap ok] HsonVal BsonValue instance [overlap ok] HsonVal Integer instance [overlap ok] HsonVal Int instance [overlap ok] HsonVal Int64 instance [overlap ok] HsonVal Int32 instance [overlap ok] (HsonVal a, BsonVal a) => HsonVal (Maybe a) instance [overlap ok] HsonVal (Maybe BsonValue) instance [overlap ok] HsonVal UTCTime instance [overlap ok] HsonVal Bool instance [overlap ok] HsonVal ObjectId instance [overlap ok] HsonVal Binary instance [overlap ok] (HsonVal a, BsonVal a) => HsonVal [a] instance [overlap ok] HsonVal [BsonValue] instance [overlap ok] HsonVal BsonDocument instance [overlap ok] HsonVal String instance [overlap ok] HsonVal Text instance [overlap ok] HsonVal Float instance [overlap ok] HsonVal Double instance [overlap ok] HsonVal HsonValue instance [overlap ok] BsonVal Integer instance [overlap ok] BsonVal Int instance [overlap ok] BsonVal Int64 instance [overlap ok] BsonVal Int32 instance [overlap ok] BsonVal a => BsonVal (Maybe a) instance [overlap ok] BsonVal (Maybe BsonValue) instance [overlap ok] BsonVal UTCTime instance [overlap ok] BsonVal Bool instance [overlap ok] BsonVal ObjectId instance [overlap ok] BsonVal Binary instance [overlap ok] BsonVal a => BsonVal [a] instance [overlap ok] BsonVal [BsonValue] instance [overlap ok] BsonVal BsonDocument instance [overlap ok] BsonVal ByteString instance [overlap ok] BsonVal ByteString instance [overlap ok] BsonVal String instance [overlap ok] BsonVal Text instance [overlap ok] BsonVal Float instance [overlap ok] BsonVal Double instance [overlap ok] HsonVal v => GenField v HsonField instance [overlap ok] BsonVal v => GenField v BsonField instance [overlap ok] BsonVal v => DocValOps BsonDocument v instance [overlap ok] HsonVal v => DocValOps HsonDocument v instance [overlap ok] DocOps BsonValue BsonField instance [overlap ok] DocOps HsonValue HsonField instance [overlap ok] ShowTCB PolicyLabeled instance [overlap ok] ShowTCB HsonValue instance [overlap ok] Show HsonValue instance [overlap ok] Show BsonValue instance [overlap ok] Field HsonValue HsonField instance [overlap ok] Field BsonValue HsonField instance [overlap ok] Field BsonValue BsonField instance [overlap ok] IsField HsonField instance [overlap ok] IsField BsonField instance [overlap ok] Show HsonField instance [overlap ok] Show BsonField -- | This module exports the basic database types and constructors. See -- Hails.Database for a description of the Hails database system. module Hails.Database.TCB -- | The name of a collection. type CollectionName = Text -- | A labeled Collection set. type CollectionSet = DCLabeled (Set Collection) -- | A Collection is a MongoDB collection name with an associated -- label, clearance and labeling policy. Access to the collection is -- restricted according to the collection label. Data inserted-to and -- retrieved-from the collection will be labeled according to the -- collection policy, with the guarantee that no data more sensitive than -- the collection clearance can be inserted into the collection. data Collection CollectionTCB :: CollectionName -> DCLabel -> DCLabel -> CollectionPolicy -> Collection -- | Collection name colName :: Collection -> CollectionName -- | Collection label colLabel :: Collection -> DCLabel -- | Collection clearance colClearance :: Collection -> DCLabel -- | Collection labeling policies colPolicy :: Collection -> CollectionPolicy -- | Create a Collection, ignoring any IFC restrictions. collectionTCB :: CollectionName -> DCLabel -> DCLabel -> CollectionPolicy -> Collection -- | The name of a database. type DatabaseName = Text -- | A Database is a MongoDB database with an associated label and -- set of collections. The label is used to restrict access to the -- database. Since collection policies are specified by policy modules, -- every collection must always be associated with some database -- (and thereby, policy module); a policy module is not allowed to -- create a collection (and specify policies on it) in an arbitrary -- database. We allow for the existance of a collection to be secrect, -- and thus protect the set of collections with a label. data Database DatabaseTCB :: DatabaseName -> DCLabel -> CollectionSet -> Database -- | Database name databaseName :: Database -> DatabaseName -- | Label of database databaseLabel :: Database -> DCLabel -- | Collections associated with databsae databaseCollections :: Database -> CollectionSet -- | A collection policy contains the policy for labeling documents -- (documentLabelPolicy) at a coarse grained level, and a set of -- policies for labeling fields of a document -- (fieldLabelPolicies). -- -- Specific fields can be associated with a FieldPolicy, which -- allows the policy module to either: -- --
-- module My.Policy ( MyPolicyModule ) where -- -- import LIO -- import LIO.DCLabel -- import Data.Typeable -- import Hails.PolicyModule -- -- -- | Handle to policy module, not exporting @MyPolicyModuleTCB@ -- data MyPolicyModule = MyPolicyModuleTCB DCPriv deriving Typeable -- -- instance PolicyModule MyPolicyModule where -- initPolicyModule priv = do -- -- Get the policy module principal: -- let this = privDesc priv -- -- Create label: -- l = dcLabel dcTrue -- Everybody can read -- this -- Only policy module can modify -- -- Label database and collection-set: -- labelDatabaseP priv l l -- -- Create collections: -- createCollectionP priv "collection1" ... -- createCollectionP priv "collection2" ... -- .... -- createCollectionP priv "collectionN" ... -- -- Return the policy module: -- return (MyPolicyModuleTCB priv) ---- -- Here the policy module labels the database, labels the list of -- collections and finally creates N collections. The -- computation returns a value of type MyPolicyModule which -- wraps the policy module's privileges. As a consequence, trustworthy -- code that has access to the value constructor can use the policy -- module's privileges: -- --
-- -- Trustworthy code within the same module (My.Policy) -- -- alwaysInsert doc = withPolicyModule $ \(MyPolicyModuleTCB priv) -> -- insertP priv "collection1" doc ---- -- Here alwaysInsert uses the policy module's privileges to -- insert a document into collection "collection1". As such, if -- doc is well-formed the function always succeeds. (Of course, -- such functions should not be exported.) -- -- Untrusted code in a different module cannot, however use the policy -- module's privilege: -- --
-- -- Untrusted code in a separate module -- import My.Policy -- -- maybeInsertIntoDB appPriv doc = withPolicyModule $ (_ :: MyPolicyModule) -> -- insertP appPriv "collection1" doc ---- -- Depending on the privileges passed to maybeInsertIntoDB, and -- set policies, the insertion may or may not succeed. class Typeable pm => PolicyModule pm initPolicyModule :: PolicyModule pm => DCPriv -> PMAction pm -- | A policy module action (PMAction) is simply a wrapper for -- database action (DBAction). The wrapper is used to restrict -- app code from specifying policies; only policy module may -- execute PMActions, and thus create collections, set a label -- on their databases, etc. data PMAction a -- | This is the first action that any policy module should execute. It is -- simply a wrapper for setDatabaseLabelP and -- setCollectionSetLabelP. Given the policy module's privilges, -- label for the database, and label for the collection-set -- labelDatabaseP accordingly sets the labels. labelDatabaseP :: DCPriv -> DCLabel -> DCLabel -> PMAction () -- | Set the label of the underlying database. The supplied label must be -- bounded by the current label and clearance as enforced by -- guardAlloc. Moreover the current computation mut write to the -- database, as enforce by applying guardWrite to the current -- database label. The latter requirement suggests that every policy -- module use setDatabaseLabelP when first changing the label. setDatabaseLabel :: DCLabel -> PMAction () -- | Same as setDatabaseLabel, but uses privileges when performing -- label comparisons. If a policy module wishes to allow other policy -- modules or apps to access the underlying databse it must use -- setDatabaseLabelP to "downgrade" the database label, which by -- default only allows the policy module itself to access any of the -- contents (including collection-set). setDatabaseLabelP :: DCPriv -> DCLabel -> PMAction () -- | The collections label protects the collection-set of the database. It -- is used to restrict who can name a collection in the database and who -- can modify the underlying collection-set (e.g., by creating a new -- collection). The policy module may change the default collections -- label, which limits access to the policy module alone, using -- setCollectionSetLabel. -- -- The new label must be bounded by the current label and clearance as -- checked by guardAlloc. Additionally, the current label must -- flow to the label of the database which protects the label of the -- colleciton set. In most cases code should use -- setCollectionSetLabelP. setCollectionSetLabel :: DCLabel -> PMAction () -- | Same as setCollectionSetLabel, but uses the supplied privileges -- when performing label comparisons. setCollectionSetLabelP :: DCPriv -> DCLabel -> PMAction () -- | Create a Collection given a name, label, clearance, and policy. -- Several IFC rules must be respected for this function to succeed: -- --
-- withPolicyModule $ \(_ :: SomePolicyModule) -> do -- -- Perform database operations: insert, save, find, delete, etc. ---- -- Trustworthy code (as deemed by the policy module) may, however, be -- passed in additional privileges by encapsulating them in pm -- (see PolicyModule). withPolicyModule :: PolicyModule pm => (pm -> DBAction a) -> DC a -- | Policy type name. Has the form: -- --
-- <Policy module package>:<Fully qualified module>.<Policy module type> --type TypeName = String -- | Get the name of a policy module. policyModuleTypeName :: PolicyModule pm => pm -> TypeName -- | This contains a map of all the policy modules. Specifically, it maps -- the policy moule types to a pair of the policy module principal and -- database name. -- -- For the trusted programmer: The map itself is read from the -- file pointed to by the environment variable -- DATABASE_CONFIG_FILE. Each line in the file corresponds to a -- policy module. The format of a line is as follows -- --
-- ("<Policy module package>:<Fully qualified module>.<Policy module type>", "<Policy module database name>")
--
--
-- Example of valid line is:
--
--
-- ("my-policy-0.1.2.3:My.Policy.MyPolicyModule", "my_db")
--
--
-- The principal used by Hails is the first projection with a
-- "_" suffix. In the above, the principal assigned by Hails is:
--
-- -- "_my-policy-0.1.2.3:My.Policy.MyPolicyModule" --availablePolicyModules :: Map TypeName (Principal, DatabaseName) -- | This module exports the trusted types and functions used by -- Hails.Database.Query when performing database queries. module Hails.Database.Query.TCB -- | A labeled cursor. The cursor is labeled with the join of the database -- and collection it reads from. The collection policies are "carried" -- along since they are applied on-demand. data Cursor CursorTCB :: DCLabel -> Cursor -> [FieldName] -> Collection -> Cursor -- | Cursor label curLabel :: Cursor -> DCLabel -- | Internal MongoDB cursor curInternal :: Cursor -> Cursor -- | Projector from query. Used to remove fields after performing query. curProject :: Cursor -> [FieldName] -- | Collection cursor is reading from curCollection :: Cursor -> Collection -- | This module exports the basic types used to create queries and -- selections. Different from standard MongoDB, Hails queries are limited -- to SearchableFields (similarly, ordering a query result is -- limited to such fields) and projections are carried out by this -- library and not the database. The later is a result of allowing policy -- modules to express a labeling policy as a function of a document -- -- hence we cannot determine at compile time if a field is used in a -- policy and thus must be included in the projection. module Hails.Database.Query -- | Class used to generalize insertion and saving of documents. -- Specifically, it permits reusing function names when inserting/saving -- both already-labeled and unlabeled documents. Minimal definition: -- insertP and saveP. class InsertLike doc where insert = insertP noPriv insert_ c d = void $ insert c d insertP_ p c d = void $ insertP p c d save = saveP noPriv insert :: InsertLike doc => CollectionName -> doc -> DBAction ObjectId insert_ :: InsertLike doc => CollectionName -> doc -> DBAction () insertP :: InsertLike doc => DCPriv -> CollectionName -> doc -> DBAction ObjectId insertP_ :: InsertLike doc => DCPriv -> CollectionName -> doc -> DBAction () save :: InsertLike doc => CollectionName -> doc -> DBAction () saveP :: InsertLike doc => DCPriv -> CollectionName -> doc -> DBAction () -- | Class used to simplicy the creation of a 'Selection'/'Query'. -- Specifically, select can be used to create a Section -- in a straight foward manner, but similarly can be used to create a -- Query with a set of default options. class Select selectionOrQuery select :: Select selectionOrQuery => Selector -> CollectionName -> selectionOrQuery -- | A Section is a Selector query on a Collection. -- In other words, a Selection is the necessary information for -- performing a database query. data Selection Selection :: Selector -> CollectionName -> Selection -- | Selection query. selectionSelector :: Selection -> Selector -- | Collection to perform query on. selectionCollection :: Selection -> CollectionName -- | Filter for a query, analogous to the WHERE clause in SQL. -- [] matches all documents in collection. For example, [x -- -: a, y -: b] is analogous to WHERE x = a AND y -- = b in SQL. -- -- Note: only FieldNames of SearchableFields may be -- used in selections, and thus all other fields are ignored. type Selector = BsonDocument -- | Use select to create a basic query with defaults, then modify if -- desired. Example: (select sel col) {limit =: 10}. For -- simplicity, and since policies may be specified in terms of arbitrary -- fields, The selection and sort fields are restricted to -- SearchableFields, or the _id field that is -- implicitly a SearchableField. data Query Query :: [QueryOption] -> Selection -> [FieldName] -> Word32 -> Limit -> [Order] -> BatchSize -> [FieldName] -> Query -- | Query options, default []. options :: Query -> [QueryOption] -- | WHERE clause,default []. Non-SearchableFields -- ignored. selection :: Query -> Selection -- | The fields to project. Default [] corresponds to all. project :: Query -> [FieldName] -- | Number of documents to skip, default 0. skip :: Query -> Word32 -- | Max number of documents to return. Default, 0, means no limit. limit :: Query -> Limit -- | Sort result by given order, default []. -- Non-SearchableFields ignored. sort :: Query -> [Order] -- | The number of document to return in each batch response from the -- server. 0 means MongoDB default. batchSize :: Query -> BatchSize -- | Force mongoDB to use this index, default [], no hint. -- Non-SearchableFields ignored. hint :: Query -> [FieldName] data QueryOption :: * -- | Tailable means cursor is not closed when the last data is retrieved. -- Rather, the cursor marks the final object's position. You can resume -- using the cursor later, from where it was located, if more data were -- received. Like any latent cursor, the cursor may become invalid -- at some point – for example if the final object it references were -- deleted. Thus, you should be prepared to requery on CursorNotFound -- exception. TailableCursor :: QueryOption -- | The server normally times out idle cursors after 10 minutes to prevent -- a memory leak in case a client forgets to close a cursor. Set this -- option to allow a cursor to live forever until it is closed. NoCursorTimeout :: QueryOption -- | Use with TailableCursor. If we are at the end of the data, block for a -- while rather than returning no data. After a timeout period, we do -- return as normal. | Exhaust -- ^ Stream the data down full blast in -- multiple more packages, on the assumption that the client will -- fully read all data queried. Faster when you are pulling a lot of data -- and know you want to pull it all down. Note: the client is not allowed -- to not read all the data unless it closes the connection. Exhaust -- commented out because not compatible with current Pipeline -- implementation AwaitData :: QueryOption -- | Get partial results from a _mongos_ if some shards are down, instead -- of throwing an error. Partial :: QueryOption -- | Maximum number of documents to return, i.e. cursor will close after -- iterating over this number of documents. 0 means no limit. type Limit = Word32 -- | The number of document to return in each batch response from the -- server. 0 means use Mongo default. type BatchSize = Word32 -- | Sorting fields in Ascending or Descending order. data Order -- | Ascending order Asc :: FieldName -> Order -- | Descending order Desc :: FieldName -> Order -- | Get the field name in the order. orderName :: Order -> FieldName -- | A labeled cursor. The cursor is labeled with the join of the database -- and collection it reads from. The collection policies are "carried" -- along since they are applied on-demand. data Cursor -- | Cursor label curLabel :: Cursor -> DCLabel -- | Fetch documents satisfying query. A labeled Cursor is returned, -- which can be used to retrieve the actual HsonDocuments. For -- this function to succeed the current computation must be able to read -- from the database and collection (implicilty the database's -- collection-set). This is satisfied by applying taint to the -- join join of the collection, database, and ccollection-set label. The -- curor label is labeled by the upperBound of the database and -- collection labels and must be used within the same -- withPolicyModule block. -- -- Note that this function is quite permissive in the queries it accepts. -- Specifically, any non-SearchableFields used in sort, -- order, or hint are ignored (as opposed to -- throwing an exception). find :: Query -> DBAction Cursor -- | Same as find, but uses privileges when reading from the -- collection and database. findP :: DCPriv -> Query -> DBAction Cursor -- | Return next HsonDocument in the query result, or Nothing -- if finished. Note that the current computation must be able to read -- from the labeled Cursor. To enforce this, next uses -- taint to raise the current label to join of the current label -- and 'Cursor'\'s label. The returned document is labeled according to -- the underlying Collection policy. next :: Cursor -> DBAction (Maybe LabeledHsonDocument) -- | Same as next, but usess privileges when raising the current -- label. nextP :: DCPriv -> Cursor -> DBAction (Maybe LabeledHsonDocument) -- | Fetch the first document satisfying query, or Nothing if not -- documents matched the query. findOne :: Query -> DBAction (Maybe LabeledHsonDocument) -- | Same as findOne, but uses privileges when performing label -- comparisons. findOneP :: DCPriv -> Query -> DBAction (Maybe LabeledHsonDocument) -- | Same as delete, but uses privileges. deleteP :: DCPriv -> Selection -> DBAction () -- | Delete documents according to the selection. It must be that the -- current computation can overwrite the existing documents. That is, the -- current label must flow to the label of each document that matches the -- selection. delete :: Selection -> DBAction () -- | Exceptions thrown by invalid database queries. data DBError -- | Collection does not exist UnknownCollection :: DBError -- | Policy module not found UnknownPolicyModule :: DBError -- | Execution of action failed ExecFailure :: Failure -> DBError -- | Apply a collection policy the given document, using privileges when -- labeling the document and performing label comparisons. The labeling -- proceeds as follows: -- --
-- withPolicyModule $ \(_ :: SomePolicyModule) -> do -- -- Perform database operations: insert, save, find, delete, etc. ---- -- Trustworthy code (as deemed by the policy module) may, however, be -- passed in additional privileges by encapsulating them in pm -- (see PolicyModule). withPolicyModule :: PolicyModule pm => (pm -> DBAction a) -> DC a -- | Get the underlying database. Must be able to read from the database as -- enforced by applying taint to the database label. This is -- required because the database label protects the label on collections -- which can be projected given a Database value. getDatabase :: DBAction Database -- | Same as getDatabase, but uses privileges when raising the -- current label. getDatabaseP :: DCPriv -> DBAction Database -- | Exceptions thrown by invalid database queries. data DBError -- | Collection does not exist UnknownCollection :: DBError -- | Policy module not found UnknownPolicyModule :: DBError -- | Execution of action failed ExecFailure :: Failure -> DBError -- | The name of a database. type DatabaseName = Text -- | A Database is a MongoDB database with an associated label and -- set of collections. The label is used to restrict access to the -- database. Since collection policies are specified by policy modules, -- every collection must always be associated with some database -- (and thereby, policy module); a policy module is not allowed to -- create a collection (and specify policies on it) in an arbitrary -- database. We allow for the existance of a collection to be secrect, -- and thus protect the set of collections with a label. data Database -- | Database name databaseName :: Database -> DatabaseName -- | Label of database databaseLabel :: Database -> DCLabel -- | Collections associated with databsae databaseCollections :: Database -> CollectionSet -- | The name of a collection. type CollectionName = Text -- | A labeled Collection set. type CollectionSet = DCLabeled (Set Collection) -- | A Collection is a MongoDB collection name with an associated -- label, clearance and labeling policy. Access to the collection is -- restricted according to the collection label. Data inserted-to and -- retrieved-from the collection will be labeled according to the -- collection policy, with the guarantee that no data more sensitive than -- the collection clearance can be inserted into the collection. data Collection -- | Collection name colName :: Collection -> CollectionName -- | Collection label colLabel :: Collection -> DCLabel -- | Collection clearance colClearance :: Collection -> DCLabel -- | Collection labeling policies colPolicy :: Collection -> CollectionPolicy -- | A document policy error. data PolicyError -- | Document is not "well-typed" TypeError :: String -> PolicyError -- | Policy has been violated PolicyViolation :: PolicyError -- | A labeled HsonDocument. type LabeledHsonDocument = DCLabeled HsonDocument -- | Class used to generalize insertion and saving of documents. -- Specifically, it permits reusing function names when inserting/saving -- both already-labeled and unlabeled documents. Minimal definition: -- insertP and saveP. class InsertLike doc where insert = insertP noPriv insert_ c d = void $ insert c d insertP_ p c d = void $ insertP p c d save = saveP noPriv insert :: InsertLike doc => CollectionName -> doc -> DBAction ObjectId insert_ :: InsertLike doc => CollectionName -> doc -> DBAction () insertP :: InsertLike doc => DCPriv -> CollectionName -> doc -> DBAction ObjectId insertP_ :: InsertLike doc => DCPriv -> CollectionName -> doc -> DBAction () save :: InsertLike doc => CollectionName -> doc -> DBAction () saveP :: InsertLike doc => DCPriv -> CollectionName -> doc -> DBAction () -- | Fetch documents satisfying query. A labeled Cursor is returned, -- which can be used to retrieve the actual HsonDocuments. For -- this function to succeed the current computation must be able to read -- from the database and collection (implicilty the database's -- collection-set). This is satisfied by applying taint to the -- join join of the collection, database, and ccollection-set label. The -- curor label is labeled by the upperBound of the database and -- collection labels and must be used within the same -- withPolicyModule block. -- -- Note that this function is quite permissive in the queries it accepts. -- Specifically, any non-SearchableFields used in sort, -- order, or hint are ignored (as opposed to -- throwing an exception). find :: Query -> DBAction Cursor -- | Same as find, but uses privileges when reading from the -- collection and database. findP :: DCPriv -> Query -> DBAction Cursor -- | Return next HsonDocument in the query result, or Nothing -- if finished. Note that the current computation must be able to read -- from the labeled Cursor. To enforce this, next uses -- taint to raise the current label to join of the current label -- and 'Cursor'\'s label. The returned document is labeled according to -- the underlying Collection policy. next :: Cursor -> DBAction (Maybe LabeledHsonDocument) -- | Same as next, but usess privileges when raising the current -- label. nextP :: DCPriv -> Cursor -> DBAction (Maybe LabeledHsonDocument) -- | Fetch the first document satisfying query, or Nothing if not -- documents matched the query. findOne :: Query -> DBAction (Maybe LabeledHsonDocument) -- | Same as findOne, but uses privileges when performing label -- comparisons. findOneP :: DCPriv -> Query -> DBAction (Maybe LabeledHsonDocument) -- | A labeled cursor. The cursor is labeled with the join of the database -- and collection it reads from. The collection policies are "carried" -- along since they are applied on-demand. data Cursor -- | Cursor label curLabel :: Cursor -> DCLabel -- | Class used to simplicy the creation of a 'Selection'/'Query'. -- Specifically, select can be used to create a Section -- in a straight foward manner, but similarly can be used to create a -- Query with a set of default options. class Select selectionOrQuery select :: Select selectionOrQuery => Selector -> CollectionName -> selectionOrQuery -- | A Section is a Selector query on a Collection. -- In other words, a Selection is the necessary information for -- performing a database query. data Selection Selection :: Selector -> CollectionName -> Selection -- | Selection query. selectionSelector :: Selection -> Selector -- | Collection to perform query on. selectionCollection :: Selection -> CollectionName -- | Filter for a query, analogous to the WHERE clause in SQL. -- [] matches all documents in collection. For example, [x -- -: a, y -: b] is analogous to WHERE x = a AND y -- = b in SQL. -- -- Note: only FieldNames of SearchableFields may be -- used in selections, and thus all other fields are ignored. type Selector = BsonDocument -- | Use select to create a basic query with defaults, then modify if -- desired. Example: (select sel col) {limit =: 10}. For -- simplicity, and since policies may be specified in terms of arbitrary -- fields, The selection and sort fields are restricted to -- SearchableFields, or the _id field that is -- implicitly a SearchableField. data Query Query :: [QueryOption] -> Selection -> [FieldName] -> Word32 -> Limit -> [Order] -> BatchSize -> [FieldName] -> Query -- | Query options, default []. options :: Query -> [QueryOption] -- | WHERE clause,default []. Non-SearchableFields -- ignored. selection :: Query -> Selection -- | The fields to project. Default [] corresponds to all. project :: Query -> [FieldName] -- | Number of documents to skip, default 0. skip :: Query -> Word32 -- | Max number of documents to return. Default, 0, means no limit. limit :: Query -> Limit -- | Sort result by given order, default []. -- Non-SearchableFields ignored. sort :: Query -> [Order] -- | The number of document to return in each batch response from the -- server. 0 means MongoDB default. batchSize :: Query -> BatchSize -- | Force mongoDB to use this index, default [], no hint. -- Non-SearchableFields ignored. hint :: Query -> [FieldName] data QueryOption :: * -- | Tailable means cursor is not closed when the last data is retrieved. -- Rather, the cursor marks the final object's position. You can resume -- using the cursor later, from where it was located, if more data were -- received. Like any latent cursor, the cursor may become invalid -- at some point – for example if the final object it references were -- deleted. Thus, you should be prepared to requery on CursorNotFound -- exception. TailableCursor :: QueryOption -- | The server normally times out idle cursors after 10 minutes to prevent -- a memory leak in case a client forgets to close a cursor. Set this -- option to allow a cursor to live forever until it is closed. NoCursorTimeout :: QueryOption -- | Use with TailableCursor. If we are at the end of the data, block for a -- while rather than returning no data. After a timeout period, we do -- return as normal. | Exhaust -- ^ Stream the data down full blast in -- multiple more packages, on the assumption that the client will -- fully read all data queried. Faster when you are pulling a lot of data -- and know you want to pull it all down. Note: the client is not allowed -- to not read all the data unless it closes the connection. Exhaust -- commented out because not compatible with current Pipeline -- implementation AwaitData :: QueryOption -- | Get partial results from a _mongos_ if some shards are down, instead -- of throwing an error. Partial :: QueryOption -- | Maximum number of documents to return, i.e. cursor will close after -- iterating over this number of documents. 0 means no limit. type Limit = Word32 -- | The number of document to return in each batch response from the -- server. 0 means use Mongo default. type BatchSize = Word32 -- | Sorting fields in Ascending or Descending order. data Order -- | Ascending order Asc :: FieldName -> Order -- | Descending order Desc :: FieldName -> Order -- | Delete documents according to the selection. It must be that the -- current computation can overwrite the existing documents. That is, the -- current label must flow to the label of each document that matches the -- selection. delete :: Selection -> DBAction () -- | Same as delete, but uses privileges. deleteP :: DCPriv -> Selection -> DBAction () -- | This module exports a domain specific language for specifying policy -- module policies. It is recommended that all policy modules use -- this code when specifying security policies as it simplifies auditing -- and building trust in the authors. Policy modules are described in -- Hails.PolicyModule, which is a pre-required reading to this -- module's documentation. -- -- Consider creating a policy module where anybody can read and write -- freely to the databse. In this databsae we wish to create a simple -- user model collecting user names and passwords. This collection -- "users" is also readable and writable by anybody. However, the -- passwords must always belong to the named user. Specifically, only the -- user (or policy module) may read and modify the password. This policy -- is implemented below: -- --
-- data UsersPolicyModule = UsersPolicyModuleTCB DCPriv -- deriving Typeable -- -- instance PolicyModule UsersPolicyModule where -- initPolicyModule priv = do -- setPolicy priv $ do -- database $ do -- readers ==> anybody -- writers ==> anybody -- admins ==> this -- collection "users" $ do -- access $ do -- readers ==> anybody -- writers ==> anybody -- clearance $ do -- secrecy ==> this -- integrity ==> anybody -- document $ doc -> do -- readers ==> anybody -- writers ==> anybody -- field "name" $ searchable -- field "password" $ labeled $ doc -> do -- let user = "name" `at` doc :: String -- readers ==> this \/ user -- writers ==> this \/ user -- return $ UsersPolicyModuleTCB priv -- where this = privDesc priv ---- -- Notice that the database is public, as described above, but only this -- policy module may modify the internal collection names (as indicated -- by the admin keyword). Similarly the collection is publicly -- accessible (as set with the access keyword), and may contain -- data at most as sensitve as the policy module can read (i.e., the -- clearance). -- -- Documents retrieved from the "users" collection are public (as -- indicated by the document data-dependent policy that sets the -- readers and writers). The field "name" is -- searchable (i.e., it is a SearchableField) and thus can -- be used in query predicates. Conversely, the "password" field -- is labeled using a data-dependent policy. Specifically the -- field is labed using the "name" value contained in the document (i.e., -- the user's name): hence only the user having the right privilege or -- the policy module (this) may read and create such data. module Hails.PolicyModule.DSL -- | High level function used to set the policy in a PolicyModule. -- This function takes the policy module's privileges and a policy -- expression, and produces a PMAction that sets the policy. setPolicy :: DCPriv -> PolicyExpM () -> PMAction () -- | Set secrecy component of the label, i.e., the principals that can -- read. readers :: Readers -- | Set secrecy component of the label, i.e., the principals that can -- read. secrecy :: Readers -- | Set integrity component of the label, i.e., the principals that can -- write. writers :: Writers -- | Set integrity component of the label, i.e., the principals that can -- write. integrity :: Writers -- | Synonym for Admins. admins :: Admins -- | r ==> c effectively states that role r (i.e., -- readers, writers, admins must imply label -- component c). (==>) :: (Role r s m, ToComponent c) => r -> c -> m () -- | Inverse implication. Purely provided for readability. The direction is -- not relevant to the internal representation. (<==) :: (Role r s m, ToComponent c) => r -> c -> m () -- | Create a database lebeling policy The policy must set the label of the -- database, i.e., the readers and writers. Additionally it -- must state the admins that can modify the underlying -- collection-set -- -- For example, the policy -- --
-- database $ do -- readers ==> "Alice" \/ "Bob" \/ "Clarice" -- writers ==> "Alice" \/ "Bob" -- admins ==> "Alice" ---- -- states that Alice, Bob, and Clarice can read from the database, -- including the collections in the database (the readers is used -- as the secrecy component in the collection-set label). Only Alice or -- Bob may, however, write to the database. Finally, only Alice can add -- additional collections in the policy module code. database :: DBExpM () -> PolicyExpM () -- | Set the collection labels and policies. Each collection, must -- at least specify who can access the collection, what the -- clearance of the data in the collection is, and how -- documents are labeled. Below is an example that also labels the -- password field and declares name a searchable key. -- --
-- collection "w00t" $ do
-- access $ do
-- readers ==> "Alice" \/ "Bob"
-- writers ==> "Alice"
-- clearance $ do
-- secrecy ==> "Users"
-- integrity ==> "Alice"
-- document $ \doc -> do
-- readers ==> anybody
-- writers ==> "Alice" \/ (("name" `at`doc) :: String)
-- field "name" searchable
-- field "password" $ labeled $ \doc -> do
-- readers ==> (("name" `at`doc) :: String)
-- writers ==> (("name" `at`doc) :: String)
--
collection :: CollectionName -> ColExpM () -> PolicyExpM ()
-- | Set the collection access label. For example,
--
-- -- collection "w00t" $ do -- ... -- access $ do -- readers ==> "Alice" \/ "Bob" -- writers ==> "Alice" ---- -- states that Alice and Bob can read documents from the collection, but -- only Alice can insert new documents or modify existing ones. access :: ColAccExpM () -> ColExpM () -- | Set the collection clearance. For example, -- --
-- collection "w00t" $ do -- ... -- clearance $ do -- secrecy ==> "Alice" \/ "Bob" -- integrity ==> "Alice" ---- -- states that all data in the collection is always readable by Alice and -- Bob, and no more trustworthy than data Alice can create. clearance :: ColClrExpM () -> ColExpM () -- | Set data-dependent document label. For example, -- --
-- collection "w00t" $ do
-- ...
-- document $ \doc -> do
-- readers ==> anybody
-- writers ==> "Alice" \/ (("name" `at`doc) :: String)
--
--
-- states that every document in the collection is readable by anybody,
-- and only Alice or the principal named by the name value in
-- the document can modify or insert such data.
document :: (HsonDocument -> ColDocExpM ()) -> ColExpM ()
-- | Set field policy. A field can be declared to be a searchable
-- key or a labeled value.
--
-- Declaring a field to be a searchable key is straight forward:
--
-- -- collection "w00t" $ do -- ... -- field "name" searchable ---- -- The labeled field declaration is similar to the document -- policy, but sets the label of a specific field. For example -- --
-- collection "w00t" $ do -- ... -- field "password" $ labeled $ \doc -> do -- let user = "name" `at` doc :: String -- readers ==> user -- writers ==> user ---- -- states that every password field in the is readable and -- writable only by or the principal named by the name value of -- the document can modify or insert such data. field :: FieldName -> ColFieldExpM () -> ColExpM () -- | Set the underlying field to be a searchable key. -- --
-- field "name" searchable --searchable :: ColFieldExpM () -- | Synonym for searchable key :: ColFieldExpM () -- | Set data-dependent document label -- --
-- field "password" $ labeled $ \doc -> do
-- readers ==> (("name" `at`doc) :: String)
-- writers ==> (("name" `at`doc) :: String)
--
labeled :: (HsonDocument -> ColLabFieldExpM ()) -> ColFieldExpM ()
instance Typeable PolicySpecificiationError
instance Show DBExp
instance Monad DBExpM
instance MonadState DBExpS DBExpM
instance Show ColAccExp
instance Monad ColAccExpM
instance MonadState ColAccExpS ColAccExpM
instance MonadReader CollectionName ColAccExpM
instance Show ColClrExp
instance Monad ColClrExpM
instance MonadState ColClrExpS ColClrExpM
instance MonadReader CollectionName ColClrExpM
instance Monad ColDocExpM
instance MonadState ColDocExpS ColDocExpM
instance MonadReader CollectionName ColDocExpM
instance Monad ColLabFieldExpM
instance MonadState ColLabFieldExpS ColLabFieldExpM
instance MonadReader (FieldName, CollectionName) ColLabFieldExpM
instance Monad ColFieldExpM
instance MonadState (Maybe ColFieldExp) ColFieldExpM
instance MonadReader (FieldName, CollectionName) ColFieldExpM
instance Show ColExp
instance Show ColExpT
instance Monad ColExpM
instance MonadState ColExpS ColExpM
instance MonadReader CollectionName ColExpM
instance Show PolicyExp
instance Show PolicyExpT
instance Monad PolicyExpM
instance MonadState PolicyExpS PolicyExpM
instance Show PolicySpecificiationError
instance Exception PolicySpecificiationError
instance Role Writers ColLabFieldExpS ColLabFieldExpM
instance Role Readers ColLabFieldExpS ColLabFieldExpM
instance Show ColFieldExp
instance Role Writers ColDocExpS ColDocExpM
instance Role Readers ColDocExpS ColDocExpM
instance Show ColDocExp
instance Role Writers ColClrExpS ColClrExpM
instance Role Readers ColClrExpS ColClrExpM
instance Role Writers ColAccExpS ColAccExpM
instance Role Readers ColAccExpS ColAccExpM
instance Role Admins DBExpS DBExpM
instance Role Writers DBExpS DBExpM
instance Role Readers DBExpS DBExpM
instance Show Admins
instance Show Writers
instance Show Readers
-- | This module exports a class Groups that policy modules must
-- define an instance of to define groups, or mappings between a group
-- Principaland the principals in the group.
--
-- An app may then relabel a labeled value by using labelRewrite.
module Hails.PolicyModule.Groups
class PolicyModule pm => Groups pm
groups :: Groups pm => pm -> DCPriv -> Principal -> DBAction [Principal]
groupsInstanceEndorse :: Groups pm => pm
-- | Given the policy module (which is used to invoke the right
-- groups function) and labeled value, relabel the value according
-- to the Groups of the policy module. Note that the first
-- argument may be bottom since it is solely used for typing purposes.
labelRewrite :: Groups unused_pm => unused_pm -> DCLabeled a -> DBAction (DCLabeled a)
-- | 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" Documents.
module Hails.Database.Structured
-- | 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 findBy = findByP noPriv findWhere = findWhereP noPriv insertRecord = insertRecordP noPriv saveRecord = saveRecordP noPriv 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 <- getClearance; case mldoc of { Just ldoc | canFlowToP p (labelOf ldoc) c -> fromDocument `liftM` (liftLIO $ unlabelP p ldoc) _ -> return Nothing } }
fromDocument :: (DCRecord a, Monad m) => Document -> m a
toDocument :: DCRecord a => a -> Document
recordCollection :: DCRecord a => a -> CollectionName
findBy :: (DCRecord a, BsonVal v, MonadDB m) => CollectionName -> FieldName -> v -> m (Maybe a)
findWhere :: (DCRecord a, MonadDB m) => Query -> m (Maybe a)
insertRecord :: (DCRecord a, MonadDB m) => a -> m ObjectId
saveRecord :: (DCRecord a, MonadDB m) => a -> m ()
findByP :: (DCRecord a, BsonVal v, MonadDB m) => DCPriv -> CollectionName -> FieldName -> v -> m (Maybe a)
findWhereP :: (DCRecord a, MonadDB m) => DCPriv -> Query -> m (Maybe a)
insertRecordP :: (DCRecord a, MonadDB m) => DCPriv -> a -> m ObjectId
saveRecordP :: (DCRecord a, MonadDB m) => DCPriv -> a -> m ()
-- | Find all records that satisfy the query and can be read, subject to
-- the current clearance.
findAll :: (DCRecord a, MonadDB m) => Query -> m [a]
-- | Same as findAll, but uses privileges.
findAllP :: (DCRecord a, MonadDB m) => DCPriv -> Query -> m [a]
-- | 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 insertLabeledRecord lrec = insertLabeledRecordP noPriv lrec saveLabeledRecord lrec = saveLabeledRecordP noPriv 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 }
insertLabeledRecord :: (DCLabeledRecord pm a, MonadDB m) => DCLabeled a -> m ObjectId
saveLabeledRecord :: (DCLabeledRecord pm a, MonadDB m) => DCLabeled a -> m ()
insertLabeledRecordP :: (DCLabeledRecord pm a, MonadDB m) => DCPriv -> DCLabeled a -> m ObjectId
saveLabeledRecordP :: (DCLabeledRecord pm a, MonadDB m) => DCPriv -> DCLabeled a -> m ()
endorseInstance :: DCLabeledRecord pm a => DCLabeled a -> pm
-- | Convert labeled record to labeled document.
toLabeledDocument :: (MonadDB m, DCLabeledRecord pm a) => DCLabeled a -> m (DCLabeled Document)
-- | Convert labeled document to labeled record
fromLabeledDocument :: (MonadDB m, DCLabeledRecord pm a) => DCLabeled Document -> m (DCLabeled a)
-- | 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 -> m (DCLabeled Document)
-- | 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 :: (MonadDB m, DCLabeledRecord pm a) => DCPriv -> DCLabeled Document -> m (DCLabeled a)