Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ All notable changes to this project will be documented in this file. From versio
### Added

- Log error when `db-schemas` config contains schema `pg_catalog` or `information_schema` by @taimoorzaeem in #4359
- Add string slicing operator for `jwt-role-claim-key` by @taimoorzaeem in #4599
- Optimize requests with `Prefer: count=exact` that do not use ranges or `db-max-rows` by @laurenceisla in #3957
+ Removed unnecessary double count when building the `Content-Range`.
- Add config `client-error-verbosity` to customize error verbosity by @taimoorzaeem in #4088, #3980, #3824
Expand Down
16 changes: 0 additions & 16 deletions docs/references/auth.rst
Original file line number Diff line number Diff line change
Expand Up @@ -234,17 +234,6 @@ The DSL follows the `JSONPath <https://goessner.net/articles/JsonPath/>`_ expres
- ``==^`` selects the first array element that ends with the right operand
- ``*==`` selects the first array element that contains the right operand

The selected role value can also be sliced using the slice operator ``[a:b]``. It is similar to `slice operator in python <https://docs.python.org/3/library/functions.html#slice>`_. Negative index values are also supported. The syntax is as:

- ``[a:b]`` take slice from index ``a`` up to ``b``
- ``[a:]`` take slice from index ``a`` to end
- ``[:b]`` take slice from start to index ``b``
- ``[:]`` select everything, no slicing

.. important::

Make sure that you are not taking a slice where the start index comes after the end index like ``[11:2]``. The result of this would be empty string and so no role would get selected.

Usage examples:

.. code:: bash
Expand All @@ -266,11 +255,6 @@ Usage examples:
jwt-role-claim-key = ".postgrest.roles[?(@ ==^ \"hor\")]"
jwt-role-claim-key = ".postgrest.roles[?(@ *== \"utho\")]"

# {"postgrest":{"wlcg": ["/groupa", "/groupb/"]}}
# skip the "/" character using slice operator
jwt-role-claim-key = ".postgrest.wlcg[0][1:]"
jwt-role-claim-key = ".postgrest.wlcg[1][1:-1]"

.. note::

The string comparison operators are implemented as a custom extension to the JSPath and does not strictly follow the `RFC 9535 <https://www.rfc-editor.org/rfc/rfc9535.html>`_.
Expand Down
49 changes: 11 additions & 38 deletions src/PostgREST/Config/JSPath.hs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,9 @@ type JSPath = [JSPathExp]
-- NOTE: We only accept one JSPFilter expr (at the end of input)
-- | jspath expression
data JSPathExp
= JSPKey Text -- .property or ."property-dash"
| JSPIdx Int -- [0]
| JSPSlice (Maybe Int) (Maybe Int) -- [0:5] or [0:] or [:5] or [:]
| JSPFilter FilterExp -- [?(@ == "match")]
= JSPKey Text -- .property or ."property-dash"
| JSPIdx Int -- [0]
| JSPFilter FilterExp -- [?(@ == "match")]

data FilterExp
= EqualsCond Text
Expand All @@ -45,7 +44,6 @@ dumpJSPath :: JSPathExp -> Text
-- TODO: this needs to be quoted properly for special chars
dumpJSPath (JSPKey k) = "." <> show k
dumpJSPath (JSPIdx i) = "[" <> show i <> "]"
dumpJSPath (JSPSlice s e) = "[" <> maybe "" show s <> ":" <> maybe "" show e <> "]"
dumpJSPath (JSPFilter cond) = "[?(@" <> expr <> ")]"
where
expr =
Expand All @@ -61,25 +59,12 @@ walkJSPath :: Maybe JSON.Value -> JSPath -> Maybe JSON.Value
walkJSPath x [] = x
walkJSPath (Just (JSON.Object o)) (JSPKey key:rest) = walkJSPath (KM.lookup (K.fromText key) o) rest
walkJSPath (Just (JSON.Array ar)) (JSPIdx idx:rest) = walkJSPath (ar V.!? idx) rest
walkJSPath (Just (JSON.String str)) (JSPSlice start end:rest) =
let
len = T.length str

norm :: Maybe Int -> Maybe Int -- Normalize negative indices to positive
norm = fmap (\i -> max 0 $ min len $ if i < 0 then len + i else i)

s = fromMaybe 0 $ norm start -- normalized start index
e = fromMaybe len $ norm end -- normalized end index
slicedString = if s >= e then T.empty else T.take (e-s) $ T.drop s str
in
walkJSPath (Just $ JSON.String slicedString) rest

