module Ecluse.Core.Security.Limits (
Limits (..),
defaultLimits,
BodyLimit (..),
bodyLimitBytes,
LimitError (..),
boundedRead,
checkVersionCountOf,
checkArtifactCount,
ProgressFloor,
ProgressFloorError (..),
mkProgressFloor,
floorWindowMicros,
floorMinBytes,
floorServeCapMicros,
requestTimeoutSeconds,
serveCapMarginSeconds,
serveCapSeconds,
) where
import Data.ByteString qualified as BS
import Data.ByteString.Builder (byteString, toLazyByteString)
import Data.ByteString.Lazy qualified as BSL
import Data.Map.Strict qualified as Map
import Data.Time (NominalDiffTime)
import Ecluse.Core.Package (PackageInfo, infoVersions, pkgArtifacts)
data Limits = Limits
{ Limits -> Int
maxMetadataBytes :: Int
, Limits -> Int
maxPublishRequestBytes :: Int
, Limits -> Int
maxMirrorArtifactBytes :: Int
, Limits -> Int
maxVersionCount :: Int
, Limits -> Int
maxArtifactCount :: Int
, Limits -> Int
maxNestingDepth :: Int
, Limits -> ProgressFloor
progressFloor :: ProgressFloor
}
deriving stock (Limits -> Limits -> Bool
(Limits -> Limits -> Bool)
-> (Limits -> Limits -> Bool) -> Eq Limits
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Limits -> Limits -> Bool
== :: Limits -> Limits -> Bool
$c/= :: Limits -> Limits -> Bool
/= :: Limits -> Limits -> Bool
Eq, Int -> Limits -> ShowS
[Limits] -> ShowS
Limits -> String
(Int -> Limits -> ShowS)
-> (Limits -> String) -> ([Limits] -> ShowS) -> Show Limits
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Limits -> ShowS
showsPrec :: Int -> Limits -> ShowS
$cshow :: Limits -> String
show :: Limits -> String
$cshowList :: [Limits] -> ShowS
showList :: [Limits] -> ShowS
Show)
defaultLimits :: Limits
defaultLimits :: Limits
defaultLimits =
Limits
{ maxMetadataBytes :: Int
maxMetadataBytes = Int
128 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
1024 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
1024
, maxPublishRequestBytes :: Int
maxPublishRequestBytes = Int
12 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
1024 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
1024
, maxMirrorArtifactBytes :: Int
maxMirrorArtifactBytes = Int
12 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
1024 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
1024
, maxVersionCount :: Int
maxVersionCount = Int
1_000_000
, maxArtifactCount :: Int
maxArtifactCount = Int
1_000_000
, maxNestingDepth :: Int
maxNestingDepth = Int
64
, progressFloor :: ProgressFloor
progressFloor = Int -> Int -> Int -> ProgressFloor
ProgressFloor (Integer -> Int
forall a. Num a => Integer -> a
fromInteger (NominalDiffTime -> Integer
toMicros NominalDiffTime
10)) (Int
1024 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
1024) (Integer -> Int
forall a. Num a => Integer -> a
fromInteger (NominalDiffTime -> Integer
toMicros (Int -> NominalDiffTime
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
serveCapSeconds)))
}
data BodyLimit
=
MetadataBodyLimit Int
|
PublishRequestBodyLimit Int
|
MirrorArtifactBodyLimit Int
deriving stock (BodyLimit -> BodyLimit -> Bool
(BodyLimit -> BodyLimit -> Bool)
-> (BodyLimit -> BodyLimit -> Bool) -> Eq BodyLimit
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: BodyLimit -> BodyLimit -> Bool
== :: BodyLimit -> BodyLimit -> Bool
$c/= :: BodyLimit -> BodyLimit -> Bool
/= :: BodyLimit -> BodyLimit -> Bool
Eq, Int -> BodyLimit -> ShowS
[BodyLimit] -> ShowS
BodyLimit -> String
(Int -> BodyLimit -> ShowS)
-> (BodyLimit -> String)
-> ([BodyLimit] -> ShowS)
-> Show BodyLimit
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> BodyLimit -> ShowS
showsPrec :: Int -> BodyLimit -> ShowS
$cshow :: BodyLimit -> String
show :: BodyLimit -> String
$cshowList :: [BodyLimit] -> ShowS
showList :: [BodyLimit] -> ShowS
Show)
bodyLimitBytes :: BodyLimit -> Int
bodyLimitBytes :: BodyLimit -> Int
bodyLimitBytes = \case
MetadataBodyLimit Int
cap -> Int
cap
PublishRequestBodyLimit Int
cap -> Int
cap
MirrorArtifactBodyLimit Int
cap -> Int
cap
data LimitError
=
BodyTooLarge BodyLimit
|
TooManyVersions Int Int
|
TooManyArtifacts Int Int
|
TooDeeplyNested Int
deriving stock (LimitError -> LimitError -> Bool
(LimitError -> LimitError -> Bool)
-> (LimitError -> LimitError -> Bool) -> Eq LimitError
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: LimitError -> LimitError -> Bool
== :: LimitError -> LimitError -> Bool
$c/= :: LimitError -> LimitError -> Bool
/= :: LimitError -> LimitError -> Bool
Eq, Int -> LimitError -> ShowS
[LimitError] -> ShowS
LimitError -> String
(Int -> LimitError -> ShowS)
-> (LimitError -> String)
-> ([LimitError] -> ShowS)
-> Show LimitError
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> LimitError -> ShowS
showsPrec :: Int -> LimitError -> ShowS
$cshow :: LimitError -> String
show :: LimitError -> String
$cshowList :: [LimitError] -> ShowS
showList :: [LimitError] -> ShowS
Show)
boundedRead :: (Monad m) => BodyLimit -> m ByteString -> m (Either LimitError (Int, ByteString))
boundedRead :: forall (m :: * -> *).
Monad m =>
BodyLimit
-> m ByteString -> m (Either LimitError (Int, ByteString))
boundedRead BodyLimit
bound m ByteString
readChunk = Int -> Builder -> m (Either LimitError (Int, ByteString))
go Int
0 Builder
forall a. Monoid a => a
mempty
where
cap :: Int
cap = BodyLimit -> Int
bodyLimitBytes BodyLimit
bound
go :: Int -> Builder -> m (Either LimitError (Int, ByteString))
go !Int
seen Builder
acc = do
chunk <- m ByteString
readChunk
if BS.null chunk
then pure (Right (seen, BSL.toStrict (toLazyByteString acc)))
else
let seen' = Int
seen Int -> Int -> Int
forall a. Num a => a -> a -> a
+ ByteString -> Int
BS.length ByteString
chunk
in if seen' > cap
then pure (Left (BodyTooLarge bound))
else go seen' (acc <> byteString chunk)
checkVersionCountOf :: Limits -> Int -> Either LimitError ()
checkVersionCountOf :: Limits -> Int -> Either LimitError ()
checkVersionCountOf Limits
limits Int
count
| Int
count Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
cap = LimitError -> Either LimitError ()
forall a b. a -> Either a b
Left (Int -> Int -> LimitError
TooManyVersions Int
count Int
cap)
| Bool
otherwise = () -> Either LimitError ()
forall a b. b -> Either a b
Right ()
where
cap :: Int
cap = Limits -> Int
maxVersionCount Limits
limits
checkArtifactCount :: Limits -> PackageInfo -> Either LimitError PackageInfo
checkArtifactCount :: Limits -> PackageInfo -> Either LimitError PackageInfo
checkArtifactCount Limits
limits PackageInfo
info
| Int
seen Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
cap = LimitError -> Either LimitError PackageInfo
forall a b. a -> Either a b
Left (Int -> Int -> LimitError
TooManyArtifacts Int
seen Int
cap)
| Bool
otherwise = PackageInfo -> Either LimitError PackageInfo
forall a b. b -> Either a b
Right PackageInfo
info
where
cap :: Int
cap = Limits -> Int
maxArtifactCount Limits
limits
seen :: Int
seen = (Int -> PackageDetails -> Int)
-> Int -> Map Text PackageDetails -> Int
forall a b k. (a -> b -> a) -> a -> Map k b -> a
Map.foldl' (\Int
acc PackageDetails
details -> Int
acc Int -> Int -> Int
forall a. Num a => a -> a -> a
+ NonEmpty Artifact -> Int
forall a. NonEmpty a -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length (PackageDetails -> NonEmpty Artifact
pkgArtifacts PackageDetails
details)) Int
0 (PackageInfo -> Map Text PackageDetails
infoVersions PackageInfo
info)
requestTimeoutSeconds :: Int
requestTimeoutSeconds :: Int
requestTimeoutSeconds = Int
60
serveCapMarginSeconds :: Int
serveCapMarginSeconds :: Int
serveCapMarginSeconds = Int
10
serveCapSeconds :: Int
serveCapSeconds :: Int
serveCapSeconds = Int
requestTimeoutSeconds Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
serveCapMarginSeconds
data ProgressFloor = ProgressFloor Int Int Int
deriving stock (ProgressFloor -> ProgressFloor -> Bool
(ProgressFloor -> ProgressFloor -> Bool)
-> (ProgressFloor -> ProgressFloor -> Bool) -> Eq ProgressFloor
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ProgressFloor -> ProgressFloor -> Bool
== :: ProgressFloor -> ProgressFloor -> Bool
$c/= :: ProgressFloor -> ProgressFloor -> Bool
/= :: ProgressFloor -> ProgressFloor -> Bool
Eq, Int -> ProgressFloor -> ShowS
[ProgressFloor] -> ShowS
ProgressFloor -> String
(Int -> ProgressFloor -> ShowS)
-> (ProgressFloor -> String)
-> ([ProgressFloor] -> ShowS)
-> Show ProgressFloor
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ProgressFloor -> ShowS
showsPrec :: Int -> ProgressFloor -> ShowS
$cshow :: ProgressFloor -> String
show :: ProgressFloor -> String
$cshowList :: [ProgressFloor] -> ShowS
showList :: [ProgressFloor] -> ShowS
Show)
data ProgressFloorError
=
WindowNotPositive
|
WindowNotBelowServeCap
|
MinBytesNotPositive
deriving stock (ProgressFloorError -> ProgressFloorError -> Bool
(ProgressFloorError -> ProgressFloorError -> Bool)
-> (ProgressFloorError -> ProgressFloorError -> Bool)
-> Eq ProgressFloorError
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ProgressFloorError -> ProgressFloorError -> Bool
== :: ProgressFloorError -> ProgressFloorError -> Bool
$c/= :: ProgressFloorError -> ProgressFloorError -> Bool
/= :: ProgressFloorError -> ProgressFloorError -> Bool
Eq, Int -> ProgressFloorError -> ShowS
[ProgressFloorError] -> ShowS
ProgressFloorError -> String
(Int -> ProgressFloorError -> ShowS)
-> (ProgressFloorError -> String)
-> ([ProgressFloorError] -> ShowS)
-> Show ProgressFloorError
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ProgressFloorError -> ShowS
showsPrec :: Int -> ProgressFloorError -> ShowS
$cshow :: ProgressFloorError -> String
show :: ProgressFloorError -> String
$cshowList :: [ProgressFloorError] -> ShowS
showList :: [ProgressFloorError] -> ShowS
Show)
mkProgressFloor :: NominalDiffTime -> NominalDiffTime -> Int -> Either (NonEmpty ProgressFloorError) ProgressFloor
mkProgressFloor :: NominalDiffTime
-> NominalDiffTime
-> Int
-> Either (NonEmpty ProgressFloorError) ProgressFloor
mkProgressFloor NominalDiffTime
serveCap NominalDiffTime
window Int
minBytes =
Either (NonEmpty ProgressFloorError) ProgressFloor
-> (NonEmpty ProgressFloorError
-> Either (NonEmpty ProgressFloorError) ProgressFloor)
-> Maybe (NonEmpty ProgressFloorError)
-> Either (NonEmpty ProgressFloorError) ProgressFloor
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (ProgressFloor -> Either (NonEmpty ProgressFloorError) ProgressFloor
forall a b. b -> Either a b
Right (Int -> Int -> Int -> ProgressFloor
ProgressFloor (Integer -> Int
forall a. Num a => Integer -> a
fromInteger Integer
windowMicros) Int
minBytes (Integer -> Int
forall a. Num a => Integer -> a
fromInteger Integer
capMicros))) NonEmpty ProgressFloorError
-> Either (NonEmpty ProgressFloorError) ProgressFloor
forall a b. a -> Either a b
Left ([ProgressFloorError] -> Maybe (NonEmpty ProgressFloorError)
forall a. [a] -> Maybe (NonEmpty a)
nonEmpty [ProgressFloorError]
refusals)
where
windowMicros :: Integer
windowMicros = NominalDiffTime -> Integer
toMicros NominalDiffTime
window
capMicros :: Integer
capMicros = NominalDiffTime -> Integer
toMicros NominalDiffTime
serveCap
refusals :: [ProgressFloorError]
refusals =
[ProgressFloorError
WindowNotPositive | Integer
windowMicros Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
<= Integer
0]
[ProgressFloorError]
-> [ProgressFloorError] -> [ProgressFloorError]
forall a. Semigroup a => a -> a -> a
<> [ProgressFloorError
WindowNotBelowServeCap | Integer
windowMicros Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
> Integer
0, Integer
windowMicros Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
>= Integer
capMicros]
[ProgressFloorError]
-> [ProgressFloorError] -> [ProgressFloorError]
forall a. Semigroup a => a -> a -> a
<> [ProgressFloorError
MinBytesNotPositive | Int
minBytes Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
0]
floorWindowMicros :: ProgressFloor -> Int
floorWindowMicros :: ProgressFloor -> Int
floorWindowMicros (ProgressFloor Int
window Int
_ Int
_) = Int
window
floorMinBytes :: ProgressFloor -> Int
floorMinBytes :: ProgressFloor -> Int
floorMinBytes (ProgressFloor Int
_ Int
minBytes Int
_) = Int
minBytes
floorServeCapMicros :: ProgressFloor -> Int
floorServeCapMicros :: ProgressFloor -> Int
floorServeCapMicros (ProgressFloor Int
_ Int
_ Int
cap) = Int
cap
toMicros :: NominalDiffTime -> Integer
toMicros :: NominalDiffTime -> Integer
toMicros NominalDiffTime
seconds = NominalDiffTime -> Integer
forall b. Integral b => NominalDiffTime -> b
forall a b. (RealFrac a, Integral b) => a -> b
round (NominalDiffTime
seconds NominalDiffTime -> NominalDiffTime -> NominalDiffTime
forall a. Num a => a -> a -> a
* NominalDiffTime
1_000_000)