Skip to content

Commit 905766f

Browse files
author
Benjamin Geer
authored
fix(api-v2): Don't check file extensions of XSL files and Gravsearch templates (DSP-1005) (#1749)
1 parent 43ff213 commit 905766f

5 files changed

Lines changed: 42 additions & 24 deletions

File tree

‎.github/workflows/main.yml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -315,9 +315,9 @@ jobs:
315315
env:
316316
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
317317

318-
# publish only on release
318+
# publish on merge to main
319319
publish:
320-
name: Publish (on release only)
320+
name: Publish (on merge to main)
321321
needs: [
322322
api-unit-tests,
323323
api-e2e-tests,
@@ -326,7 +326,7 @@ jobs:
326326
docs-build-test
327327
]
328328
runs-on: ubuntu-latest
329-
if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags')
329+
if: github.ref == 'refs/heads/main'
330330
steps:
331331
- uses: actions/checkout@v1
332332
with:

‎webapi/src/main/scala/org/knora/webapi/responders/v2/ResourcesResponderV2.scala‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,20 +1336,20 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
13361336
throw BadRequestException(s"Resource $gravsearchTemplateIri is not a Gravsearch template (text file expected)")
13371337
}
13381338

1339-
gravsearchFileValueContent: TextFileValueContentV2 = resource.values.get(OntologyConstants.KnoraBase.HasTextFileValue.toSmartIri) match {
1339+
(fileValueIri: IRI, gravsearchFileValueContent: TextFileValueContentV2) = resource.values.get(OntologyConstants.KnoraBase.HasTextFileValue.toSmartIri) match {
13401340
case Some(values: Seq[ReadValueV2]) if values.size == 1 => values.head match {
13411341
case value: ReadValueV2 => value.valueContent match {
1342-
case textRepr: TextFileValueContentV2 => textRepr
1342+
case textRepr: TextFileValueContentV2 => (value.valueIri, textRepr)
13431343
case _ => throw InconsistentTriplestoreDataException(s"Resource $gravsearchTemplateIri is supposed to have exactly one value of type ${OntologyConstants.KnoraBase.TextFileValue}")
13441344
}
13451345
}
13461346

13471347
case None => throw InconsistentTriplestoreDataException(s"Resource $gravsearchTemplateIri has no property ${OntologyConstants.KnoraBase.HasTextFileValue}")
13481348
}
13491349

1350-
// check if `xsltFileValue` represents an XSL transformation
1351-
_ = if (!(gravsearchFileValueContent.fileValue.internalMimeType == "text/plain" && gravsearchFileValueContent.fileValue.internalFilename.endsWith(".txt"))) {
1352-
throw BadRequestException(s"Resource $gravsearchTemplateIri does not have a file value referring to a Gravsearch template")
1350+
// check if gravsearchFileValueContent represents a text file
1351+
_ = if (gravsearchFileValueContent.fileValue.internalMimeType != "text/plain") {
1352+
throw BadRequestException(s"Expected $fileValueIri to be a text file referring to a Gravsearch template, but it has MIME type ${gravsearchFileValueContent.fileValue.internalMimeType}")
13531353
}
13541354

13551355
gravsearchUrl: String = s"${settings.internalSipiBaseUrl}/${resource.projectADM.shortcode}/${gravsearchFileValueContent.fileValue.internalFilename}/file"

‎webapi/src/main/scala/org/knora/webapi/responders/v2/StandoffResponderV2.scala‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -173,20 +173,20 @@ class StandoffResponderV2(responderData: ResponderData) extends Responder(respon
173173
throw BadRequestException(s"Resource $xslTransformationIri is not a ${OntologyConstants.KnoraBase.XSLTransformation}")
174174
}
175175

176-
xsltFileValueContent: TextFileValueContentV2 = resource.values.get(OntologyConstants.KnoraBase.HasTextFileValue.toSmartIri) match {
176+
(fileValueIri: IRI, xsltFileValueContent: TextFileValueContentV2) = resource.values.get(OntologyConstants.KnoraBase.HasTextFileValue.toSmartIri) match {
177177
case Some(values: Seq[ReadValueV2]) if values.size == 1 => values.head match {
178178
case value: ReadValueV2 => value.valueContent match {
179-
case textRepr: TextFileValueContentV2 => textRepr
179+
case textRepr: TextFileValueContentV2 => (value.valueIri, textRepr)
180180
case _ => throw InconsistentTriplestoreDataException(s"${OntologyConstants.KnoraBase.XSLTransformation} $xslTransformationIri is supposed to have exactly one value of type ${OntologyConstants.KnoraBase.TextFileValue}")
181181
}
182182
}
183183

184184
case None => throw InconsistentTriplestoreDataException(s"${OntologyConstants.KnoraBase.XSLTransformation} has no property ${OntologyConstants.KnoraBase.HasTextFileValue}")
185185
}
186186

187-
// check if xsltFileValue represents an XSL transformation
188-
_ = if (!(xmlMimeTypes.contains(xsltFileValueContent.fileValue.internalMimeType) && xsltFileValueContent.fileValue.internalFilename.endsWith(".xsl"))) {
189-
throw BadRequestException(s"$xslTransformationIri does not have a file value referring to an XSL transformation")
187+
// check if xsltFileValueContent represents an XSL transformation
188+
_ = if (!xmlMimeTypes.contains(xsltFileValueContent.fileValue.internalMimeType)) {
189+
throw BadRequestException(s"Expected $fileValueIri to be an XML file referring to an XSL transformation, but it has MIME type ${xsltFileValueContent.fileValue.internalMimeType}")
190190
}
191191

192192
xsltUrl: String = s"${settings.internalSipiBaseUrl}/${resource.projectADM.shortcode}/${xsltFileValueContent.fileValue.internalFilename}/file"

‎webapi/src/main/scala/org/knora/webapi/routing/v2/SearchRouteV2.scala‎

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import org.knora.webapi.feature.FeatureFactoryConfig
2727
import org.knora.webapi.messages.IriConversions._
2828
import org.knora.webapi.messages.util.search.gravsearch.GravsearchParser
2929
import org.knora.webapi.messages.v2.responder.searchmessages._
30-
import org.knora.webapi.messages.{SmartIri, StringFormatter}
30+
import org.knora.webapi.messages.{OntologyConstants, SmartIri, StringFormatter}
3131
import org.knora.webapi.routing.{Authenticator, KnoraRoute, KnoraRouteData, RouteUtilV2}
3232

3333
import scala.concurrent.Future
@@ -152,13 +152,17 @@ class SearchRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit
152152
}
153153
}
154154

155-
private def fullTextSearchCount(featureFactoryConfig: FeatureFactoryConfig): Route = path("v2" / "search" / "count" / Segment) { searchval => // TODO: if a space is encoded as a "+", this is not converted back to a space
155+
private def fullTextSearchCount(featureFactoryConfig: FeatureFactoryConfig): Route = path("v2" / "search" / "count" / Segment) { searchStr => // TODO: if a space is encoded as a "+", this is not converted back to a space
156156
get {
157157
requestContext =>
158-
val searchString = stringFormatter.toSparqlEncodedString(searchval, throw BadRequestException(s"Invalid search string: '$searchval'"))
158+
if (searchStr.contains(OntologyConstants.KnoraApi.ApiOntologyHostname)) {
159+
throw BadRequestException("It looks like you are submitting a Gravsearch request to a full-text search route")
160+
}
159161

160-
if (searchString.length < settings.searchValueMinLength) {
161-
throw BadRequestException(s"A search value is expected to have at least length of ${settings.searchValueMinLength}, but '$searchString' given of length ${searchString.length}.")
162+
val escapedSearchStr = stringFormatter.toSparqlEncodedString(searchStr, throw BadRequestException(s"Invalid search string: '$searchStr'"))
163+
164+
if (escapedSearchStr.length < settings.searchValueMinLength) {
165+
throw BadRequestException(s"A search value is expected to have at least length of ${settings.searchValueMinLength}, but '$escapedSearchStr' given of length ${escapedSearchStr.length}.")
162166
}
163167

164168
val params: Map[String, String] = requestContext.request.uri.query().toMap
@@ -172,7 +176,7 @@ class SearchRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit
172176
val requestMessage: Future[FullTextSearchCountRequestV2] = for {
173177
requestingUser <- getUserADM(requestContext)
174178
} yield FullTextSearchCountRequestV2(
175-
searchValue = searchString,
179+
searchValue = escapedSearchStr,
176180
limitToProject = limitToProject,
177181
limitToResourceClass = limitToResourceClass,
178182
limitToStandoffClass = limitToStandoffClass,
@@ -192,13 +196,17 @@ class SearchRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit
192196
}
193197
}
194198

195-
private def fullTextSearch(featureFactoryConfig: FeatureFactoryConfig): Route = path("v2" / "search" / Segment) { searchval => // TODO: if a space is encoded as a "+", this is not converted back to a space
199+
private def fullTextSearch(featureFactoryConfig: FeatureFactoryConfig): Route = path("v2" / "search" / Segment) { searchStr => // TODO: if a space is encoded as a "+", this is not converted back to a space
196200
get {
197201
requestContext => {
198-
val searchString = stringFormatter.toSparqlEncodedString(searchval, throw BadRequestException(s"Invalid search string: '$searchval'"))
202+
if (searchStr.contains(OntologyConstants.KnoraApi.ApiOntologyHostname)) {
203+
throw BadRequestException("It looks like you are submitting a Gravsearch request to a full-text search route")
204+
}
199205

200-
if (searchString.length < settings.searchValueMinLength) {
201-
throw BadRequestException(s"A search value is expected to have at least length of ${settings.searchValueMinLength}, but '$searchString' given of length ${searchString.length}.")
206+
val escapedSearchStr = stringFormatter.toSparqlEncodedString(searchStr, throw BadRequestException(s"Invalid search string: '$searchStr'"))
207+
208+
if (escapedSearchStr.length < settings.searchValueMinLength) {
209+
throw BadRequestException(s"A search value is expected to have at least length of ${settings.searchValueMinLength}, but '$escapedSearchStr' given of length ${escapedSearchStr.length}.")
202210
}
203211

204212
val params: Map[String, String] = requestContext.request.uri.query().toMap
@@ -217,7 +225,7 @@ class SearchRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit
217225
val requestMessage: Future[FulltextSearchRequestV2] = for {
218226
requestingUser <- getUserADM(requestContext)
219227
} yield FulltextSearchRequestV2(
220-
searchValue = searchString,
228+
searchValue = escapedSearchStr,
221229
offset = offset,
222230
limitToProject = limitToProject,
223231
limitToResourceClass = limitToResourceClass,

‎webapi/src/test/scala/org/knora/webapi/e2e/v2/SearchRouteV2R2RSpec.scala‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,16 @@ class SearchRouteV2R2RSpec extends R2RSpec {
198198
}
199199
}
200200

201+
"not accept a fulltext query containing http://api.knora.org" in {
202+
val invalidSearchString: String = URLEncoder.encode("PREFIX knora-api: <http://api.knora.org/ontology/knora-api/v2#>", "UTF-8")
203+
204+
Get(s"/v2/search/$invalidSearchString") ~> searchPath ~> check {
205+
val responseStr = responseAs[String]
206+
assert(status == StatusCodes.BAD_REQUEST, responseStr)
207+
assert(responseStr.contains("It looks like you are submitting a Gravsearch request to a full-text search route"))
208+
}
209+
}
210+
201211
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
202212
// Queries without type inference
203213

0 commit comments

Comments
 (0)