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

refactor: Add BEOL exception to UUID validation (DEV-1570) #2349

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
12 changes: 6 additions & 6 deletions dsp-shared/src/main/scala/dsp/valueobjects/Iri.scala
Expand Up @@ -49,7 +49,7 @@ object Iri {

if (!V2IriValidation.isKnoraGroupIriStr(value)) {
Validation.fail(BadRequestException(IriErrorMessages.GroupIriInvalid))
} else if (isUuid && !V2UuidValidation.isUuidVersion4Or5(value)) {
} else if (isUuid && !V2UuidValidation.isUuidSupported(value)) {
Validation.fail(BadRequestException(IriErrorMessages.UuidVersionInvalid))
} else {
val validatedValue = Validation(
Expand Down Expand Up @@ -80,7 +80,7 @@ object Iri {

if (!V2IriValidation.isKnoraListIriStr(value)) {
Validation.fail(BadRequestException(IriErrorMessages.ListIriInvalid))
} else if (isUuid && !V2UuidValidation.isUuidVersion4Or5(value)) {
} else if (isUuid && !V2UuidValidation.isUuidSupported(value)) {
Validation.fail(BadRequestException(IriErrorMessages.UuidVersionInvalid))
} else {
val validatedValue = Validation(
Expand Down Expand Up @@ -112,7 +112,7 @@ object Iri {
} else {
if (!V2IriValidation.isKnoraProjectIriStr(value)) {
Validation.fail(ValidationException(IriErrorMessages.ProjectIriInvalid))
} else if (!V2IriValidation.isKnoraBuiltInProjectIriStr(value) && !V2UuidValidation.isUuidVersion4Or5(value)) {
} else if (!V2IriValidation.isKnoraBuiltInProjectIriStr(value) && !V2UuidValidation.isUuidSupported(value)) {
Validation.fail(ValidationException(IriErrorMessages.UuidVersionInvalid))
} else {
val eitherValue = Try(
Expand Down Expand Up @@ -147,7 +147,7 @@ object Iri {
Validation.fail(ValidationException(IriErrorMessages.UuidMissing))
} else if (!V2UuidValidation.hasUuidLength(value)) {
Validation.fail(ValidationException(IriErrorMessages.UuidInvalid(value)))
} else if (!V2UuidValidation.isUuidVersion4Or5(value)) {
} else if (!V2UuidValidation.isUuidSupported(value)) {
Validation.fail(ValidationException(IriErrorMessages.UuidVersionInvalid))
} else Validation.succeed(new Base64Uuid(value) {})
}
Expand All @@ -165,7 +165,7 @@ object Iri {

if (!V2IriValidation.isKnoraRoleIriStr(value)) {
Validation.fail(BadRequestException(IriErrorMessages.RoleIriInvalid(value)))
} else if (isUuid && !V2UuidValidation.isUuidVersion4Or5(value)) {
} else if (isUuid && !V2UuidValidation.isUuidSupported(value)) {
Validation.fail(BadRequestException(IriErrorMessages.UuidVersionInvalid))
} else {
val validatedValue = Validation(
Expand Down Expand Up @@ -198,7 +198,7 @@ object Iri {

if (!V2IriValidation.isKnoraUserIriStr(value)) {
Validation.fail(BadRequestException(IriErrorMessages.UserIriInvalid(value)))
} else if (isUuid && !V2UuidValidation.isUuidVersion4Or5(value)) {
} else if (isUuid && !V2UuidValidation.isUuidSupported(value)) {
Validation.fail(BadRequestException(IriErrorMessages.UuidVersionInvalid))
} else {
val validatedValue = Validation(
Expand Down
14 changes: 10 additions & 4 deletions dsp-shared/src/main/scala/dsp/valueobjects/V2.scala
Expand Up @@ -217,15 +217,20 @@ object V2UuidValidation {
s.length == CanonicalUuidLength || s.length == Base64UuidLength

/**
* Checks if UUID used to create IRI has correct version (4 and 5 are allowed).
* Checks if UUID used to create IRI has supported version (4 and 5 are allowed).
* With an exception of BEOL project IRI `http://rdfh.ch/projects/yTerZGyxjZVqFMNNKXCDPF`.
*
* @param s the string (IRI) to be checked.
* @return TRUE for correct versions, FALSE for incorrect.
* @return TRUE for supported versions, FALSE for not supported.
*/
def isUuidVersion4Or5(s: String): Boolean =
getUUIDVersion(s) == 4 || getUUIDVersion(s) == 5
def isUuidSupported(s: String): Boolean =
if (s != "http://rdfh.ch/projects/yTerZGyxjZVqFMNNKXCDPF") {
getUUIDVersion(s) == 4 || getUUIDVersion(s) == 5
} else true

/**
* Decodes Base64 encoded UUID and gets its version.
*
* @param uuid the Base64 encoded UUID as [[String]] to be checked.
* @return UUID version.
*/
Expand All @@ -236,6 +241,7 @@ object V2UuidValidation {

/**
* Gets the last IRI segment - Base64 encoded UUID.
*
* @param iri the IRI [[String]] to get the UUID from.
* @return Base64 encoded UUID as [[String]]
*/
Expand Down
5 changes: 4 additions & 1 deletion dsp-shared/src/test/scala/dsp/valueobjects/IriSpec.scala
Expand Up @@ -27,6 +27,7 @@ object IriSpec extends ZIOSpecDefault {

val invalidProjectIri = "http://rdfh.ch/projects/0001"
val validProjectIri = "http://rdfh.ch/projects/CwQ8hXF9Qlm1gl2QE6pTpg"
val beolProjectIri = "http://rdfh.ch/projects/yTerZGyxjZVqFMNNKXCDPF"
val projectIriWithUUIDVersion3 = "http://rdfh.ch/projects/tZjZhGSZMeCLA5VeUmwAmg"
val builtInProjectIri = "http://www.knora.org/ontology/knora-admin#SystemProject"

Expand Down Expand Up @@ -183,9 +184,11 @@ object IriSpec extends ZIOSpecDefault {
(for {
iri <- makeProjectIri(validProjectIri)
iri2 <- makeProjectIri(builtInProjectIri)
beolIri <- makeProjectIri(beolProjectIri)
maybeIri <- maybeProjectIri
} yield assertTrue(iri.value == validProjectIri) &&
assertTrue(iri2.value == builtInProjectIri) &&
assertTrue(beolIri.value == beolProjectIri) &&
assert(maybeIri)(isSome(equalTo(iri)))).toZIO
},
test("successfully validate passing None") {
Expand All @@ -199,7 +202,7 @@ object IriSpec extends ZIOSpecDefault {
test("pass an empty value and return an error") {
assertTrue(Base64Uuid.make("") == Validation.fail(ValidationException(IriErrorMessages.UuidMissing)))
},
test("pass an invalid UUID and return an error") {
test("pass an invalid UUID and return an error") {
assertTrue(
Base64Uuid.make(invalidIri) == Validation.fail(ValidationException(IriErrorMessages.UuidInvalid(invalidIri)))
)
Expand Down
Expand Up @@ -264,6 +264,27 @@ class CreateListItemsRouteADME2ESpec
newListIri.set(listInfo.id)
}

"create a list using bad project IRI, created in the old way with bad UUID version" in {
val createListRequest: String =
s"""{
| "projectIri": "${SharedTestDataADM.BEOL_PROJECT_IRI}",
| "labels": [{ "value": "Neue Liste", "language": "de"}],
| "comments": [{ "value": "XXXXX", "language": "en"}]
|}""".stripMargin

val request = Post(
baseApiUrl + s"/admin/lists",
HttpEntity(ContentTypes.`application/json`, createListRequest)
) ~> addCredentials(beolAdminUserCreds.basicHttpCredentials)
val response: HttpResponse = singleAwaitingRequest(request)
response.status should be(StatusCodes.OK)

val receivedList: ListADM = AkkaHttpUtils.httpResponseToJson(response).fields("list").convertTo[ListADM]

val listInfo = receivedList.listinfo
listInfo.projectIri should be(SharedTestDataADM.BEOL_PROJECT_IRI)
}

"return a ForbiddenException if the user creating the list is not project or system admin" in {
val params =
s"""
Expand Down
Expand Up @@ -1259,29 +1259,21 @@ class StringFormatterSpec extends CoreSpec {
uuid should be(base4DecodedUuid)
}

"return TRUE if IRI contains UUID version 4 or 5, otherwise return FALSE" in {
val iri3 = "http://rdfh.ch/0000/rKAU0FNjPUKWqOT8MEW_UQ"
val iri4 = "http://rdfh.ch/0001/cmfk1DMHRBiR4-_6HXpEFA"
val iri5 = "http://rdfh.ch/080C/Ef9heHjPWDS7dMR_gGax2Q"
"return TRUE for IRIs BEOL project IRI and other which contain UUID version 4 or 5, otherwise return FALSE" in {
val iri3 = "http://rdfh.ch/0000/rKAU0FNjPUKWqOT8MEW_UQ"
val iri4 = "http://rdfh.ch/0001/cmfk1DMHRBiR4-_6HXpEFA"
val iri5 = "http://rdfh.ch/080C/Ef9heHjPWDS7dMR_gGax2Q"
val beolIri = "http://rdfh.ch/projects/yTerZGyxjZVqFMNNKXCDPF"

val testIRIFromVersion3UUID = stringFormatter.isUuidVersion4Or5(iri3)
val testIRIFromVersion4UUID = stringFormatter.isUuidVersion4Or5(iri4)
val testIRIFromVersion5UUID = stringFormatter.isUuidVersion4Or5(iri5)

val iri = "http://rdfh.ch/0001/rYAMw7wSTbGw3boYHefByg"

println(
777,
stringFormatter.makeRandomBase64EncodedUuid,
stringFormatter.makeRandomBase64EncodedUuid,
stringFormatter.getUUIDVersion(iri),
stringFormatter.hasUuidLength(iri.split("/").last),
stringFormatter.isUuidVersion4Or5(iri)
)
val testIRIFromVersion3UUID = stringFormatter.isUuidSupported(iri3)
val testIRIFromVersion4UUID = stringFormatter.isUuidSupported(iri4)
val testIRIFromVersion5UUID = stringFormatter.isUuidSupported(iri5)
val testBeolIri = stringFormatter.isUuidSupported(beolIri)

testIRIFromVersion3UUID should be(false)
testIRIFromVersion4UUID should be(true)
testIRIFromVersion5UUID should be(true)
testBeolIri should be(true)
}
}
}
Expand Up @@ -2776,6 +2776,7 @@ class StringFormatter private (

/**
* Gets the last segment of IRI, decodes UUID and gets the version.
*
* @param s the string (IRI) to be checked.
* @return UUID version.
*/
Expand All @@ -2785,12 +2786,16 @@ class StringFormatter private (
}

/**
* Checks if UUID used to create IRI has correct version (4 and 5 are allowed).
* Checks if UUID used to create IRI has supported version (4 and 5 are allowed).
* With an exception of BEOL project IRI `http://rdfh.ch/projects/yTerZGyxjZVqFMNNKXCDPF`.
*
* @param s the string (IRI) to be checked.
* @return TRUE for correct versions, FALSE for incorrect.
* @return TRUE for supported versions, FALSE for not supported.
*/
def isUuidVersion4Or5(s: IRI): Boolean =
getUUIDVersion(s) == 4 || getUUIDVersion(s) == 5
def isUuidSupported(s: String): Boolean =
if (s != "http://rdfh.ch/projects/yTerZGyxjZVqFMNNKXCDPF") {
getUUIDVersion(s) == 4 || getUUIDVersion(s) == 5
} else true

/**
* Checks if a string is the right length to be a canonical or Base64-encoded UUID.
Expand All @@ -2803,19 +2808,21 @@ class StringFormatter private (

/**
* Validates resource IRI
*
* @param iri to be validated
*/
def validateUUIDOfResourceIRI(iri: SmartIri): Unit =
if (iri.isKnoraResourceIri && hasUuidLength(iri.toString.split("/").last) && !isUuidVersion4Or5(iri.toString)) {
if (iri.isKnoraResourceIri && hasUuidLength(iri.toString.split("/").last) && !isUuidSupported(iri.toString)) {
throw BadRequestException(IriErrorMessages.UuidVersionInvalid)
}

/**
* Validates permission IRI
*
* @param iri to be validated.
*/
def validatePermissionIRI(iri: IRI): Unit =
if (isKnoraPermissionIriStr(iri) && !isUuidVersion4Or5(iri)) {
if (isKnoraPermissionIriStr(iri) && !isUuidSupported(iri)) {
throw BadRequestException(IriErrorMessages.UuidVersionInvalid)
} else {
validatePermissionIri(iri, throw BadRequestException(s"Invalid permission IRI ${iri} is given."))
Expand Down
Expand Up @@ -494,7 +494,7 @@ object DeleteValueRequestV2 extends KnoraJsonLDRequestReaderV2[DeleteValueReques

if (
stringFormatter.hasUuidLength(valueIri.toString.split("/").last)
&& !stringFormatter.isUuidVersion4Or5(valueIri.toString)
&& !stringFormatter.isUuidSupported(valueIri.toString)
) {
throw BadRequestException(IriErrorMessages.UuidVersionInvalid)
}
Expand Down