restrict openapi spec based on sql grants
This commit is contained in:
@@ -27,8 +27,9 @@ import PostgREST.Config (AppConfig (..), Proxy (..),
|
||||
isMalformedProxyUri, toURI)
|
||||
import PostgREST.MediaType
|
||||
import PostgREST.Network (escapeHostName)
|
||||
import PostgREST.Query.OpenApi (TableAccess (..), TablesAccess)
|
||||
import PostgREST.SchemaCache (SchemaCache (..))
|
||||
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..))
|
||||
import PostgREST.SchemaCache.Identifiers (FieldName, QualifiedIdentifier (..))
|
||||
import PostgREST.SchemaCache.Relationship (Cardinality (..), Relationship (..),
|
||||
RelationshipsMap)
|
||||
import PostgREST.SchemaCache.Routine (FuncVolatility (..), Routine (..),
|
||||
@@ -38,18 +39,27 @@ import PostgREST.SchemaCache.Table (Column (..), Table (..), TablesMap,
|
||||
|
||||
import Protolude hiding (Proxy, get)
|
||||
|
||||
encode :: (Text, Text) -> AppConfig -> SchemaCache -> TablesMap -> HM.HashMap k [Routine] -> Maybe Text -> LBS.ByteString
|
||||
encode versions conf sCache tables procs schemaDescription =
|
||||
encode :: (Text, Text) -> AppConfig -> SchemaCache -> TablesMap -> TablesAccess -> HM.HashMap k [Routine] -> Maybe Text -> LBS.ByteString
|
||||
encode versions conf sCache tables access procs schemaDescription =
|
||||
JSON.encode $
|
||||
postgrestSpec
|
||||
versions
|
||||
(dbRelationships sCache)
|
||||
(concat $ HM.elems procs)
|
||||
(snd <$> HM.toList tables)
|
||||
(fmap (\(_, t) -> (t, accessFor access t)) (HM.toList tables))
|
||||
(proxyUri conf)
|
||||
schemaDescription
|
||||
(configOpenApiSecurityActive conf)
|
||||
|
||||
-- | Get the access privileges for a table. When the table is not present in the
|
||||
-- map(ignore-privileges mode), assume the role has full access to it.
|
||||
accessFor :: TablesAccess -> Table -> TableAccess
|
||||
accessFor access t =
|
||||
fromMaybe fullAccess (HM.lookup (QualifiedIdentifier (tableSchema t) (tableName t)) access)
|
||||
where
|
||||
fullAccess = TableAccess allCols allCols allCols True
|
||||
allCols = colName <$> tableColumnsList t
|
||||
|
||||
makeMimeList :: [MediaType] -> MimeList
|
||||
makeMimeList cs = MimeList $ fmap (fromString . BS.unpack . toMime) cs
|
||||
|
||||
@@ -97,14 +107,19 @@ parseDefault colType colDefault =
|
||||
where
|
||||
wrapInQuotations text = "\"" <> text <> "\""
|
||||
|
||||
makeTableDef :: RelationshipsMap -> Table -> (Text, Schema)
|
||||
makeTableDef rels t =
|
||||
let tn = tableName t in
|
||||
(tn, (mempty :: Schema)
|
||||
& description .~ tableDescription t
|
||||
& type_ ?~ SwaggerObject
|
||||
& properties .~ fromList (makeProperty t rels <$> tableColumnsList t)
|
||||
& required .~ fmap colName (filter (not . colNullable) $ tableColumnsList t))
|
||||
makeTableDef :: RelationshipsMap -> (Table, TableAccess) -> (Text, Schema)
|
||||
makeTableDef rels (t, access) =
|
||||
(tn, (mempty :: Schema)
|
||||
& description .~ tableDescription t
|
||||
& type_ ?~ SwaggerObject
|
||||
& properties .~ fromList (makeProperty t rels <$> cols)
|
||||
& required .~ fmap colName (filter (not . colNullable) cols))
|
||||
where
|
||||
tn = tableName t
|
||||
cols = accessibleCols t (taSelectCols access)
|
||||
|
||||
accessibleCols :: Table -> [FieldName] -> [Column]
|
||||
accessibleCols t cols = filter ((`elem` cols) . colName) (tableColumnsList t)
|
||||
|
||||
makeProperty :: Table -> RelationshipsMap -> Column -> (Text, Referenced Schema)
|
||||
makeProperty tbl rels col = (colName col, Inline s)
|
||||
@@ -222,8 +237,8 @@ makeProcPostParams pd =
|
||||
, Ref $ Reference "preferParams"
|
||||
]
|
||||
|
||||
makeParamDefs :: [Table] -> [(Text, Param)]
|
||||
makeParamDefs ti =
|
||||
makeParamDefs :: RelationshipsMap -> [(Table, TableAccess)] -> [(Text, Param)]
|
||||
makeParamDefs rels tis =
|
||||
-- TODO: create Prefer for each method (GET, PATCH, etc.)
|
||||
[ ("preferParams", makePreferParam ["params"])
|
||||
, ("preferReturn", makePreferParam ["return"])
|
||||
@@ -280,17 +295,27 @@ makeParamDefs ti =
|
||||
& in_ .~ ParamQuery
|
||||
& type_ ?~ SwaggerString))
|
||||
]
|
||||
<> concat [ makeObjectBody (tableName t) : makeRowFilters (tableName t) (tableColumnsList t)
|
||||
| t <- ti
|
||||
<> concat [ makeObjectBody rels t access <> makeRowFilters (tableName t) (accessibleCols t (taSelectCols access))
|
||||
| (t, access) <- tis
|
||||
]
|
||||
|
||||
makeObjectBody :: Text -> (Text, Param)
|
||||
makeObjectBody tn =
|
||||
("body." <> tn, (mempty :: Param)
|
||||
& name .~ tn
|
||||
& description ?~ tn
|
||||
& required ?~ False
|
||||
& schema .~ ParamBody (Ref (Reference tn)))
|
||||
makeObjectBody :: RelationshipsMap -> Table -> TableAccess -> [(Text, Param)]
|
||||
makeObjectBody rels t access =
|
||||
[ ("body." <> tn, makeBodyParam (taInsertCols access))
|
||||
, ("body." <> tn <> ".patch", makeBodyParam (taUpdateCols access))
|
||||
]
|
||||
where
|
||||
tn = tableName t
|
||||
makeBodyParam cols = (mempty :: Param)
|
||||
& name .~ tn
|
||||
& description ?~ tn
|
||||
& required ?~ False
|
||||
& schema .~ ParamBody (Inline bodySchema)
|
||||
where
|
||||
bodySchema = (mempty :: Schema)
|
||||
& type_ ?~ SwaggerObject
|
||||
& properties .~ fromList (makeProperty t rels <$> accessibleCols t cols)
|
||||
& required .~ fmap colName (filter (not . colNullable) (accessibleCols t cols))
|
||||
|
||||
makeRowFilter :: Text -> Column -> (Text, Param)
|
||||
makeRowFilter tn c =
|
||||
@@ -305,8 +330,8 @@ makeRowFilter tn c =
|
||||
makeRowFilters :: Text -> [Column] -> [(Text, Param)]
|
||||
makeRowFilters tn = fmap (makeRowFilter tn)
|
||||
|
||||
makePathItem :: Table -> (FilePath, PathItem)
|
||||
makePathItem t = ("/" ++ T.unpack tn, p $ tableInsertable t || tableUpdatable t || tableDeletable t)
|
||||
makePathItem :: (Table, TableAccess) -> (FilePath, PathItem)
|
||||
makePathItem (t, access) = ("/" ++ T.unpack tn, p)
|
||||
where
|
||||
-- Use first line of table description as summary; rest as description (if present)
|
||||
-- We strip leading newlines from description so that users can include a blank line between summary and description
|
||||
@@ -327,20 +352,26 @@ makePathItem t = ("/" ++ T.unpack tn, p $ tableInsertable t || tableUpdatable t
|
||||
)
|
||||
)
|
||||
postOp = tOp
|
||||
& parameters .~ fmap ref ["body." <> tn, "select", "preferPost"]
|
||||
& parameters .~ fmap ref [bodyParam, "select", "preferPost"]
|
||||
& at 201 ?~ "Created"
|
||||
patchOp = tOp
|
||||
& parameters .~ fmap ref (rs <> ["body." <> tn, "preferReturn"])
|
||||
& parameters .~ fmap ref (rs <> [patchBodyParam, "preferReturn"])
|
||||
& at 204 ?~ "No Content"
|
||||
deletOp = tOp
|
||||
& parameters .~ fmap ref (rs <> ["preferReturn"])
|
||||
& at 204 ?~ "No Content"
|
||||
pr = (mempty :: PathItem) & get ?~ getOp
|
||||
pw = pr & post ?~ postOp & patch ?~ patchOp & delete ?~ deletOp
|
||||
p False = pr
|
||||
p True = pw
|
||||
p = (mempty :: PathItem)
|
||||
& get .~ (if not (null selCols) then Just getOp else Nothing)
|
||||
& post .~ (if tableInsertable t && not (null insCols) then Just postOp else Nothing)
|
||||
& patch .~ (if tableUpdatable t && not (null updCols) then Just patchOp else Nothing)
|
||||
& delete .~ (if tableDeletable t && taDelete access then Just deletOp else Nothing)
|
||||
tn = tableName t
|
||||
rs = [ T.intercalate "." ["rowFilter", tn, colName c ] | c <- tableColumnsList t ]
|
||||
selCols = accessibleCols t (taSelectCols access)
|
||||
insCols = accessibleCols t (taInsertCols access)
|
||||
updCols = accessibleCols t (taUpdateCols access)
|
||||
rs = [ T.intercalate "." ["rowFilter", tn, colName c ] | c <- selCols ]
|
||||
bodyParam = "body." <> tn
|
||||
patchBodyParam = "body." <> tn <> ".patch"
|
||||
ref = Ref . Reference
|
||||
|
||||
makeProcPathItem :: Routine -> (FilePath, PathItem)
|
||||
@@ -375,9 +406,9 @@ makeRootPathItem = ("/", p)
|
||||
pr = (mempty :: PathItem) & get ?~ getOp
|
||||
p = pr
|
||||
|
||||
makePathItems :: [Routine] -> [Table] -> InsOrdHashMap FilePath PathItem
|
||||
makePathItems pds ti = fromList $ makeRootPathItem :
|
||||
fmap makePathItem ti ++ fmap makeProcPathItem pds
|
||||
makePathItems :: [Routine] -> [(Table, TableAccess)] -> InsOrdHashMap FilePath PathItem
|
||||
makePathItems pds tis = fromList $ makeRootPathItem :
|
||||
fmap makePathItem tis ++ fmap makeProcPathItem pds
|
||||
|
||||
makeSecurityDefinitions :: Text -> Bool -> SecurityDefinitions
|
||||
makeSecurityDefinitions secName allow
|
||||
@@ -387,8 +418,8 @@ makeSecurityDefinitions secName allow
|
||||
secSchType = SecuritySchemeApiKey (ApiKeyParams "Authorization" ApiKeyHeader)
|
||||
secSchDescription = Just "Add the token prepending \"Bearer \" (without quotes) to it"
|
||||
|
||||
postgrestSpec :: (Text, Text) -> RelationshipsMap -> [Routine] -> [Table] -> (Text, Text, Integer, Text) -> Maybe Text -> Bool -> Swagger
|
||||
postgrestSpec (prettyVersion, docsVersion) rels pds ti (s, h, p, b) sd allowSecurityDef = (mempty :: Swagger)
|
||||
postgrestSpec :: (Text, Text) -> RelationshipsMap -> [Routine] -> [(Table, TableAccess)] -> (Text, Text, Integer, Text) -> Maybe Text -> Bool -> Swagger
|
||||
postgrestSpec (prettyVersion, docsVersion) rels pds tis (s, h, p, b) sd allowSecurityDef = (mempty :: Swagger)
|
||||
& basePath ?~ T.unpack b
|
||||
& schemes ?~ [s']
|
||||
& info .~ ((mempty :: Info)
|
||||
@@ -399,9 +430,9 @@ postgrestSpec (prettyVersion, docsVersion) rels pds ti (s, h, p, b) sd allowSecu
|
||||
& description ?~ "PostgREST Documentation"
|
||||
& url .~ URL ("https://postgrest.org/en/" <> docsVersion <> "/references/api.html"))
|
||||
& host .~ h'
|
||||
& definitions .~ fromList (makeTableDef rels <$> ti)
|
||||
& parameters .~ fromList (makeParamDefs ti)
|
||||
& paths .~ makePathItems pds ti
|
||||
& definitions .~ fromList (makeTableDef rels <$> tis)
|
||||
& parameters .~ fromList (makeParamDefs rels tis)
|
||||
& paths .~ makePathItems pds tis
|
||||
& produces .~ makeMimeList [MTApplicationJSON, MTVndSingularJSON True, MTVndSingularJSON False, MTTextCSV]
|
||||
& consumes .~ makeMimeList [MTApplicationJSON, MTVndSingularJSON True, MTVndSingularJSON False, MTTextCSV]
|
||||
& securityDefinitions .~ makeSecurityDefinitions securityDefName allowSecurityDef
|
||||
|
||||
Reference in New Issue
Block a user