diff --git a/biscuit/src/Auth/Biscuit/Datalog/ScopedExecutor.hs b/biscuit/src/Auth/Biscuit/Datalog/ScopedExecutor.hs index 44b021a..9d676ec 100644 --- a/biscuit/src/Auth/Biscuit/Datalog/ScopedExecutor.hs +++ b/biscuit/src/Auth/Biscuit/Datalog/ScopedExecutor.hs @@ -12,6 +12,7 @@ module Auth.Biscuit.Datalog.ScopedExecutor ( BlockWithRevocationId , runAuthorizer , runAuthorizerWithLimits + , runAuthorizerWithLimitsPure , runAuthorizerNoTimeout , runFactGeneration , PureExecError (..) @@ -31,6 +32,7 @@ import Control.Monad.State (StateT (..), evalStateT, get, import Data.Bifunctor (first) import Data.ByteString (ByteString) import Data.Foldable (fold, traverse_) +import Data.Functor.Identity (Identity(Identity)) import Data.List (genericLength) import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as NE @@ -115,6 +117,19 @@ runAuthorizerWithLimits l@Limits{..} authority blocks v = do Nothing -> Left Timeout Just r -> r +-- | Given a series of blocks and an authorizer, ensure that all +-- the checks and policies match +runAuthorizerWithLimitsPure :: Limits + -- ^ custom limits + -> BlockWithRevocationId + -- ^ The authority block + -> [BlockWithRevocationId] + -- ^ The extra blocks + -> Authorizer + -- ^ A authorizer + -> Identity (Either ExecutionError AuthorizationSuccess) +runAuthorizerWithLimitsPure l authority blocks v = + Identity $ runAuthorizerNoTimeout l authority blocks v mkRevocationIdFacts :: BlockWithRevocationId -> [BlockWithRevocationId] -> Set Fact diff --git a/biscuit/src/Auth/Biscuit/Token.hs b/biscuit/src/Auth/Biscuit/Token.hs index 3666657..a2b14e5 100644 --- a/biscuit/src/Auth/Biscuit/Token.hs +++ b/biscuit/src/Auth/Biscuit/Token.hs @@ -45,6 +45,8 @@ module Auth.Biscuit.Token , serializeBiscuit , authorizeBiscuit , authorizeBiscuitWithLimits + , authorizeBiscuitNoTimeout + , authorizeBiscuitWithLimitsNoTimeout , fromOpen , fromSealed , asOpen @@ -67,6 +69,7 @@ import Data.Bifunctor (first) import Data.ByteString (ByteString) import qualified Data.ByteString.Base64.URL as B64 import Data.Foldable (fold) +import Data.Functor.Identity (runIdentity) import Data.List.NonEmpty (NonEmpty ((:|))) import qualified Data.List.NonEmpty as NE import Data.Set (Set) @@ -89,10 +92,12 @@ import Auth.Biscuit.Datalog.AST (Authorizer, Block, Query, import Auth.Biscuit.Datalog.Executor (Bindings, ExecutionError, Limits, defaultLimits) import Auth.Biscuit.Datalog.ScopedExecutor (AuthorizationSuccess, + BlockWithRevocationId, collectWorld, queryAvailableFacts, queryGeneratedFacts, - runAuthorizerWithLimits) + runAuthorizerWithLimits, + runAuthorizerWithLimitsPure) import qualified Auth.Biscuit.Proto as PB import Auth.Biscuit.ProtoBufAdapter (blockToPb, pbToBlock, pbToProof, @@ -559,9 +564,11 @@ getRevocationIds Biscuit{authority, blocks} = getRevocationId (_, sig, _, _) = sigBytes sig in getRevocationId <$> allBlocks --- | Generic version of 'authorizeBiscuitWithLimits' which takes custom 'Limits'. -authorizeBiscuitWithLimits :: Limits -> Biscuit proof Verified -> Authorizer -> IO (Either ExecutionError (AuthorizedBiscuit proof)) -authorizeBiscuitWithLimits l biscuit@Biscuit{..} authorizer = +-- | Generic version of 'authorizeBiscuit' which takes custom 'Limits' and a runner function +-- to run actual authorization. It is polymorphic on the functor returned by the runner. +authorizeBiscuitWithRunner :: Functor f => (Limits -> BlockWithRevocationId -> [BlockWithRevocationId] -> Authorizer -> f (Either ExecutionError AuthorizationSuccess)) + -> Limits -> Biscuit proof Verified -> Authorizer -> f (Either ExecutionError (AuthorizedBiscuit proof)) +authorizeBiscuitWithRunner runAuthorizerF l biscuit@Biscuit{..} authorizer = let toBlockWithRevocationId ((_, block), sig, _, eSig) = (block, sigBytes sig, snd <$> eSig) -- the authority block can't be externally signed. If it carries a signature, it won't be -- verified. So we need to make sure there is none, to avoid having facts trusted without @@ -573,11 +580,25 @@ authorizeBiscuitWithLimits l biscuit@Biscuit{..} authorizer = , authorizationSuccess } in fmap withBiscuit <$> - runAuthorizerWithLimits l + runAuthorizerF l (dropExternalPk $ toBlockWithRevocationId authority) (toBlockWithRevocationId <$> blocks) authorizer +-- | Generic version of 'authorizeBiscuitWithLimits' which takes custom 'Limits'. +authorizeBiscuitWithLimits :: Limits -> Biscuit proof Verified -> Authorizer -> IO (Either ExecutionError (AuthorizedBiscuit proof)) +authorizeBiscuitWithLimits = authorizeBiscuitWithRunner runAuthorizerWithLimits + +-- | Generic version of 'authorizeBiscuit' which takes custom 'Limits' and doesn't timeout. +-- +-- The absence of timeout enables this function to be pure but opens one important vulnerability: +-- an attacker could craft a biscuit with some pathological datalog whose evaluation takes a very +-- long time. So this function should only be used in a context that sets a timeout if run time +-- can be an issue. +authorizeBiscuitWithLimitsNoTimeout :: Limits -> Biscuit proof Verified -> Authorizer -> Either ExecutionError (AuthorizedBiscuit proof) +authorizeBiscuitWithLimitsNoTimeout l biscuit authorizer = + runIdentity $ authorizeBiscuitWithRunner runAuthorizerWithLimitsPure l biscuit authorizer + -- | Given a biscuit with a verified signature and an authorizer (a set of facts, rules, checks -- and policies), verify a biscuit: -- @@ -594,6 +615,9 @@ authorizeBiscuitWithLimits l biscuit@Biscuit{..} authorizer = authorizeBiscuit :: Biscuit proof Verified -> Authorizer -> IO (Either ExecutionError (AuthorizedBiscuit proof)) authorizeBiscuit = authorizeBiscuitWithLimits defaultLimits +authorizeBiscuitNoTimeout :: Biscuit proof Verified -> Authorizer -> Either ExecutionError (AuthorizedBiscuit proof) +authorizeBiscuitNoTimeout = authorizeBiscuitWithLimitsNoTimeout defaultLimits + -- | Retrieve the `PublicKey` which was used to verify the `Biscuit` signatures getVerifiedBiscuitPublicKey :: Biscuit a Verified -> PublicKey getVerifiedBiscuitPublicKey Biscuit{proofCheck} =