diff --git a/webapi/src/main/scala/org/knora/webapi/messages/OntologyConstants.scala b/webapi/src/main/scala/org/knora/webapi/messages/OntologyConstants.scala index b4f6572583..8ac8015ad5 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/OntologyConstants.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/OntologyConstants.scala @@ -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. */ diff --git a/webapi/src/main/scala/org/knora/webapi/messages/StringFormatter.scala b/webapi/src/main/scala/org/knora/webapi/messages/StringFormatter.scala index 788afe0d8c..1601cc0b83 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/StringFormatter.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/StringFormatter.scala @@ -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"Internal ontology <$iri> cannot be served") + } + } } diff --git a/webapi/src/main/scala/org/knora/webapi/routing/v2/OntologiesRouteV2.scala b/webapi/src/main/scala/org/knora/webapi/routing/v2/OntologiesRouteV2.scala index c5b9902ccb..1aa8329fe9 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/v2/OntologiesRouteV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/v2/OntologiesRouteV2.scala @@ -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") @@ -163,7 +165,6 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) put { entity(as[String]) { jsonRequest => requestContext => { - val requestDoc: JsonLDDocument = JsonLDUtil.parseJsonLD(jsonRequest) val requestMessageFuture: Future[ChangeOntologyMetadataRequestV2] = for { @@ -171,6 +172,7 @@ class OntologiesRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) requestContext = requestContext, featureFactoryConfig = featureFactoryConfig ) + requestMessage: ChangeOntologyMetadataRequestV2 <- ChangeOntologyMetadataRequestV2.fromJsonLD( jsonLDDocument = requestDoc, apiRequestID = UUID.randomUUID, @@ -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 @@ -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") @@ -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, @@ -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, @@ -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, @@ -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, @@ -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") } @@ -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") @@ -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, @@ -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, @@ -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") } @@ -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") @@ -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, @@ -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)) { diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/v2/OntologyV2R2RSpec.scala b/webapi/src/test/scala/org/knora/webapi/e2e/v2/OntologyV2R2RSpec.scala index ebeac6d93b..e090e6153a 100644 --- a/webapi/src/test/scala/org/knora/webapi/e2e/v2/OntologyV2R2RSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/e2e/v2/OntologyV2R2RSpec.scala @@ -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 @@ -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._ @@ -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) @@ -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"