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(OntologiesRouteV2): Reject internal ontology names in external schema (DSP-1394) #1827

Merged
merged 4 commits into from Mar 11, 2021
Merged
Show file tree
Hide file tree
Changes from 3 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
Expand Up @@ -158,6 +158,14 @@ object OntologyConstants {
OntologyConstants.Rdfs.Label -> OntologyConstants.Xsd.String
)

/**
* Ontology labels that are used only in the internal schema.
*/
val InternalOntologyLabels: Set[String] = Set(
KnoraBase.KnoraBaseOntologyLabel,
KnoraAdmin.KnoraAdminOntologyLabel
)

/**
* Ontology labels that are reserved for built-in ontologies.
*/
Expand Down
Expand Up @@ -3236,4 +3236,16 @@ class StringFormatter private (val maybeSettings: Option[KnoraSettingsImpl] = No

customValueIri
}

/**
* Throws [[BadRequestException]] if a Knora API v2 definition API has an ontology name that can only be used
* in the internal schema.
*
* @param iri the IRI to be checked.
*/
def checkExternalOntologyName(iri: SmartIri): Unit = {
if (iri.isKnoraApiV2DefinitionIri && OntologyConstants.InternalOntologyLabels.contains(iri.getOntologyName)) {
throw BadRequestException(s"Invalid IRI: <$iri>")
Copy link
Contributor

Choose a reason for hiding this comment

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

@benjamingeer don't you think it might be better to throw a more clear exception message indicating that internal ontologies cannot be served?

Copy link
Author

Choose a reason for hiding this comment

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

Done: 05a7ba1

}
}
}
Expand Up @@ -93,6 +93,8 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData)
val requestedOntology = requestedOntologyStr.toSmartIriWithErr(
throw BadRequestException(s"Invalid ontology IRI: $requestedOntologyStr"))

stringFormatter.checkExternalOntologyName(requestedOntology)

