-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
V3 casl 561 query tags #370
Conversation
Signed-off-by: Jakub Amanowicz <[email protected]>
Signed-off-by: Jakub Amanowicz <[email protected]>
ad7816f
to
c1bd5bd
Compare
Signed-off-by: Jakub Amanowicz <[email protected]>
c1bd5bd
to
0e50edd
Compare
@@ -160,6 +161,80 @@ class WhereClauseBuilder(private val request: ReadFeatures) { | |||
} | |||
} | |||
|
|||
private fun whereTags() { | |||
request.query.tags?.let { tagQuery -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why .let ? Maybe I'm old-fashioned, but it looks a little weird, as I understand it replaces if(tags != null)
, but I had to read it twice to understand :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you are totally right, this is not clear - will change
Signed-off-by: Jakub Amanowicz <[email protected]>
here-naksha-lib-model/src/commonMain/kotlin/naksha/model/Tag.kt
Outdated
Show resolved
Hide resolved
* | ||
* By default, (if no special prefix is found) tag is normalized with NFD, lowercased, cleaned of non-ASCII and splittable. | ||
*/ | ||
object TagNormalizer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please do not use object
, I removed it everywhere. If this should be a singleton, use the following syntax:
@JsExport
class TagNormalizer private constructor() {
companion object TagNormalizer_C {
@JvmStatic
@JsStatic
fun foo() {}
}
}
The reason are plenty of bugs in the JavaScript compiler/linker combination. Sadly, the compiler (for objects) automatically names the companion object, and this naming does not work very well, when used across different modules, because somehow the linker messes up naming. We need to open a ticket with Jetbrains about it, but I would first want to have a working code, then to show the issues in the working code, so they can simply checkout, compile and debug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
here-naksha-lib-model/src/commonMain/kotlin/naksha/model/TagNormalizer.kt
Outdated
Show resolved
Hide resolved
here-naksha-lib-model/src/commonMain/kotlin/naksha/model/TagNormalizer.kt
Outdated
Show resolved
Hide resolved
here-naksha-lib-model/src/commonMain/kotlin/naksha/model/TagNormalizer.kt
Show resolved
Hide resolved
here-naksha-lib-psql/src/commonMain/kotlin/naksha/psql/executors/query/WhereClauseBuilder.kt
Outdated
Show resolved
Hide resolved
Signed-off-by: Jakub Amanowicz <[email protected]>
val i = normalizedTag.indexOf('=') | ||
val key: String | ||
val value: Any? | ||
if (i > 1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will fail for a=1
, because in this case index will be 1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch, fixed & tested
private fun resolveSingleTagQuery(tagQuery: TagQuery) { | ||
when (tagQuery) { | ||
is TagExists -> { | ||
where.append("$tagsAsJsonb ?? '${tagQuery.name}'") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is prone to SQL injections and ASCII zero injections. Values that are provided by the client, like tag-names, must only be used in prepared-statements! You can quote this alternatively, still, it may have a bad impact on performance, when it is executed multiple times, because it can't be cached by the query parser (very likely does not matter in this case, because tag queries will be slow anyway).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very well spotted again - fixed & tested.
Currently, all values from queries (as well as already persisted tags) are used via prepared statement
} | ||
|
||
is TagValueIsNull -> { | ||
where.append("${tagValue(tagQuery)} = null") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That does not work, it need to be something IS NULL
, NULL is like NaN, any value compared against it will be false (well, not exactly, but it is good to remember it like this).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 fixed
|
||
is TagValueMatches -> { | ||
val regex = tagQuery.regex | ||
where.append("$tagsAsJsonb @?? '\$.${tagQuery.name} ? (@ like_regex \"${regex}\")'") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, this is prone to SQL and ASCII zero injections, because not correctly escaped (remember, tags that start with @
allow all unicode characters! For regular expression, we should review carefully, if there is no way of code execution or query manipulation.
return when (castTo) { | ||
null -> "$tagsAsJsonb->'${tagQuery.name}'" | ||
PgType.STRING -> "$tagsAsJsonb->>'${tagQuery.name}'" | ||
else -> "($tagsAsJsonb->'${tagQuery.name}')::${castTo.value}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of this prone to SQL injections and ASCII zero attacks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrapped this in prepared statement as so:
private fun selectTagValue(tagQuery: TagQuery, castTo: PgType? = null): String {
val tagKeyPlaceholder = placeholderForArg(tagQuery.name, PgType.STRING)
return when (castTo) {
null -> "$tagsAsJsonb->$tagKeyPlaceholder"
PgType.STRING -> "$tagsAsJsonb->>$tagKeyPlaceholder"
else -> "($tagsAsJsonb->$tagKeyPlaceholder)::${castTo.value}"
}
}
The tag key will be wrapped as $N
and replaced in prepared statement.
The tagsAsJsonb
alone is safe:
companion object {
private val tagsAsJsonb = "naksha_tags(${PgColumn.tags}, ${PgColumn.flags})"
}```
Signed-off-by: Jakub Amanowicz <[email protected]>
a86d3fd
to
516c8e5
Compare
Signed-off-by: Jakub Amanowicz <[email protected]>
* | ||
* By default, (if no special prefix is found) tag is normalized with NFD, lowercased, cleaned of non-ASCII and splittable. | ||
*/ | ||
class TagNormalizer private constructor() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As this class is part of lib-models
, and it is a public class, it must be exposed to javascript via @JsExport
annotation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
/** | ||
* Main method for normalized tag splitting. See[TagNormalizer] doc for more | ||
*/ | ||
fun splitNormalizedTag(normalizedTag: String): Pair<String, Any?> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pair is not exportable to JS, do we need to keep the method public? If not, just make it private or internal, this should solve the issue I would say. Otherwise, you can use kotlin.collections.Map.Entry
or its implementation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
internal
should do the trick - changed
Signed-off-by: Jakub Amanowicz <[email protected]>
No description provided.