move auth logic into upload endpoint and remove separate endpoint

This commit is contained in:
Lucy Cifferello
2024-04-16 18:33:58 -04:00
parent 8834dd1d28
commit e508ec64d4
5 changed files with 77 additions and 71 deletions

View File

@@ -18,8 +18,7 @@
/package/#ApiVersion/version/#PkgId PkgVersionR GET -- get most recent appId version /package/#ApiVersion/version/#PkgId PkgVersionR GET -- get most recent appId version
-- ADMIN API V0 -- ADMIN API V0
/admin/v0/auth/#PkgId CheckPkgAuthR POST !admin /admin/v0/upload PkgUploadR POST !admin -- ?id=<pkgId>
/admin/v0/upload PkgUploadR POST !admin
/admin/v0/eos-upload EosUploadR POST !admin /admin/v0/eos-upload EosUploadR POST !admin
/admin/v0/index PkgIndexR POST !admin /admin/v0/index PkgIndexR POST !admin
/admin/v0/deindex PkgDeindexR GET POST !admin /admin/v0/deindex PkgDeindexR GET POST !admin

View File

@@ -184,7 +184,6 @@ import Handler.Admin (
postPkgDeindexR, postPkgDeindexR,
postPkgIndexR, postPkgIndexR,
postPkgUploadR, postPkgUploadR,
postCheckPkgAuthR
) )
import Handler.Eos (getEosR, getEosVersionR) import Handler.Eos (getEosR, getEosVersionR)
import Handler.Root(getRootR, getMarketplaceR) import Handler.Root(getRootR, getMarketplaceR)

View File

