mirror of
https://github.com/Start9Labs/registry.git
synced 2026-03-26 02:11:53 +00:00
Fix/misc (#134)
* use latest version of dependency for metadata if best version is unsatisfied * cleanup * add config setting to allow protections around package uploads to specific registries * change to whitelist * properly parse whitelist * enable deleting deprecated admin users
This commit is contained in:
@@ -39,6 +39,7 @@ marketplace-name: "_env:MARKETPLACE_NAME:CHANGE ME"
|
|||||||
max-eos-version: "_env:MAX_VERSION:0.3.4.0"
|
max-eos-version: "_env:MAX_VERSION:0.3.4.0"
|
||||||
min-eos-version: "_env:MIN_VERSION:0.3.4.0"
|
min-eos-version: "_env:MIN_VERSION:0.3.4.0"
|
||||||
run-migration: "_env:RUN_MIGRATION:false"
|
run-migration: "_env:RUN_MIGRATION:false"
|
||||||
|
whitelist: "_env:WHITELIST:ADD ME"
|
||||||
|
|
||||||
database:
|
database:
|
||||||
database: "_env:PG_DATABASE:start9_registry"
|
database: "_env:PG_DATABASE:start9_registry"
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import Model (
|
|||||||
Metric (Metric),
|
Metric (Metric),
|
||||||
PkgDependency (..),
|
PkgDependency (..),
|
||||||
PkgRecord (PkgRecord),
|
PkgRecord (PkgRecord),
|
||||||
VersionRecord (VersionRecord), VersionPlatform (VersionPlatform), EntityField (VersionPlatformPkgId, VersionPlatformVersionNumber, VersionPlatformArch),
|
VersionRecord (VersionRecord), VersionPlatform (VersionPlatform), EntityField (VersionPlatformPkgId, VersionPlatformVersionNumber, VersionPlatformArch), PkgRecordId,
|
||||||
)
|
)
|
||||||
import Orphans.Emver ()
|
import Orphans.Emver ()
|
||||||
import Startlude (
|
import Startlude (
|
||||||
@@ -286,14 +286,14 @@ fetchAppVersion :: MonadIO m => PkgId -> Version -> ReaderT SqlBackend m (Maybe
|
|||||||
fetchAppVersion pkgId version = get (VersionRecordKey (PkgRecordKey pkgId) version)
|
fetchAppVersion pkgId version = get (VersionRecordKey (PkgRecordKey pkgId) version)
|
||||||
|
|
||||||
|
|
||||||
fetchLatestApp :: MonadIO m => PkgId -> ReaderT SqlBackend m (Maybe (P.Entity PkgRecord, P.Entity VersionRecord))
|
fetchLatestApp :: MonadIO m => PkgRecordId -> ReaderT SqlBackend m (Maybe (P.Entity PkgRecord, P.Entity VersionRecord))
|
||||||
fetchLatestApp appId = fmap headMay . sortResults . select $ do
|
fetchLatestApp appId = fmap headMay . sortResults . select $ do
|
||||||
(service :& version) <-
|
(service :& version) <-
|
||||||
from $
|
from $
|
||||||
table @PkgRecord
|
table @PkgRecord
|
||||||
`innerJoin` table @VersionRecord
|
`innerJoin` table @VersionRecord
|
||||||
`on` (\(service :& version) -> service ^. PkgRecordId ==. version ^. VersionRecordPkgId)
|
`on` (\(service :& version) -> service ^. PkgRecordId ==. version ^. VersionRecordPkgId)
|
||||||
where_ (service ^. PkgRecordId ==. val (PkgRecordKey appId))
|
where_ (service ^. PkgRecordId ==. val appId)
|
||||||
pure (service, version)
|
pure (service, version)
|
||||||
where
|
where
|
||||||
sortResults = fmap $ sortOn (Down . versionRecordNumber . entityVal . snd)
|
sortResults = fmap $ sortOn (Down . versionRecordNumber . entityVal . snd)
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ import Lib.PkgRepository (
|
|||||||
import Lib.Types.Core (PkgId, S9PK)
|
import Lib.Types.Core (PkgId, S9PK)
|
||||||
import Model (
|
import Model (
|
||||||
Admin (..),
|
Admin (..),
|
||||||
Key (AdminKey),
|
Key (AdminKey), EntityField (AdminId, AdminDeletedAt),
|
||||||
)
|
)
|
||||||
import Settings (AppSettings (appShouldLogAll))
|
import Settings (AppSettings (appShouldLogAll))
|
||||||
import System.Console.ANSI.Codes (
|
import System.Console.ANSI.Codes (
|
||||||
@@ -150,6 +150,9 @@ import Yesod.Persist.Core (
|
|||||||
YesodPersistRunner (..),
|
YesodPersistRunner (..),
|
||||||
defaultGetDBRunner,
|
defaultGetDBRunner,
|
||||||
)
|
)
|
||||||
|
import Database.Persist ((==.))
|
||||||
|
import Database.Persist (selectFirst)
|
||||||
|
import Database.Persist (entityVal)
|
||||||
|
|
||||||
|
|
||||||
-- | The foundation datatype for your application. This can be a good place to
|
-- | The foundation datatype for your application. This can be a good place to
|
||||||
@@ -312,9 +315,11 @@ instance YesodAuth RegistryCtx where
|
|||||||
pool <- getsYesod appConnPool
|
pool <- getsYesod appConnPool
|
||||||
let checkCreds k s = flip runSqlPool pool $ do
|
let checkCreds k s = flip runSqlPool pool $ do
|
||||||
let passHash = hashWith SHA256 . encodeUtf8 . ("start9_admin:" <>) $ decodeUtf8 s
|
let passHash = hashWith SHA256 . encodeUtf8 . ("start9_admin:" <>) $ decodeUtf8 s
|
||||||
get (AdminKey $ decodeUtf8 k) <&> \case
|
selectFirst [AdminDeletedAt ==. Nothing, AdminId ==. (AdminKey $ decodeUtf8 k)] [] <&> \case
|
||||||
Nothing -> False
|
Nothing -> False
|
||||||
Just Admin{adminPassHash} -> adminPassHash == passHash
|
Just adminEntity -> do
|
||||||
|
let Admin{adminPassHash} = entityVal adminEntity
|
||||||
|
adminPassHash == passHash
|
||||||
|
|
||||||
defaultMaybeBasicAuthId checkCreds defaultAuthSettings
|
defaultMaybeBasicAuthId checkCreds defaultAuthSettings
|
||||||
loginDest _ = PackageIndexR V1
|
loginDest _ = PackageIndexR V1
|
||||||
|
|||||||
@@ -144,11 +144,13 @@ import Yesod (
|
|||||||
import Yesod.Auth (YesodAuth (maybeAuthId))
|
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)
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
withTempDirectory resourcesTemp "newpkg" $ \dir -> do
|
withTempDirectory resourcesTemp "newpkg" $ \dir -> do
|
||||||
let path = dir </> "temp" <.> "s9pk"
|
let path = dir </> "temp" <.> "s9pk"
|
||||||
@@ -159,22 +161,25 @@ postPkgUploadR = do
|
|||||||
when (isNothing res) $ do
|
when (isNothing res) $ do
|
||||||
$logError "Failed to extract package"
|
$logError "Failed to extract package"
|
||||||
sendResponseText status500 "Failed to extract package"
|
sendResponseText status500 "Failed to extract package"
|
||||||
PackageManifest{..} <-
|
PackageManifest{..} <- do
|
||||||
liftIO (decodeFileStrict (dir </> "manifest.json"))
|
liftIO (decodeFileStrict (dir </> "manifest.json"))
|
||||||
`orThrow` sendResponseText status500 "Failed to parse manifest.json"
|
`orThrow` sendResponseText status500 "Failed to parse manifest.json"
|
||||||
renameFile path (dir </> (toS . unPkgId) packageManifestId <.> "s9pk")
|
if (not $ elem packageManifestId whitelist)
|
||||||
let targetPath = pkgRepoFileRoot </> show packageManifestId </> show packageManifestVersion
|
then sendResponseText status500 "Package does not belong on this registry."
|
||||||
removePathForcibly targetPath
|
else do
|
||||||
createDirectoryIfMissing True targetPath
|
renameFile path (dir </> (toS . unPkgId) packageManifestId <.> "s9pk")
|
||||||
renameDirectory dir targetPath
|
let targetPath = pkgRepoFileRoot </> show packageManifestId </> show packageManifestVersion
|
||||||
maybeAuthId >>= \case
|
removePathForcibly targetPath
|
||||||
Nothing -> do
|
createDirectoryIfMissing True targetPath
|
||||||
$logError
|
renameDirectory dir targetPath
|
||||||
"The Impossible has happened, an unauthenticated user has managed to upload a pacakge to this registry"
|
maybeAuthId >>= \case
|
||||||
pure ()
|
Nothing -> do
|
||||||
Just name -> do
|
$logError
|
||||||
now <- liftIO getCurrentTime
|
"The Impossible has happened, an unauthenticated user has managed to upload a pacakge to this registry"
|
||||||
runDB $ insert_ (Upload (AdminKey name) (PkgRecordKey packageManifestId) packageManifestVersion now)
|
pure ()
|
||||||
|
Just name -> do
|
||||||
|
now <- liftIO getCurrentTime
|
||||||
|
runDB $ insert_ (Upload (AdminKey name) (PkgRecordKey packageManifestId) packageManifestVersion now)
|
||||||
where
|
where
|
||||||
retry m = runMaybeT . asum $ replicate 3 (MaybeT $ hush <$> try @_ @SomeException m)
|
retry m = runMaybeT . asum $ replicate 3 (MaybeT $ hush <$> try @_ @SomeException m)
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ import Startlude (
|
|||||||
Show,
|
Show,
|
||||||
Text,
|
Text,
|
||||||
Traversable (traverse),
|
Traversable (traverse),
|
||||||
catMaybes,
|
|
||||||
const,
|
const,
|
||||||
encodeUtf8,
|
encodeUtf8,
|
||||||
filter,
|
filter,
|
||||||
@@ -90,6 +89,7 @@ import Yesod (
|
|||||||
import Data.Tuple (fst)
|
import Data.Tuple (fst)
|
||||||
import Database.Persist.Postgresql (entityVal)
|
import Database.Persist.Postgresql (entityVal)
|
||||||
import Yesod.Core (getsYesod)
|
import Yesod.Core (getsYesod)
|
||||||
|
import Data.List (head)
|
||||||
|
|
||||||
data PackageReq = PackageReq
|
data PackageReq = PackageReq
|
||||||
{ packageReqId :: !PkgId
|
{ packageReqId :: !PkgId
|
||||||
@@ -187,7 +187,7 @@ getPackageDependencies osPredicate PackageMetadata{packageMetadataPkgId = pkg, p
|
|||||||
let pkgDepInfo = fmap (\a -> (entityVal $ fst a, entityVal $ snd a)) pkgDepInfo'
|
let pkgDepInfo = fmap (\a -> (entityVal $ fst a, entityVal $ snd a)) pkgDepInfo'
|
||||||
pkgDepInfoWithVersions <- traverse getDependencyVersions (fst <$> pkgDepInfo)
|
pkgDepInfoWithVersions <- traverse getDependencyVersions (fst <$> pkgDepInfo)
|
||||||
let compatiblePkgDepInfo = fmap (filter (osPredicate . versionRecordOsVersion)) pkgDepInfoWithVersions
|
let compatiblePkgDepInfo = fmap (filter (osPredicate . versionRecordOsVersion)) pkgDepInfoWithVersions
|
||||||
let depMetadata = catMaybes $ zipWith selectDependencyBestVersion pkgDepInfo compatiblePkgDepInfo
|
let depMetadata = zipWith selectDependencyBestVersion pkgDepInfo compatiblePkgDepInfo
|
||||||
lift $
|
lift $
|
||||||
fmap HM.fromList $
|
fmap HM.fromList $
|
||||||
for depMetadata $ \(depId, title, v, isLocal) -> do
|
for depMetadata $ \(depId, title, v, isLocal) -> do
|
||||||
@@ -244,13 +244,17 @@ selectLatestVersionFromSpec pkgRanges vs =
|
|||||||
|
|
||||||
|
|
||||||
-- get best version of the dependency based on what is specified in the db (ie. what is specified in the manifest for the package)
|
-- get best version of the dependency based on what is specified in the db (ie. what is specified in the manifest for the package)
|
||||||
selectDependencyBestVersion :: (PkgDependency, PkgRecord) -> [VersionRecord] -> Maybe (PkgId, Text, Version, Bool)
|
selectDependencyBestVersion :: (PkgDependency, PkgRecord) -> [VersionRecord] -> (PkgId, Text, Version, Bool)
|
||||||
selectDependencyBestVersion pkgDepInfo depVersions = do
|
selectDependencyBestVersion pkgDepInfo depVersions = do
|
||||||
let pkgDepRecord = fst pkgDepInfo
|
let pkgDepRecord = fst pkgDepInfo
|
||||||
let isLocal = pkgRecordHidden $ snd pkgDepInfo
|
let isLocal = pkgRecordHidden $ snd pkgDepInfo
|
||||||
let depId = pkgDependencyDepId pkgDepRecord
|
let depId = pkgDependencyDepId pkgDepRecord
|
||||||
let versionRequirement = pkgDependencyDepVersionRange pkgDepRecord
|
let versionRequirement = pkgDependencyDepVersionRange pkgDepRecord
|
||||||
let satisfactory = filter ((<|| versionRequirement) . versionRecordNumber) depVersions
|
let satisfactory = filter ((<|| versionRequirement) . versionRecordNumber) depVersions
|
||||||
|
let pkgId = unPkgRecordKey depId
|
||||||
case maximumOn versionRecordNumber satisfactory of
|
case maximumOn versionRecordNumber satisfactory of
|
||||||
Just bestVersion -> Just (unPkgRecordKey depId, versionRecordTitle bestVersion, versionRecordNumber bestVersion, isLocal)
|
Just bestVersion -> (pkgId, versionRecordTitle bestVersion, versionRecordNumber bestVersion, isLocal)
|
||||||
Nothing -> Nothing
|
-- use latest version of dep for metadata info
|
||||||
|
Nothing -> do
|
||||||
|
let latestDepVersion = head $ sortOn (Down . versionRecordNumber) depVersions
|
||||||
|
(pkgId, versionRecordTitle latestDepVersion, versionRecordNumber latestDepVersion, isLocal)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import Lib.PkgRepository (
|
|||||||
)
|
)
|
||||||
import Lib.Types.Core (PkgId, OsArch)
|
import Lib.Types.Core (PkgId, OsArch)
|
||||||
import Lib.Types.Emver (
|
import Lib.Types.Emver (
|
||||||
Version (Version, unVersion),
|
Version,
|
||||||
VersionRange,
|
VersionRange,
|
||||||
satisfies, parseVersion
|
satisfies, parseVersion
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ Admin
|
|||||||
Id Text
|
Id Text
|
||||||
createdAt UTCTime
|
createdAt UTCTime
|
||||||
passHash (Digest SHA256)
|
passHash (Digest SHA256)
|
||||||
|
deletedAt UTCTime Maybe
|
||||||
|
|
||||||
Upload
|
Upload
|
||||||
uploader AdminId
|
uploader AdminId
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import Startlude ( ($)
|
|||||||
, ByteString
|
, ByteString
|
||||||
, ConvertText(toS)
|
, ConvertText(toS)
|
||||||
, FilePath
|
, FilePath
|
||||||
, IsString(fromString)
|
|
||||||
, Monad(return)
|
, Monad(return)
|
||||||
, Monoid(mempty)
|
, Monoid(mempty)
|
||||||
, Text
|
, Text
|
||||||
@@ -56,6 +56,10 @@ import Lib.PkgRepository ( EosRepo(EosRepo, eosRepoFileRo
|
|||||||
)
|
)
|
||||||
import Lib.Types.Emver ( Version )
|
import Lib.Types.Emver ( Version )
|
||||||
import Orphans.Emver ( )
|
import Orphans.Emver ( )
|
||||||
|
import Lib.Types.Core (PkgId)
|
||||||
|
import Data.String
|
||||||
|
import Data.List.Extra (splitOn)
|
||||||
|
import Prelude (map, read)
|
||||||
-- | Runtime settings to configure this application. These settings can be
|
-- | Runtime settings to configure this application. These settings can be
|
||||||
-- loaded from various sources: defaults, environment variables, config files,
|
-- loaded from various sources: defaults, environment variables, config files,
|
||||||
-- theoretically even a database.
|
-- theoretically even a database.
|
||||||
@@ -75,12 +79,12 @@ data AppSettings = AppSettings
|
|||||||
-- ^ Should all log messages be displayed?
|
-- ^ Should all log messages be displayed?
|
||||||
, errorLogRoot :: !FilePath
|
, errorLogRoot :: !FilePath
|
||||||
, marketplaceName :: !Text
|
, marketplaceName :: !Text
|
||||||
, maxOsVersion :: !Version
|
, maxOsVersion :: !Version
|
||||||
, minOsVersion :: !Version
|
, minOsVersion :: !Version
|
||||||
, registryHostname :: !Text
|
, registryHostname :: !Text
|
||||||
, registryVersion :: !Version
|
, registryVersion :: !Version
|
||||||
, resourcesDir :: !FilePath
|
, resourcesDir :: !FilePath
|
||||||
, needsMigration :: !Bool
|
, needsMigration :: !Bool
|
||||||
, sslAuto :: !Bool
|
, sslAuto :: !Bool
|
||||||
, sslCertLocation :: !FilePath
|
, sslCertLocation :: !FilePath
|
||||||
, sslCsrLocation :: !FilePath
|
, sslCsrLocation :: !FilePath
|
||||||
@@ -88,6 +92,7 @@ data AppSettings = AppSettings
|
|||||||
, sslPath :: !FilePath
|
, sslPath :: !FilePath
|
||||||
, staticBinDir :: !FilePath
|
, staticBinDir :: !FilePath
|
||||||
, torPort :: !AppPort
|
, torPort :: !AppPort
|
||||||
|
, whitelist :: ![PkgId]
|
||||||
}
|
}
|
||||||
instance Has PkgRepo AppSettings where
|
instance Has PkgRepo AppSettings where
|
||||||
extract = liftA2 PkgRepo ((</> "apps") . resourcesDir) staticBinDir
|
extract = liftA2 PkgRepo ((</> "apps") . resourcesDir) staticBinDir
|
||||||
@@ -111,8 +116,8 @@ instance FromJSON AppSettings where
|
|||||||
appShouldLogAll <- o .:? "should-log-all" .!= False
|
appShouldLogAll <- o .:? "should-log-all" .!= False
|
||||||
errorLogRoot <- o .: "error-log-root"
|
errorLogRoot <- o .: "error-log-root"
|
||||||
marketplaceName <- o .: "marketplace-name"
|
marketplaceName <- o .: "marketplace-name"
|
||||||
maxOsVersion <- o .: "max-eos-version"
|
maxOsVersion <- o .: "max-eos-version"
|
||||||
minOsVersion <- o .: "min-eos-version"
|
minOsVersion <- o .: "min-eos-version"
|
||||||
registryHostname <- o .: "registry-hostname"
|
registryHostname <- o .: "registry-hostname"
|
||||||
resourcesDir <- o .: "resources-path"
|
resourcesDir <- o .: "resources-path"
|
||||||
needsMigration <- o .: "run-migration"
|
needsMigration <- o .: "run-migration"
|
||||||
@@ -120,6 +125,7 @@ instance FromJSON AppSettings where
|
|||||||
sslPath <- o .: "ssl-path"
|
sslPath <- o .: "ssl-path"
|
||||||
staticBinDir <- o .: "static-bin-dir"
|
staticBinDir <- o .: "static-bin-dir"
|
||||||
torPort <- o .: "tor-port"
|
torPort <- o .: "tor-port"
|
||||||
|
whitelist <- parseCommaSeparatedList <$> o .:? "whitelist" .!= []
|
||||||
|
|
||||||
let sslKeyLocation = sslPath </> "key.pem"
|
let sslKeyLocation = sslPath </> "key.pem"
|
||||||
let sslCsrLocation = sslPath </> "certificate.csr"
|
let sslCsrLocation = sslPath </> "certificate.csr"
|
||||||
@@ -141,3 +147,8 @@ compileTimeAppSettings :: AppSettings
|
|||||||
compileTimeAppSettings = case fromJSON $ applyEnvValue False mempty configSettingsYmlValue of
|
compileTimeAppSettings = case fromJSON $ applyEnvValue False mempty configSettingsYmlValue of
|
||||||
Error e -> panic $ toS e
|
Error e -> panic $ toS e
|
||||||
Success settings -> settings
|
Success settings -> settings
|
||||||
|
|
||||||
|
parseCommaSeparatedList :: String -> [PkgId]
|
||||||
|
parseCommaSeparatedList input = do
|
||||||
|
let strings = splitOn "," input
|
||||||
|
map read strings
|
||||||
Reference in New Issue
Block a user