Skip to content
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

fix(api-v2): Don't check file extensions of XSL files and Gravsearch templates (DSP-1005) #1749

Merged
merged 6 commits into from Nov 5, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/main.yml
Expand Up @@ -315,9 +315,9 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}

# publish only on release
# publish on merge to main
publish:
name: Publish (on release only)
name: Publish (on merge to main)
needs: [
api-unit-tests,
api-e2e-tests,
Expand All @@ -326,7 +326,7 @@ jobs:
docs-build-test
]
runs-on: ubuntu-latest
if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags')
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v1
with:
Expand Down
Expand Up @@ -1336,20 +1336,20 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt
throw BadRequestException(s"Resource $gravsearchTemplateIri is not a Gravsearch template (text file expected)")
}

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

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

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

gravsearchUrl: String = s"${settings.internalSipiBaseUrl}/${resource.projectADM.shortcode}/${gravsearchFileValueContent.fileValue.internalFilename}/file"
Expand Down
Expand Up @@ -173,20 +173,20 @@ class StandoffResponderV2(responderData: ResponderData) extends Responder(respon
throw BadRequestException(s"Resource $xslTransformationIri is not a ${OntologyConstants.KnoraBase.XSLTransformation}")
}

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

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

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

xsltUrl: String = s"${settings.internalSipiBaseUrl}/${resource.projectADM.shortcode}/${xsltFileValueContent.fileValue.internalFilename}/file"
Expand Down
Expand Up @@ -27,7 +27,7 @@ import org.knora.webapi.feature.FeatureFactoryConfig
import org.knora.webapi.messages.IriConversions._
import org.knora.webapi.messages.util.search.gravsearch.GravsearchParser
import org.knora.webapi.messages.v2.responder.searchmessages._
import org.knora.webapi.messages.{SmartIri, StringFormatter}
import org.knora.webapi.messages.{OntologyConstants, SmartIri, StringFormatter}
import org.knora.webapi.routing.{Authenticator, KnoraRoute, KnoraRouteData, RouteUtilV2}

import scala.concurrent.Future
Expand Down Expand Up @@ -152,13 +152,17 @@ class SearchRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit
}
}

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
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
get {
requestContext =>
val searchString = stringFormatter.toSparqlEncodedString(searchval, throw BadRequestException(s"Invalid search string: '$searchval'"))
if (searchStr.contains(OntologyConstants.KnoraApi.ApiOntologyHostname)) {
throw BadRequestException("It looks like you are submitting a Gravsearch request to a full-text search route")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hahaha, I love this!

}

if (searchString.length < settings.searchValueMinLength) {
throw BadRequestException(s"A search value is expected to have at least length of ${settings.searchValueMinLength}, but '$searchString' given of length ${searchString.length}.")
val escapedSearchStr = stringFormatter.toSparqlEncodedString(searchStr, throw BadRequestException(s"Invalid search string: '$searchStr'"))

if (escapedSearchStr.length < settings.searchValueMinLength) {
throw BadRequestException(s"A search value is expected to have at least length of ${settings.searchValueMinLength}, but '$escapedSearchStr' given of length ${escapedSearchStr.length}.")
}

val params: Map[String, String] = requestContext.request.uri.query().toMap
Expand All @@ -172,7 +176,7 @@ class SearchRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit
val requestMessage: Future[FullTextSearchCountRequestV2] = for {
requestingUser <- getUserADM(requestContext)
} yield FullTextSearchCountRequestV2(
searchValue = searchString,
searchValue = escapedSearchStr,
limitToProject = limitToProject,
limitToResourceClass = limitToResourceClass,
limitToStandoffClass = limitToStandoffClass,
Expand All @@ -192,13 +196,17 @@ class SearchRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit
}
}

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
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
get {
requestContext => {
val searchString = stringFormatter.toSparqlEncodedString(searchval, throw BadRequestException(s"Invalid search string: '$searchval'"))
if (searchStr.contains(OntologyConstants.KnoraApi.ApiOntologyHostname)) {
throw BadRequestException("It looks like you are submitting a Gravsearch request to a full-text search route")
}

if (searchString.length < settings.searchValueMinLength) {
throw BadRequestException(s"A search value is expected to have at least length of ${settings.searchValueMinLength}, but '$searchString' given of length ${searchString.length}.")
val escapedSearchStr = stringFormatter.toSparqlEncodedString(searchStr, throw BadRequestException(s"Invalid search string: '$searchStr'"))

if (escapedSearchStr.length < settings.searchValueMinLength) {
throw BadRequestException(s"A search value is expected to have at least length of ${settings.searchValueMinLength}, but '$escapedSearchStr' given of length ${escapedSearchStr.length}.")
}

val params: Map[String, String] = requestContext.request.uri.query().toMap
Expand All @@ -217,7 +225,7 @@ class SearchRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit
val requestMessage: Future[FulltextSearchRequestV2] = for {
requestingUser <- getUserADM(requestContext)
} yield FulltextSearchRequestV2(
searchValue = searchString,
searchValue = escapedSearchStr,
offset = offset,
limitToProject = limitToProject,
limitToResourceClass = limitToResourceClass,
Expand Down
Expand Up @@ -198,6 +198,16 @@ class SearchRouteV2R2RSpec extends R2RSpec {
}
}

"not accept a fulltext query containing http://api.knora.org" in {
val invalidSearchString: String = URLEncoder.encode("PREFIX knora-api: <http://api.knora.org/ontology/knora-api/v2#>", "UTF-8")

Get(s"/v2/search/$invalidSearchString") ~> searchPath ~> check {
val responseStr = responseAs[String]
assert(status == StatusCodes.BAD_REQUEST, responseStr)
assert(responseStr.contains("It looks like you are submitting a Gravsearch request to a full-text search route"))
}
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Queries without type inference

Expand Down