update admin endpoints to check if authorized to upload pkg

This commit is contained in:
Lucy Cifferello
2024-04-15 16:11:27 -04:00
parent d4483f8ca3
commit 27d0b54c9b
4 changed files with 85 additions and 30 deletions

View File

@@ -19,7 +19,7 @@ import Model (
Metric (Metric), Metric (Metric),
PkgDependency (..), PkgDependency (..),
PkgRecord (PkgRecord), PkgRecord (PkgRecord),
VersionRecord (VersionRecord), VersionPlatform (VersionPlatform), EntityField (VersionPlatformPkgId, VersionPlatformVersionNumber, VersionPlatformArch), PkgRecordId, VersionRecord (VersionRecord), VersionPlatform (VersionPlatform), EntityField (VersionPlatformPkgId, VersionPlatformVersionNumber, VersionPlatformArch, AdminPkgsPkgId, AdminPkgsAdmin), PkgRecordId, AdminPkgs, AdminId,
) )
import Orphans.Emver () import Orphans.Emver ()
import Startlude ( import Startlude (
@@ -327,4 +327,13 @@ getVersionPlatform pkgId arches = do
where_ $ v ^. VersionPlatformPkgId ==. val pkgId where_ $ v ^. VersionPlatformPkgId ==. val pkgId
where_ (v ^. VersionPlatformArch `in_` (valList arches)) where_ (v ^. VersionPlatformArch `in_` (valList arches))
pure v pure v
pure $ entityVal <$> vps pure $ entityVal <$> vps
getAllowedPkgs :: (Monad m, MonadIO m) => PkgRecordId -> AdminId -> ReaderT SqlBackend m [AdminPkgs]
getAllowedPkgs pkgId adminId = do
pkgs <- select $ do
p <- from $ table @AdminPkgs
where_ $ p ^. AdminPkgsPkgId ==. val pkgId
where_ $ p ^. AdminPkgsAdmin ==. val adminId
pure p
pure $ entityVal <$> pkgs

View File

@@ -58,7 +58,7 @@ import Handler.Util (
getHashFromQuery, getHashFromQuery,
getVersionFromQuery, getVersionFromQuery,
orThrow, orThrow,
sendResponseText, sendResponseText, checkAdminAllowedPkgs,
) )
import Lib.PkgRepository ( import Lib.PkgRepository (
PkgRepo (PkgRepo, pkgRepoFileRoot), PkgRepo (PkgRepo, pkgRepoFileRoot),
@@ -150,6 +150,7 @@ import Yesod.Core.Types (JSONResponse (JSONResponse))
import Database.Persist.Sql (runSqlPool) import Database.Persist.Sql (runSqlPool)
import Data.List (elem, length) import Data.List (elem, length)
import Database.Persist ((==.)) import Database.Persist ((==.))
import Network.HTTP.Types.Status (status401)
postPkgUploadR :: Handler () postPkgUploadR :: Handler ()
postPkgUploadR = do postPkgUploadR = do
@@ -181,8 +182,12 @@ postPkgUploadR = do
"The Impossible has happened, an unauthenticated user has managed to upload a pacakge to this registry" "The Impossible has happened, an unauthenticated user has managed to upload a pacakge to this registry"
pure () pure ()
Just name -> do Just name -> do
now <- liftIO getCurrentTime authorized <- checkAdminAllowedPkgs packageManifestId name
runDB $ insert_ (Upload (AdminKey name) (PkgRecordKey packageManifestId) packageManifestVersion now) if authorized
then do
now <- liftIO getCurrentTime
runDB $ insert_ (Upload (AdminKey name) (PkgRecordKey packageManifestId) packageManifestVersion now)
else sendResponseText status401 "User not authorized to upload this package."
else sendResponseText status500 "Package does not belong on this registry." else sendResponseText status500 "Package does not belong on this registry."
where where
retry m = runMaybeT . asum $ replicate 3 (MaybeT $ hush <$> try @_ @SomeException m) retry m = runMaybeT . asum $ replicate 3 (MaybeT $ hush <$> try @_ @SomeException m)
@@ -230,24 +235,44 @@ instance ToJSON IndexPkgReq where
postPkgIndexR :: Handler () postPkgIndexR :: Handler ()
postPkgIndexR = do postPkgIndexR = do
IndexPkgReq{..} <- requireCheckJsonBody IndexPkgReq{..} <- requireCheckJsonBody
manifest <- getManifestLocation indexPkgReqId indexPkgReqVersion maybeAuthId >>= \case
man <- Nothing -> do
liftIO (decodeFileStrict manifest) $logError
`orThrow` sendResponseText "An unauthenticated user has accessed the index endpoint."
status404 pure ()
[i|Could not decode manifest for #{indexPkgReqId}@#{indexPkgReqVersion}|] Just name -> do
pool <- getsYesod appConnPool authorized <- checkAdminAllowedPkgs indexPkgReqId name
runSqlPoolNoTransaction (upsertPackageVersion man) pool Nothing if authorized
runSqlPool (upsertPackageVersionPlatform indexPkgReqArches man) pool then do
manifest <- getManifestLocation indexPkgReqId indexPkgReqVersion
man <-
liftIO (decodeFileStrict manifest)
`orThrow` sendResponseText
status404
[i|Could not decode manifest for #{indexPkgReqId}@#{indexPkgReqVersion}|]
pool <- getsYesod appConnPool
runSqlPoolNoTransaction (upsertPackageVersion man) pool Nothing
runSqlPool (upsertPackageVersionPlatform indexPkgReqArches man) pool
else sendResponseText status401 "User not authorized to index this package."
postPkgDeindexR :: Handler () postPkgDeindexR :: Handler ()
postPkgDeindexR = do postPkgDeindexR = do
IndexPkgReq{..} <- requireCheckJsonBody IndexPkgReq{..} <- requireCheckJsonBody
case indexPkgReqArches of maybeAuthId >>= \case
Nothing -> runDB $ delete (VersionRecordKey (PkgRecordKey indexPkgReqId) indexPkgReqVersion) Nothing -> do
Just a -> do $logError
_ <- traverse (deleteArch indexPkgReqId indexPkgReqVersion) a "An unauthenticated user has accessed the deindex endpoint."
pure () pure ()
Just name -> do
authorized <- checkAdminAllowedPkgs indexPkgReqId name
if authorized
then do
case indexPkgReqArches of
Nothing -> runDB $ delete (VersionRecordKey (PkgRecordKey indexPkgReqId) indexPkgReqVersion)
Just a -> do
_ <- traverse (deleteArch indexPkgReqId indexPkgReqVersion) a
pure ()
else sendResponseText status401 "User not authorized to deindex this package."
where where
deleteArch :: PkgId -> Version -> OsArch -> Handler () deleteArch :: PkgId -> Version -> OsArch -> Handler ()
deleteArch id v a = runDB $ deleteWhere [VersionPlatformArch ==. a, VersionPlatformVersionNumber ==. v, VersionPlatformPkgId ==. PkgRecordKey id] deleteArch id v a = runDB $ deleteWhere [VersionPlatformArch ==. a, VersionPlatformVersionNumber ==. v, VersionPlatformPkgId ==. PkgRecordKey id]
@@ -298,16 +323,26 @@ deleteCategoryR cat = runDB $ deleteBy (UniqueName cat)
postPkgCategorizeR :: Text -> PkgId -> Handler () postPkgCategorizeR :: Text -> PkgId -> Handler ()
postPkgCategorizeR cat pkg = runDB $ do postPkgCategorizeR cat pkg = do
catEnt <- getBy (UniqueName cat) `orThrow` sendResponseText status404 [i|Category "#{cat}" does not exist|] maybeAuthId >>= \case
_pkgEnt <- get (PkgRecordKey pkg) `orThrow` sendResponseText status404 [i|Package "#{pkg}" does not exist|] Nothing -> do
now <- liftIO getCurrentTime $logError
void $ "An unauthenticated user has accessed the categorize endpoint."
insertUnique (PkgCategory now (PkgRecordKey pkg) (entityKey catEnt)) pure ()
`orThrow` sendResponseText Just name -> do
status403 authorized <- checkAdminAllowedPkgs pkg name
[i|Package "#{pkg}" is already assigned to category "#{cat}"|] if authorized
then runDB $ do
catEnt <- getBy (UniqueName cat) `orThrow` sendResponseText status404 [i|Category "#{cat}" does not exist|]
_pkgEnt <- get (PkgRecordKey pkg) `orThrow` sendResponseText status404 [i|Package "#{pkg}" does not exist|]
now <- liftIO getCurrentTime
void $
insertUnique (PkgCategory now (PkgRecordKey pkg) (entityKey catEnt))
`orThrow` sendResponseText
status403
[i|Package "#{pkg}" is already assigned to category "#{cat}"|]
else sendResponseText status401 "User not authorized to categorize this package."
deletePkgCategorizeR :: Text -> PkgId -> Handler () deletePkgCategorizeR :: Text -> PkgId -> Handler ()
deletePkgCategorizeR cat pkg = runDB $ do deletePkgCategorizeR cat pkg = runDB $ do

View File

@@ -18,7 +18,7 @@ import Data.String.Interpolate.IsString (
import Data.Text qualified as T import Data.Text qualified as T
import Data.Text.Lazy qualified as TL import Data.Text.Lazy qualified as TL
import Data.Text.Lazy.Builder qualified as TB import Data.Text.Lazy.Builder qualified as TB
import Database.Queries (fetchAllPkgVersions, getVersionPlatform) import Database.Queries (fetchAllPkgVersions, getVersionPlatform, getAllowedPkgs)
import Foundation import Foundation
import Lib.PkgRepository ( import Lib.PkgRepository (
PkgRepo, PkgRepo,
@@ -32,7 +32,7 @@ import Lib.Types.Emver (
) )
import Model ( import Model (
UserActivity (..), UserActivity (..),
VersionRecord (versionRecordOsVersion, versionRecordDeprecatedAt, versionRecordPkgId), VersionPlatform (versionPlatformDevice), VersionRecord (versionRecordOsVersion, versionRecordDeprecatedAt, versionRecordPkgId), VersionPlatform (versionPlatformDevice), AdminId, Key (PkgRecordKey, AdminKey),
) )
import Network.HTTP.Types ( import Network.HTTP.Types (
Status, Status,
@@ -61,6 +61,7 @@ import Startlude (
void, void,
($), ($),
(.), (.),
(>),
(<$>), (<$>),
(>>=), note, (=<<), catMaybes, all, encodeUtf8, toS, fmap, traceM, show, trace, any, or, (++), IO, putStrLn, map (>>=), note, (=<<), catMaybes, all, encodeUtf8, toS, fmap, traceM, show, trace, any, or, (++), IO, putStrLn, map
) )
@@ -88,6 +89,7 @@ import Data.Aeson (eitherDecodeStrict)
import Data.Bifunctor (Bifunctor(first)) import Data.Bifunctor (Bifunctor(first))
import qualified Data.MultiMap as MM import qualified Data.MultiMap as MM
import Startlude (bimap) import Startlude (bimap)
import Data.List (length)
orThrow :: MonadHandler m => m (Maybe a) -> m a -> m a orThrow :: MonadHandler m => m (Maybe a) -> m a -> m a
orThrow action other = orThrow action other =
@@ -252,4 +254,9 @@ areRegexMatchesEqual textMap (PackageDevice regexMap) =
checkMatch :: (Text, RegexPattern) -> Bool checkMatch :: (Text, RegexPattern) -> Bool
checkMatch (key, regexPattern) = checkMatch (key, regexPattern) =
case MM.lookup key textMap of case MM.lookup key textMap of
val -> or $ regexMatch regexPattern <$> val val -> or $ regexMatch regexPattern <$> val
checkAdminAllowedPkgs :: PkgId -> Text -> Handler Bool
checkAdminAllowedPkgs pkgId adminId = do
res <- runDB $ getAllowedPkgs (PkgRecordKey pkgId) (AdminKey adminId)
pure $ if length res > 0 then True else False

View File

@@ -153,6 +153,10 @@ Admin
passHash (Digest SHA256) passHash (Digest SHA256)
deletedAt UTCTime Maybe deletedAt UTCTime Maybe
AdminPkgs
admin AdminId
pkgId PkgRecordId
Upload Upload
uploader AdminId uploader AdminId
pkgId PkgRecordId pkgId PkgRecordId