walkJSPath (Just (JSON.Array ar)) (JSPFilter jspFilter:rest) = case jspFilter of
EqualsCond txt -> walkJSPath (findFirstMatch (==) txt ar) rest
NotEqualsCond txt -> walkJSPath (findFirstMatch (/=) txt ar) rest
StartsWithCond txt -> walkJSPath (findFirstMatch T.isPrefixOf txt ar) rest
EndsWithCond txt -> walkJSPath (findFirstMatch T.isSuffixOf txt ar) rest
ContainsCond txt -> walkJSPath (findFirstMatch T.isInfixOf txt ar) rest
walkJSPath (Just (JSON.Array ar)) [JSPFilter jspFilter] = case jspFilter of
EqualsCond txt -> findFirstMatch (==) txt ar
NotEqualsCond txt -> findFirstMatch (/=) txt ar
StartsWithCond txt -> findFirstMatch T.isPrefixOf txt ar
EndsWithCond txt -> findFirstMatch T.isSuffixOf txt ar
ContainsCond txt -> findFirstMatch T.isInfixOf txt ar
where
findFirstMatch matchWith pattern = find (\case
JSON.String txt -> pattern `matchWith` txt
Expand All @@ -95,7 +80,7 @@ pJSPath :: P.Parser JSPath
pJSPath = P.many1 pJSPathExp <* P.eof

pJSPathExp :: P.Parser JSPathExp
pJSPathExp = P.try pJSPKey <|> P.try pJSPFilter <|> P.try pJSPIdx <|> pJSPSlice
pJSPathExp = pJSPKey <|> pJSPFilter <|> pJSPIdx

pJSPKey :: P.Parser JSPathExp
pJSPKey = do
Expand All @@ -110,25 +95,13 @@ pJSPIdx = do
P.char ']'
return (JSPIdx num) <?> "pJSPIdx: JSPath array index"

pJSPSlice :: P.Parser JSPathExp
pJSPSlice = do
P.char '['
startSign <- P.optionMaybe $ P.char '-'
startIndex <- P.optionMaybe (read <$> P.many1 P.digit)
P.char ':'
endSign <- P.optionMaybe $ P.char '-'
endIndex <- P.optionMaybe (read <$> P.many1 P.digit)
P.char ']'
let start' = if isJust startSign then ((-1) *) <$> startIndex else startIndex
end' = if isJust endSign then ((-1) *) <$> endIndex else endIndex
return (JSPSlice start' end') <?> "pJSPSlice: JSPath string slice"

pJSPFilter :: P.Parser JSPathExp
pJSPFilter = do
P.try $ P.string "[?("
condition <- pFilterConditionParser
P.char ')'
P.char ']'
P.eof -- this should be the last jspath expression
return (JSPFilter condition) <?> "pJSPFilter: JSPath filter exp"

pFilterConditionParser :: P.Parser FilterExp
Expand Down
38 changes: 0 additions & 38 deletions test/io/fixtures/fixtures.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -194,44 +194,6 @@ roleclaims:
roles:
- obj_key: obj_value
expected_status: 401 # fails because it compares an object with a string
- key: '.realm_access.roles[0][7:]'
data:
realm_access:
roles:
- prefix_postgrest_test_author
expected_status: 200 # passes because it removes the "prefix_" part using slice
- key: '.realm_access.roles[0][:-7]'
data:
realm_access:
roles:
- postgrest_test_author_suffix
expected_status: 200 # passes because it removes the "_suffix" part using slice
- key: '.realm_access.roles[0][7:-7]'
data:
realm_access:
roles:
- prefix_postgrest_test_author_suffix
expected_status: 200 # passes because it removes the "prefix_" and "_suffix" part using slice
- key: '.realm_access.roles[0][:]'
data:
realm_access:
roles:
- postgrest_test_author
expected_status: 200 # passes because nothing gets sliced
- key: '.realm_access.roles[?(@ *== "_test_")][7:]'
data:
realm_access:
roles:
- other
- prefix_postgrest_test_author
expected_status: 200 # passes on both comparison operators and slicing
- key: '.realm_access.roles[?(@ *== "_test_")][200:]'
data:
realm_access:
roles:
- other
- prefix_postgrest_test_author
expected_status: 401 # fails due to slicing results in empty string

jwtaudroleclaims:
- key: '.aud'
Expand Down