Skip to content

Commit

Permalink
fix: Allow shorter usernames (DEV-3797) (#3292)
Browse files Browse the repository at this point in the history
  • Loading branch information
seakayone authored Jun 24, 2024
1 parent bd0d887 commit 6f31133
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 124 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -181,14 +181,14 @@ object Username extends StringValueCompanion[Username] {

/**
* A regex that matches a valid username
* - 4 - 50 characters long
* - 3 - 50 characters long
* - Only contains alphanumeric characters, underscore, hyphen and dot.
* - Underscore, hyphen and dot can't be at the end or start of a username
* - Underscore, hyphen or dot can't be used multiple times in a row
*/
private val UsernameRegex: Regex = (
"^(?=.{4,50}$)" +
// 4 - 50 characters long
"^(?=.{3,50}$)" +
// 3 - 50 characters long
"(?![_.-])" +
// Underscore, hyphen and dot can't be at the start of a username
"(?!.*[_.-]{2})" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,84 @@ package org.knora.webapi.slice.admin.domain.model

import zio.test.*

import org.knora.webapi.slice.admin.domain.model.UserSpec.test

object UserIriSpec extends ZIOSpecDefault {

private val testIris = Seq(
private val validIris = Seq(
"http://rdfh.ch/users/9CzAcnT0So-k10CTySHnQA",
"http://rdfh.ch/users/A6AZpISxSm65bBHZ_G5Z4w",
"http://rdfh.ch/users/AnythingAdminUser",
"http://rdfh.ch/users/AnythingAdminUser",
"http://rdfh.ch/users/PSGbemdjZi4kQ6GHJVkLGE",
"http://rdfh.ch/users/_fH9FS-VRMiPPiIMRpjevA",
"http://rdfh.ch/users/activites-cs-test-user1",
"http://rdfh.ch/users/drawings-gods-test-ddd1",
"http://rdfh.ch/users/drawings-gods-test-user-metaannotator",
"http://rdfh.ch/users/images-reviewer-user",
"http://rdfh.ch/users/jDEEitJESRi3pDaDjjQ1WQ",
"http://rdfh.ch/users/mls-0807-import-user",
"http://rdfh.ch/users/multiuser",
"http://rdfh.ch/users/normaluser",
"http://rdfh.ch/users/parole-religieuse-test-user1",
"http://rdfh.ch/users/reforme-geneve-test1",
"http://rdfh.ch/users/root",
"http://rdfh.ch/users/root",
"http://rdfh.ch/users/roud-oeuvres-test-user1",
"http://rdfh.ch/users/stardom-archivist-test-user",
"http://rdfh.ch/users/stardom-cacourcoux",
"http://rdfh.ch/users/stardom-import-snf",
"http://rdfh.ch/users/subotic",
"http://rdfh.ch/users/superuser",
"http://rdfh.ch/users/theatre-societe-test-user",
"http://www.knora.org/ontology/knora-admin#AnonymousUser",
"http://www.knora.org/ontology/knora-admin#SystemUser",
)

val spec = suite("UserIriSpec")(test("") {
check(Gen.fromIterable(testIris))(iri => assertTrue(UserIri.from(iri).map(_.value) == Right(iri)))
})
val spec = suite("UserIri")(
test("must not be empty") {
check(Gen.fromIterable(validIris))(iri => assertTrue(UserIri.from(iri).map(_.value) == Right(iri)))
},
test("make new should create a valid user iri") {
assertTrue(UserIri.makeNew.value.startsWith("http://rdfh.ch/users/"))
},
test("built in users should be builtIn") {
val builtInIris = Gen.fromIterable(
Seq(
"http://www.knora.org/ontology/knora-admin#AnonymousUser",
"http://www.knora.org/ontology/knora-admin#SystemUser",
"http://www.knora.org/ontology/knora-admin#AnonymousUser",
),
)
check(builtInIris) { i =>
val userIri = UserIri.unsafeFrom(i)
assertTrue(!userIri.isRegularUser, userIri.isBuiltInUser)
}
},
test("regular user iris should not be builtIn") {
val builtInIris = Gen.fromIterable(
Seq(
"http://rdfh.ch/users/jDEEitJESRi3pDaDjjQ1WQ",
"http://rdfh.ch/users/PSGbemdjZi4kQ6GHJVkLGE",
),
)
check(builtInIris) { i =>
val userIri = UserIri.unsafeFrom(i)
assertTrue(userIri.isRegularUser, !userIri.isBuiltInUser)
}
},
test("pass an empty value and return an error") {
assertTrue(UserIri.from("") == Left("User IRI cannot be empty."))
},
test("pass an invalid value and return an error") {
val invalidIris = Gen.fromIterable(
Seq(
"Invalid IRI",
"http://rdfh.ch/user/AnythingAdminUser",
"http://rdfh.ch/users/AnythingAdminUser/",
),
)
check(invalidIris)(i => assertTrue(UserIri.from(i) == Left(s"User IRI is invalid.")))
},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,58 +12,6 @@ object UserSpec extends ZIOSpecDefault {
private val validGivenName = "John"
private val validFamilyName = "Rambo"

private val userSuite = suite("User")()

private val usernameSuite = suite("Username")(
test("Username must not be empty") {
assertTrue(Username.from("") == Left("Username cannot be empty."))
},
test("Username may contain alphanumeric characters, underscore, hyphen and dot") {
assertTrue(Username.from("a_2.3-4").isRight)
},
test("Username has to be at least 4 characters long") {
assertTrue(Username.from("abc") == Left("Username is invalid."))
},
test("Username has to be at most 50 characters long") {
assertTrue(
Username.from("123456789012345678901234567890123456789012345678901") == Left("Username is invalid."),
)
},
test("Username must not contain other characters") {
val invalid = List("a_2.3!", "a_2,3", "[email protected]")
check(Gen.fromIterable(invalid)) { i =>
assertTrue(Username.from(i) == Left("Username is invalid."))
}
},
test("Username must not start with a dot") {
assertTrue(Username.from(".abc") == Left("Username is invalid."))
},
test("Username must not end with a dot") {
assertTrue(Username.from("abc.") == Left("Username is invalid."))
},
test("Username must not contain two dots in a row") {
assertTrue(Username.from("a..bc") == Left("Username is invalid."))
},
test("Username must not start with an underscore") {
assertTrue(Username.from("_abc") == Left("Username is invalid."))
},
test("Username must not end with an underscore") {
assertTrue(Username.from("abc_") == Left("Username is invalid."))
},
test("Username must not contain two underscores in a row") {
assertTrue(Username.from("a__bc") == Left("Username is invalid."))
},
test("Username must not start with an hyphen") {
assertTrue(Username.from("-abc") == Left("Username is invalid."))
},
test("Username must not end with an hyphen") {
assertTrue(Username.from("abc-") == Left("Username is invalid."))
},
test("Username must not contain two hyphen in a row") {
assertTrue(Username.from("a--bc") == Left("Username is invalid."))
},
)

private val emailSuite = suite("Email")(
test("Email must be a correct email address") {
assertTrue(Email.from("[email protected]").isRight)
Expand All @@ -76,68 +24,6 @@ object UserSpec extends ZIOSpecDefault {
},
)

private val iriSuite = suite("UserIri")(
test("pass an empty value and return an error") {
assertTrue(UserIri.from("") == Left("User IRI cannot be empty."))
},
test("make new should create a valid user iri") {
assertTrue(UserIri.makeNew.value.startsWith("http://rdfh.ch/users/"))
},
test("built in users should be builtIn") {
val builtInIris = Gen.fromIterable(
Seq(
"http://www.knora.org/ontology/knora-admin#AnonymousUser",
"http://www.knora.org/ontology/knora-admin#SystemUser",
"http://www.knora.org/ontology/knora-admin#AnonymousUser",
),
)
check(builtInIris) { i =>
val userIri = UserIri.unsafeFrom(i)
assertTrue(!userIri.isRegularUser, userIri.isBuiltInUser)
}
},
test("regular user iris should not be builtIn") {
val builtInIris = Gen.fromIterable(
Seq(
"http://rdfh.ch/users/jDEEitJESRi3pDaDjjQ1WQ",
"http://rdfh.ch/users/PSGbemdjZi4kQ6GHJVkLGE",
),
)
check(builtInIris) { i =>
val userIri = UserIri.unsafeFrom(i)
assertTrue(userIri.isRegularUser, !userIri.isBuiltInUser)
}
},
test("valid iris should be a valid iri") {
val validIris = Gen.fromIterable(
Seq(
"http://rdfh.ch/users/jDEEitJESRi3pDaDjjQ1WQ",
"http://rdfh.ch/users/PSGbemdjZi4kQ6GHJVkLGE",
"http://www.knora.org/ontology/knora-admin#AnonymousUser",
"http://www.knora.org/ontology/knora-admin#SystemUser",
"http://www.knora.org/ontology/knora-admin#AnonymousUser",
"http://rdfh.ch/users/mls-0807-import-user",
"http://rdfh.ch/users/root",
"http://rdfh.ch/users/images-reviewer-user",
"http://rdfh.ch/users/AnythingAdminUser",
"http://rdfh.ch/users/subotic",
"http://rdfh.ch/users/_fH9FS-VRMiPPiIMRpjevA",
),
)
check(validIris)(i => assertTrue(UserIri.from(i).isRight))
},
test("pass an invalid value and return an error") {
val invalidIris = Gen.fromIterable(
Seq(
"Invalid IRI",
"http://rdfh.ch/user/AnythingAdminUser",
"http://rdfh.ch/users/AnythingAdminUser/",
),
)
check(invalidIris)(i => assertTrue(UserIri.from(i) == Left(s"User IRI is invalid.")))
},
)

private val givenNameSuite = suite("GivenName")(
test("pass an empty value and return an error") {
assertTrue(GivenName.from("") == Left("GivenName cannot be empty."))
Expand Down Expand Up @@ -178,10 +64,7 @@ object UserSpec extends ZIOSpecDefault {
)

val spec: Spec[Any, Any] = suite("UserSpec")(
userSuite,
usernameSuite,
emailSuite,
iriSuite,
givenNameSuite,
familyNameSuite,
passwordSuite,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright © 2021 - 2024 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors.
* SPDX-License-Identifier: Apache-2.0
*/

package org.knora.webapi.slice.admin.domain.model

import zio.test.*

object UsernameSpec extends ZIOSpecDefault {
private val validNames = Seq(
"username",
"user_name",
"user-name",
"user.name",
"user123",
"user_123",
"user-123",
"user.123",
"use",
)
private val invalidNames = Seq(
"_username", // (starts with underscore)
"-username", // (starts with hyphen)
".username", // (starts with dot)
"username_", // (ends with underscore)
"username-", // (ends with hyphen)
"username.", // (ends with dot)
"user__name", // (contains multiple underscores in a row)
"user--name", // (contains multiple hyphens in a row)
"user..name", // (contains multiple dots in a row)
"us", // (less than 3 characters)
"a".repeat(51), // (more than 50 characters)
)

val spec = suite("UsernameSpec")(
test("Username must not be empty") {
assertTrue(Username.from("") == Left("Username cannot be empty."))
},
test("should allow valid names") {
check(Gen.fromIterable(validNames))(it => assertTrue(Username.from(it).map(_.value) == Right(it)))
},
test("should reject invalid names") {
check(Gen.fromIterable(invalidNames))(it => assertTrue(Username.from(it) == Left("Username is invalid.")))
},
)
}

0 comments on commit 6f31133

Please sign in to comment.