val targetSchema = requestedOntology.getOntologySchema match {
case Some(apiV2Schema: ApiV2Schema) => apiV2Schema
case _ => throw BadRequestException(s"Invalid ontology IRI: $requestedOntologyStr")
Expand Down Expand Up @@ -163,14 +165,14 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData)
put {
entity(as[String]) { jsonRequest => requestContext =>
{

val requestDoc: JsonLDDocument = JsonLDUtil.parseJsonLD(jsonRequest)

val requestMessageFuture: Future[ChangeOntologyMetadataRequestV2] = for {
requestingUser <- getUserADM(
requestContext = requestContext,
featureFactoryConfig = featureFactoryConfig
)

requestMessage: ChangeOntologyMetadataRequestV2 <- ChangeOntologyMetadataRequestV2.fromJsonLD(
jsonLDDocument = requestDoc,
apiRequestID = UUID.randomUUID,
Expand Down Expand Up @@ -208,6 +210,7 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData)
requestContext = requestContext,
featureFactoryConfig = featureFactoryConfig
)

validatedProjectIris = projectIris
.map(iri => iri.toSmartIriWithErr(throw BadRequestException(s"Invalid project IRI: $iri")))
.toSet
Expand Down Expand Up @@ -235,6 +238,8 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData)
val requestedOntologyIri = externalOntologyIriStr.toSmartIriWithErr(
throw BadRequestException(s"Invalid ontology IRI: $externalOntologyIriStr"))

stringFormatter.checkExternalOntologyName(requestedOntologyIri)

val targetSchema = requestedOntologyIri.getOntologySchema match {
case Some(apiV2Schema: ApiV2Schema) => apiV2Schema
case _ => throw BadRequestException(s"Invalid ontology IRI: $externalOntologyIriStr")
Expand Down Expand Up @@ -277,13 +282,14 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData)
// Create a new class.
entity(as[String]) { jsonRequest => requestContext =>
{

val requestMessageFuture: Future[CreateClassRequestV2] = for {
requestingUser <- getUserADM(
requestContext = requestContext,
featureFactoryConfig = featureFactoryConfig
)

requestDoc: JsonLDDocument = JsonLDUtil.parseJsonLD(jsonRequest)

requestMessage: CreateClassRequestV2 <- CreateClassRequestV2.fromJsonLD(
jsonLDDocument = requestDoc,
apiRequestID = UUID.randomUUID,
Expand Down Expand Up @@ -316,13 +322,14 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData)
// Change the labels or comments of a class.
entity(as[String]) { jsonRequest => requestContext =>
{

val requestMessageFuture: Future[ChangeClassLabelsOrCommentsRequestV2] = for {
requestingUser <- getUserADM(
requestContext = requestContext,
featureFactoryConfig = featureFactoryConfig
)

requestDoc: JsonLDDocument = JsonLDUtil.parseJsonLD(jsonRequest)

requestMessage <- ChangeClassLabelsOrCommentsRequestV2.fromJsonLD(
jsonLDDocument = requestDoc,
apiRequestID = UUID.randomUUID,
Expand Down Expand Up @@ -356,13 +363,14 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData)
// Add cardinalities to a class.
entity(as[String]) { jsonRequest => requestContext =>
{

val requestMessageFuture: Future[AddCardinalitiesToClassRequestV2] = for {
requestingUser <- getUserADM(
requestContext = requestContext,
featureFactoryConfig = featureFactoryConfig
)

requestDoc: JsonLDDocument = JsonLDUtil.parseJsonLD(jsonRequest)

requestMessage: AddCardinalitiesToClassRequestV2 <- AddCardinalitiesToClassRequestV2.fromJsonLD(
jsonLDDocument = requestDoc,
apiRequestID = UUID.randomUUID,
Expand Down Expand Up @@ -396,13 +404,14 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData)
// Change a class's cardinalities.
entity(as[String]) { jsonRequest => requestContext =>
{

val requestMessageFuture: Future[ChangeCardinalitiesRequestV2] = for {
requestingUser <- getUserADM(
requestContext = requestContext,
featureFactoryConfig = featureFactoryConfig
)

requestDoc: JsonLDDocument = JsonLDUtil.parseJsonLD(jsonRequest)

requestMessage: ChangeCardinalitiesRequestV2 <- ChangeCardinalitiesRequestV2.fromJsonLD(
jsonLDDocument = requestDoc,
apiRequestID = UUID.randomUUID,
Expand Down Expand Up @@ -434,11 +443,12 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData)
path(OntologiesBasePath / "classes" / Segments) { externalResourceClassIris: List[IRI] =>
get { requestContext =>
{

val classesAndSchemas: Set[(SmartIri, ApiV2Schema)] = externalResourceClassIris.map { classIriStr: IRI =>
val requestedClassIri: SmartIri =
classIriStr.toSmartIriWithErr(throw BadRequestException(s"Invalid class IRI: $classIriStr"))

stringFormatter.checkExternalOntologyName(requestedClassIri)

if (!requestedClassIri.isKnoraApiV2EntityIri) {
throw BadRequestException(s"Invalid class IRI: $classIriStr")
}
Expand Down Expand Up @@ -508,6 +518,7 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData)
}

val classIri = classIriStr.toSmartIri
stringFormatter.checkExternalOntologyName(classIri)

if (!classIri.getOntologySchema.contains(ApiV2Complex)) {
throw BadRequestException(s"Invalid class IRI for request: $classIriStr")
Expand Down Expand Up @@ -555,13 +566,14 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData)
// Create a new property.
entity(as[String]) { jsonRequest => requestContext =>
{

val requestMessageFuture: Future[CreatePropertyRequestV2] = for {
requestingUser <- getUserADM(
requestContext = requestContext,
featureFactoryConfig = featureFactoryConfig
)

requestDoc: JsonLDDocument = JsonLDUtil.parseJsonLD(jsonRequest)

requestMessage: CreatePropertyRequestV2 <- CreatePropertyRequestV2.fromJsonLD(
jsonLDDocument = requestDoc,
apiRequestID = UUID.randomUUID,
Expand Down Expand Up @@ -595,13 +607,14 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData)
// Change the labels or comments of a property.
entity(as[String]) { jsonRequest => requestContext =>
{

val requestMessageFuture: Future[ChangePropertyLabelsOrCommentsRequestV2] = for {
requestingUser <- getUserADM(
requestContext = requestContext,
featureFactoryConfig = featureFactoryConfig
)

requestDoc: JsonLDDocument = JsonLDUtil.parseJsonLD(jsonRequest)

requestMessage: ChangePropertyLabelsOrCommentsRequestV2 <- ChangePropertyLabelsOrCommentsRequestV2
.fromJsonLD(
jsonLDDocument = requestDoc,
Expand Down Expand Up @@ -634,11 +647,12 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData)
path(OntologiesBasePath / "properties" / Segments) { externalPropertyIris: List[IRI] =>
get { requestContext =>
{

val propsAndSchemas: Set[(SmartIri, ApiV2Schema)] = externalPropertyIris.map { propIriStr: IRI =>
val requestedPropIri: SmartIri =
propIriStr.toSmartIriWithErr(throw BadRequestException(s"Invalid property IRI: $propIriStr"))

stringFormatter.checkExternalOntologyName(requestedPropIri)

if (!requestedPropIri.isKnoraApiV2EntityIri) {
throw BadRequestException(s"Invalid property IRI: $propIriStr")
}
Expand Down Expand Up @@ -701,13 +715,13 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData)
path(OntologiesBasePath / "properties" / Segments) { externalPropertyIris: List[IRI] =>
delete { requestContext =>
{

val propertyIriStr = externalPropertyIris match {
case List(str) => str
case _ => throw BadRequestException(s"Only one property can be deleted at a time")
}

val propertyIri = propertyIriStr.toSmartIri
stringFormatter.checkExternalOntologyName(propertyIri)

if (!propertyIri.getOntologySchema.contains(ApiV2Complex)) {
throw BadRequestException(s"Invalid property IRI for request: $propertyIri")
Expand Down Expand Up @@ -754,13 +768,14 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData)
post {
entity(as[String]) { jsonRequest => requestContext =>
{

val requestMessageFuture: Future[CreateOntologyRequestV2] = for {
requestingUser <- getUserADM(
requestContext = requestContext,
featureFactoryConfig = featureFactoryConfig
)

requestDoc: JsonLDDocument = JsonLDUtil.parseJsonLD(jsonRequest)

requestMessage: CreateOntologyRequestV2 <- CreateOntologyRequestV2.fromJsonLD(
jsonLDDocument = requestDoc,
apiRequestID = UUID.randomUUID,
Expand Down Expand Up @@ -792,8 +807,8 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData)
ontologyIriStr =>
delete { requestContext =>
{

val ontologyIri = ontologyIriStr.toSmartIri
stringFormatter.checkExternalOntologyName(ontologyIri)

if (!ontologyIri.isKnoraOntologyIri || ontologyIri.isKnoraBuiltInDefinitionIri || !ontologyIri.getOntologySchema
.contains(ApiV2Complex)) {
Expand Down
@@ -1,7 +1,6 @@
package org.knora.webapi.e2e.v2

import java.nio.file.{Path, Paths, Files}

import java.nio.file.{Files, Path, Paths}
import java.net.URLEncoder
import java.time.Instant

Expand All @@ -12,6 +11,7 @@ import akka.http.scaladsl.testkit.RouteTestTimeout
import org.knora.webapi._
import org.knora.webapi.e2e.{ClientTestDataCollector, TestDataFileContent, TestDataFilePath}
import org.knora.webapi.exceptions.AssertionException
import org.knora.webapi.http.directives.DSPApiDirectives
import org.knora.webapi.messages.IriConversions._
import org.knora.webapi.messages.store.triplestoremessages.RdfDataObject
import org.knora.webapi.messages.util.rdf._
Expand Down Expand Up @@ -49,7 +49,7 @@ class OntologyV2R2RSpec extends R2RSpec {

private implicit val stringFormatter: StringFormatter = StringFormatter.getGeneralInstance

private val ontologiesPath = new OntologiesRouteV2(routeData).knoraApiPath
private val ontologiesPath = DSPApiDirectives.handleErrors(system) { new OntologiesRouteV2(routeData).knoraApiPath }

implicit def default(implicit system: ActorSystem): RouteTestTimeout = RouteTestTimeout(settings.defaultTimeout)

Expand Down Expand Up @@ -384,6 +384,20 @@ class OntologyV2R2RSpec extends R2RSpec {
}
}

"not allow the user to request the knora-base ontology" in {
Get("/v2/ontologies/allentities/http%3A%2F%2Fapi.knora.org%2Fontology%2Fknora-base%2Fv2") ~> ontologiesPath ~> check {
val responseStr: String = responseAs[String]
assert(response.status == StatusCodes.BadRequest, responseStr)
}
}

"not allow the user to request the knora-admin ontology" in {
Get("/v2/ontologies/allentities/http%3A%2F%2Fapi.knora.org%2Fontology%2Fknora-admin%2Fv2") ~> ontologiesPath ~> check {
val responseStr: String = responseAs[String]
assert(response.status == StatusCodes.BadRequest, responseStr)
}
}

"create an empty ontology called 'foo' with a project code" in {
val label = "The foo ontology"

Expand Down