@@ -90,7 +90,7 @@ import Network.HTTP.Simple (
setRequestBody, setRequestBody,
setRequestBodyJSON, setRequestBodyJSON,
setRequestHeaders, setRequestHeaders,
setRequestResponseTimeout, setRequestResponseTimeout, setRequestQueryString,
) )
import Network.HTTP.Types (status200) import Network.HTTP.Types (status200)
import Network.URI ( import Network.URI (
@@ -179,7 +179,7 @@ import Startlude (
($>), ($>),
(&), (&),
(.), (.),
(<&>), (<&>), encodeUtf8,
) )
import System.Directory ( import System.Directory (
createDirectoryIfMissing, createDirectoryIfMissing,
@@ -529,20 +529,10 @@ upload (Upload name mpkg shouldIndex arches) = do
exitWith $ ExitFailure 1 exitWith $ ExitFailure 1
Just s -> pure s Just s -> pure s
let pkgId_ = head $ splitOn "." $ last $ splitOn "/" $ show pkg let pkgId_ = head $ splitOn "." $ last $ splitOn "/" $ show pkg
pkgAuthBody <-
parseRequest ("POST " <> show publishCfgRepoLocation <> "/admin/v0/auth/" <> unpack pkgId_)
<&> setRequestHeaders [("accept", "text/plain")]
<&> setRequestResponseTimeout (responseTimeoutMicro (90_000_000))
<&> applyBasicAuth (B8.pack publishCfgRepoUser) (B8.pack publishCfgRepoPass)
manager <- newTlsManager manager <- newTlsManager
pkgAuthRes <- runReaderT (httpLbs pkgAuthBody) manager
if getResponseStatus pkgAuthRes == status200
then pure () -- no output is successful
else do
$logError (decodeUtf8 . LB.toStrict $ getResponseBody pkgAuthRes)
exitWith $ ExitFailure 1
noBody <- noBody <-
parseRequest ("POST " <> show publishCfgRepoLocation <> "/admin/v0/upload") parseRequest ("POST " <> show publishCfgRepoLocation <> "/admin/v0/upload")
<&> setRequestQueryString [("id", Just $ encodeUtf8 pkgId_)]
<&> setRequestHeaders [("accept", "text/plain")] <&> setRequestHeaders [("accept", "text/plain")]
<&> setRequestResponseTimeout (responseTimeoutMicro (5_400_000_000)) -- 90 minutes <&> setRequestResponseTimeout (responseTimeoutMicro (5_400_000_000)) -- 90 minutes
<&> applyBasicAuth (B8.pack publishCfgRepoUser) (B8.pack publishCfgRepoPass) <&> applyBasicAuth (B8.pack publishCfgRepoUser) (B8.pack publishCfgRepoPass)

View File

@@ -58,7 +58,7 @@ import Handler.Util (
getHashFromQuery, getHashFromQuery,
getVersionFromQuery, getVersionFromQuery,
orThrow, orThrow,
sendResponseText, checkAdminAllowedPkgs, checkAdminAuth, sendResponseText, checkAdminAuth, checkAdminAuthUpload, getPkgIdParam,
) )
import Lib.PkgRepository ( import Lib.PkgRepository (
PkgRepo (PkgRepo, pkgRepoFileRoot), PkgRepo (PkgRepo, pkgRepoFileRoot),
@@ -79,7 +79,7 @@ import Model (
Unique (UniqueName, UniquePkgCategory), Unique (UniqueName, UniquePkgCategory),
Upload (..), Upload (..),
VersionRecord (versionRecordNumber, versionRecordPkgId), VersionRecord (versionRecordNumber, versionRecordPkgId),
unPkgRecordKey, AdminPkgs (AdminPkgs), unPkgRecordKey,
) )
import Network.HTTP.Types ( import Network.HTTP.Types (
status400, status400,
@@ -116,10 +116,8 @@ import Startlude (
(<$>), (<$>),
(<<$>>), (<<$>>),
(<>), (<>),
(>), (/=),
(&&), FilePath
(||),
(<=),
) )
import System.FilePath ( import System.FilePath (
(<.>), (<.>),
@@ -145,68 +143,50 @@ import Yesod (
runDB, runDB,
sendResponseStatus, sendResponseStatus,
) )
import Yesod.Auth (YesodAuth (maybeAuthId))
import Yesod.Core.Types (JSONResponse (JSONResponse)) import Yesod.Core.Types (JSONResponse (JSONResponse))
import Database.Persist.Sql (runSqlPool) import Database.Persist.Sql (runSqlPool)
import Data.List (elem, length)
import Database.Persist ((==.)) import Database.Persist ((==.))
import Network.HTTP.Types.Status (status401) import Network.HTTP.Types.Status (status401)
import Network.HTTP.Types (status200)
postCheckPkgAuthR :: PkgId -> Handler ()
postCheckPkgAuthR pkgId = do
whitelist <- getsYesod $ whitelist . appSettings
maybeAuthId >>= \case
Nothing -> do
sendResponseText status401 "User not an authorized admin."
Just name -> do
if ((length whitelist > 0 && (pkgId `elem` whitelist)) || length whitelist <= 0)
then do
(authorized, newPkg) <- checkAdminAllowedPkgs pkgId name
if authorized && not newPkg
then sendResponseText status200 "User authorized to upload this package."
else if authorized && newPkg
-- if pkg is whitelisted and a new upload, add as authorized for this admin user
then do
runDB $ insert_ (AdminPkgs (AdminKey name) pkgId)
sendResponseText status200 "User authorized to upload this package."
else sendResponseText status401 "User not authorized to upload this package."
else sendResponseText status500 "Package does not belong on this registry."
postPkgUploadR :: Handler () postPkgUploadR :: Handler ()
postPkgUploadR = do postPkgUploadR = do
resourcesTemp <- getsYesod $ (</> "temp") . resourcesDir . appSettings resourcesTemp <- getsYesod $ (</> "temp") . resourcesDir . appSettings
whitelist <- getsYesod $ whitelist . appSettings
createDirectoryIfMissing True resourcesTemp createDirectoryIfMissing True resourcesTemp
pkgId_ <- getPkgIdParam
withTempDirectory resourcesTemp "newpkg" $ \dir -> do withTempDirectory resourcesTemp "newpkg" $ \dir -> do
let path = dir </> "temp" <.> "s9pk" let path = dir </> "temp" <.> "s9pk"
runConduit $ rawRequestBody .| sinkFile path case pkgId_ of
pool <- getsYesod appConnPool Nothing -> do
PkgRepo{..} <- ask PackageManifest{..} <- extractPackageManifest dir path
res <- retry $ extractPkg pool path name <- checkAdminAuthUpload packageManifestId
when (isNothing res) $ do finishUpload dir path name PackageManifest{..}
$logError "Failed to extract package" Just pkgId -> do
sendResponseText status500 "Failed to extract package" name <- checkAdminAuthUpload pkgId
PackageManifest{..} <- do PackageManifest{..} <- extractPackageManifest dir path
liftIO (decodeFileStrict (dir </> "manifest.json")) if packageManifestId /= pkgId
`orThrow` sendResponseText status500 "Failed to parse manifest.json" then sendResponseText status401 [i|Package id #{packageManifestId} does not match request id of #{pkgId}|]
if ((length whitelist > 0 && (packageManifestId `elem` whitelist)) || length whitelist <= 0) else finishUpload dir path name PackageManifest{..}
then do where
retry m = runMaybeT . asum $ replicate 3 (MaybeT $ hush <$> try @_ @SomeException m)
extractPackageManifest :: FilePath -> FilePath -> Handler PackageManifest
extractPackageManifest dir path = do
runConduit $ rawRequestBody .| sinkFile path
pool <- getsYesod appConnPool
res <- retry $ extractPkg pool path
when (isNothing res) $ do
$logError "Failed to extract package"
sendResponseText status500 "Failed to extract package"
liftIO (decodeFileStrict (dir </> "manifest.json")) `orThrow` sendResponseText status500 "Failed to parse manifest.json"
finishUpload :: FilePath -> FilePath -> Text -> PackageManifest -> Handler ()
finishUpload dir path admin PackageManifest{..} = do
PkgRepo{..} <- ask
renameFile path (dir </> (toS . unPkgId) packageManifestId <.> "s9pk") renameFile path (dir </> (toS . unPkgId) packageManifestId <.> "s9pk")
let targetPath = pkgRepoFileRoot </> show packageManifestId </> show packageManifestVersion let targetPath = pkgRepoFileRoot </> show packageManifestId </> show packageManifestVersion
removePathForcibly targetPath removePathForcibly targetPath
createDirectoryIfMissing True targetPath createDirectoryIfMissing True targetPath
renameDirectory dir targetPath renameDirectory dir targetPath
(authorized, name) <- checkAdminAuth packageManifestId now <- liftIO getCurrentTime
if authorized runDB $ insert_ (Upload (AdminKey admin) (PkgRecordKey packageManifestId)packageManifestVersion now)
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."
where
retry m = runMaybeT . asum $ replicate 3 (MaybeT $ hush <$> try @_ @SomeException m)
postEosUploadR :: Handler () postEosUploadR :: Handler ()
postEosUploadR = do postEosUploadR = do

View File

@@ -32,7 +32,7 @@ import Lib.Types.Emver (
) )
import Model ( import Model (
UserActivity (..), UserActivity (..),
VersionRecord (versionRecordOsVersion, versionRecordDeprecatedAt, versionRecordPkgId), VersionPlatform (versionPlatformDevice), AdminId, Key (PkgRecordKey, AdminKey), VersionRecord (versionRecordOsVersion, versionRecordDeprecatedAt, versionRecordPkgId), VersionPlatform (versionPlatformDevice), AdminId, Key (PkgRecordKey, AdminKey), AdminPkgs (AdminPkgs),
) )
import Network.HTTP.Types ( import Network.HTTP.Types (
Status, Status,
@@ -63,6 +63,9 @@ import Startlude (
(.), (.),
(>), (>),
(<$>), (<$>),
(&&),
(||),
(<=),
(>>=), 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
) )
import UnliftIO (MonadUnliftIO) import UnliftIO (MonadUnliftIO)
@@ -89,9 +92,15 @@ 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) import Data.List (elem, length)
import Control.Monad.Logger (logError) import Control.Monad.Logger (logError)
import Yesod.Auth (YesodAuth(maybeAuthId)) import Yesod.Auth (YesodAuth(maybeAuthId))
import Network.HTTP.Types.Status (status401)
import Yesod (getsYesod)
import Settings (AppSettings(whitelist))
import Network.HTTP.Types (status200)
import Database.Persist (insert_)
import Yesod (lookupPostParam)
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 =
@@ -277,4 +286,33 @@ checkAdminAuth pkgId = do
pure (False, "") pure (False, "")
Just name -> do Just name -> do
(authorized, _) <- checkAdminAllowedPkgs pkgId name (authorized, _) <- checkAdminAllowedPkgs pkgId name
pure (authorized, name) pure (authorized, name)
checkAdminAuthUpload :: PkgId -> Handler Text
checkAdminAuthUpload pkgId = do
whitelist <- getsYesod $ whitelist . appSettings
maybeAuthId >>= \case
Nothing -> do
sendResponseText status401 "User not an authorized admin."
Just name -> do
if ((length whitelist > 0 && (pkgId `elem` whitelist)) || length whitelist <= 0)
then do
(authorized, newPkg) <- checkAdminAllowedPkgs pkgId name
if authorized && not newPkg
then pure name
else if authorized && newPkg
-- if pkg is whitelisted and a new upload, add as authorized for this admin user
then do
runDB $ insert_ (AdminPkgs (AdminKey name) pkgId)
pure name
else sendResponseText status401 "User not authorized to upload this package."
else sendResponseText status401 "Package does not belong on this registry."
getPkgIdParam :: MonadHandler m => m (Maybe PkgId)
getPkgIdParam = do
id <- lookupPostParam "id"
case id of
Nothing -> pure Nothing
Just v -> case readMaybe v of
Nothing -> sendResponseStatus status400 ("Invalid PkgId" :: Text)
Just t -> pure (Just t)