From 38164c72d29e9bea0abc1d3e31430b3bd3c647d0 Mon Sep 17 00:00:00 2001 From: ztefanie Date: Wed, 19 Jun 2024 10:25:02 +0200 Subject: [PATCH 01/10] 1433: Create user hashing for Koblenz --- backend/build.gradle.kts | 5 + .../backend/common/utils/Environment.kt | 8 ++ .../backend/common/webservice/Constants.kt | 2 + ...tion.kt => NotCorrectProjectExceptions.kt} | 2 + .../backend/regions/database/Setup.kt | 56 ++++++---- .../backend/verification/Argon2IdHasher.kt | 85 +++++++++++++++ .../webservice/schema/CardMutationService.kt | 22 ++++ .../backend/helper/ExampleCards.kt | 103 +++++++++++------- .../verification/Argon2IdHasherTest.kt | 27 +++++ .../backend/verification/CanonicalJsonTest.kt | 92 ++++++++++------ docs/CreateKoblenzHash.md | 69 ++++++++++++ specs/backend-api.graphql | 2 + specs/card.proto | 5 + 13 files changed, 384 insertions(+), 94 deletions(-) create mode 100644 backend/src/main/kotlin/app/ehrenamtskarte/backend/common/utils/Environment.kt rename backend/src/main/kotlin/app/ehrenamtskarte/backend/exception/service/{NotEakProjectException.kt => NotCorrectProjectExceptions.kt} (59%) create mode 100644 backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasher.kt create mode 100644 backend/src/test/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasherTest.kt create mode 100644 docs/CreateKoblenzHash.md diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts index 802b86676..e929220ec 100644 --- a/backend/build.gradle.kts +++ b/backend/build.gradle.kts @@ -53,6 +53,8 @@ dependencies { // Use the Kotlin JUnit integration. testImplementation("org.jetbrains.kotlin:kotlin-test-junit") + testImplementation("io.mockk:mockk:1.13.11") + implementation("org.jetbrains.exposed:exposed-core:$exposedVersion") implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion") implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion") @@ -69,11 +71,14 @@ dependencies { implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2") implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.15.2") implementation("de.grundid.opendatalab:geojson-jackson:1.14") + implementation("commons-codec:commons-codec:1.17.0") implementation("com.eatthepath:java-otp:0.4.0") // dynamic card verification implementation("com.auth0:java-jwt:4.4.0") // JSON web tokens implementation("at.favre.lib:bcrypt:0.10.2") + implementation("org.bouncycastle:bcpkix-jdk18on:1.76") + implementation("com.google.zxing:core:3.5.2") // QR-Codes } diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/common/utils/Environment.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/common/utils/Environment.kt new file mode 100644 index 000000000..eeb01b87c --- /dev/null +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/common/utils/Environment.kt @@ -0,0 +1,8 @@ +package app.ehrenamtskarte.backend.common.utils + +// This helper class was created to enable mocking getenv in Tests +class Environment { + companion object { + fun getVariable(name: String): String? = System.getenv(name) + } +} diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/common/webservice/Constants.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/common/webservice/Constants.kt index 9bd2a3048..60d04a647 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/common/webservice/Constants.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/common/webservice/Constants.kt @@ -2,5 +2,7 @@ package app.ehrenamtskarte.backend.common.webservice const val EAK_BAYERN_PROJECT = "bayern.ehrenamtskarte.app" const val NUERNBERG_PASS_PROJECT = "nuernberg.sozialpass.app" +const val KOBLENZ_PASS_PROJECT = "koblenz.pass.app" +const val KOBLENZ_PEPPER_SYS_ENV = "KOBLENZ_PEPPER" const val SHOWCASE_PROJECT = "showcase.entitlementcard.app" const val DEFAULT_PROJECT = EAK_BAYERN_PROJECT diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/exception/service/NotEakProjectException.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/exception/service/NotCorrectProjectExceptions.kt similarity index 59% rename from backend/src/main/kotlin/app/ehrenamtskarte/backend/exception/service/NotEakProjectException.kt rename to backend/src/main/kotlin/app/ehrenamtskarte/backend/exception/service/NotCorrectProjectExceptions.kt index 805997a44..6d7e75640 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/exception/service/NotEakProjectException.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/exception/service/NotCorrectProjectExceptions.kt @@ -2,3 +2,5 @@ package app.ehrenamtskarte.backend.exception.service class NotEakProjectException() : Exception("This query can only be used for EAK project") + +class NotKoblenzProjectException() : Exception("This query can only be used for Koblenz project") diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/regions/database/Setup.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/regions/database/Setup.kt index 1a1223307..595ec7d90 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/regions/database/Setup.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/regions/database/Setup.kt @@ -1,18 +1,45 @@ package app.ehrenamtskarte.backend.regions.database import app.ehrenamtskarte.backend.common.webservice.EAK_BAYERN_PROJECT +import app.ehrenamtskarte.backend.common.webservice.KOBLENZ_PASS_PROJECT import app.ehrenamtskarte.backend.common.webservice.NUERNBERG_PASS_PROJECT import app.ehrenamtskarte.backend.projects.database.ProjectEntity import org.jetbrains.exposed.sql.transactions.transaction fun insertOrUpdateRegions() { - transaction { - val projects = ProjectEntity.all() - val dbRegions = RegionEntity.all() + val projects = ProjectEntity.all() + val dbRegions = RegionEntity.all() + + fun createOrUpdateRegion( + regionProjectId: String, + regionName: String, + regionPrefix: String, + regionWebsite: String + ) { + val project = + projects.firstOrNull { it.project == regionProjectId } + ?: throw Error("Required project '$regionProjectId' not found!") + val region = dbRegions.singleOrNull { it.projectId == project.id } + if (region == null) { + RegionEntity.new { + projectId = project.id + name = regionName + prefix = regionPrefix + regionIdentifier = null + website = regionWebsite + } + } else { + region.name = regionName + region.prefix = regionPrefix + region.website = regionWebsite + } + } + transaction { // Create or update eak regions in database - val eakProject = projects.firstOrNull { it.project == EAK_BAYERN_PROJECT } - ?: throw Error("Required project '$EAK_BAYERN_PROJECT' not found!") + val eakProject = + projects.firstOrNull { it.project == EAK_BAYERN_PROJECT } + ?: throw Error("Required project '$EAK_BAYERN_PROJECT' not found!") EAK_BAYERN_REGIONS.forEach { eakRegion -> val dbRegion = dbRegions.find { it.regionIdentifier == eakRegion[2] && it.projectId == eakProject.id } if (dbRegion == null) { @@ -30,22 +57,7 @@ fun insertOrUpdateRegions() { } } - // Create or update nuernberg region in database - val nuernbergPassProject = projects.firstOrNull { it.project == NUERNBERG_PASS_PROJECT } - ?: throw Error("Required project '$NUERNBERG_PASS_PROJECT' not found!") - val nuernbergRegion = dbRegions.singleOrNull { it.projectId == nuernbergPassProject.id } - if (nuernbergRegion == null) { - RegionEntity.new { - projectId = nuernbergPassProject.id - name = "Nürnberg" - prefix = "Stadt" - regionIdentifier = null - website = "https://nuernberg.de" - } - } else { - nuernbergRegion.name = "Nürnberg" - nuernbergRegion.prefix = "Stadt" - nuernbergRegion.website = "https://nuernberg.de" - } + createOrUpdateRegion(NUERNBERG_PASS_PROJECT, "Nürnberg", "Stadt", "https://nuernberg.de") + createOrUpdateRegion(KOBLENZ_PASS_PROJECT, "Koblenz", "Stadt", "https://koblenz.de/") } } diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasher.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasher.kt new file mode 100644 index 000000000..b5d4ef1af --- /dev/null +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasher.kt @@ -0,0 +1,85 @@ +import app.ehrenamtskarte.backend.common.utils.Environment +import app.ehrenamtskarte.backend.common.webservice.KOBLENZ_PEPPER_SYS_ENV +import app.ehrenamtskarte.backend.verification.CanonicalJson +import org.bouncycastle.crypto.generators.Argon2BytesGenerator +import org.bouncycastle.crypto.params.Argon2Parameters +import java.nio.charset.StandardCharsets +import java.util.Base64 + +class Argon2IdHasher { + companion object { + /** + * Copied from spring-security Argon2EncodingUtils.java licenced under Apache 2.0 + * + * Encodes a raw Argon2-hash and its parameters into the standard Argon2-hash-string + * as specified in the reference implementation + * (https://github.com/P-H-C/phc-winner-argon2/blob/master/src/encoding.c#L244): + * + * {@code $argon2[$v=]$m=,t=,p=$$} + **/ + @Throws(IllegalArgumentException::class) + fun encode( + hash: ByteArray?, + parameters: Argon2Parameters + ): String? { + val b64encoder: Base64.Encoder = Base64.getEncoder().withoutPadding() + val stringBuilder = StringBuilder() + val type = + when (parameters.type) { + Argon2Parameters.ARGON2_d -> "\$argon2d" + Argon2Parameters.ARGON2_i -> "\$argon2i" + Argon2Parameters.ARGON2_id -> "\$argon2id" + else -> throw IllegalArgumentException("Invalid algorithm type: " + parameters.type) + } + stringBuilder.append(type) + stringBuilder + .append("\$v=") + .append(parameters.version) + .append("\$m=") + .append(parameters.memory) + .append(",t=") + .append(parameters.iterations) + .append(",p=") + .append(parameters.lanes) + if (parameters.salt != null) { + stringBuilder.append("$").append(b64encoder.encodeToString(parameters.salt)) + } + stringBuilder.append("$").append(b64encoder.encodeToString(hash)) + return stringBuilder.toString() + } + + fun hashUserData(cardInfo: Card.CardInfo): String? { + val canonicalJson = CanonicalJson.messageToMap(cardInfo) + val hashLength = 32 + if (!isCanonicalJsonValid(canonicalJson)) { + throw Exception("Invalid Json input for hashing") + } + + val pepper = Environment.getVariable(KOBLENZ_PEPPER_SYS_ENV) // TODO handle if Null + val pepperByteArray = pepper?.toByteArray(StandardCharsets.UTF_8) + val params = + Argon2Parameters + .Builder(Argon2Parameters.ARGON2_id) + .withVersion(19) + .withIterations(2) + .withSalt(pepperByteArray) + .withParallelism(1) + .withMemoryAsKB(16) + .build() + + val generator = Argon2BytesGenerator() + generator.init(params) + val result = ByteArray(hashLength) + generator.generateBytes(CanonicalJson.serializeToString(canonicalJson).toCharArray(), result) + return encode(result, params) + } + + private fun isCanonicalJsonValid(canonicalJson: Map): Boolean { + val hasName = canonicalJson.get("1") != null + val hasExtensions = canonicalJson.get("3") as? Map + val hasKoblenzPassExtension = hasExtensions?.get("6") as? Map + val hasKoblenzPassId = hasKoblenzPassExtension?.get("1") != null + return hasName && hasKoblenzPassId + } + } +} diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/webservice/schema/CardMutationService.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/webservice/schema/CardMutationService.kt index a8ac4d33d..d0efacf66 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/webservice/schema/CardMutationService.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/webservice/schema/CardMutationService.kt @@ -1,11 +1,14 @@ package app.ehrenamtskarte.backend.verification.webservice.schema +import Argon2IdHasher import Card import app.ehrenamtskarte.backend.application.database.repos.ApplicationRepository import app.ehrenamtskarte.backend.auth.database.AdministratorEntity import app.ehrenamtskarte.backend.auth.service.Authorizer import app.ehrenamtskarte.backend.common.webservice.GraphQLContext +import app.ehrenamtskarte.backend.common.webservice.KOBLENZ_PASS_PROJECT import app.ehrenamtskarte.backend.exception.service.ForbiddenException +import app.ehrenamtskarte.backend.exception.service.NotKoblenzProjectException import app.ehrenamtskarte.backend.exception.service.ProjectNotFoundException import app.ehrenamtskarte.backend.exception.service.UnauthorizedException import app.ehrenamtskarte.backend.exception.webservice.exceptions.InvalidCardHashException @@ -163,6 +166,25 @@ class CardMutationService { return activationCodes } + @GraphQLDescription("Creates a new digital koblenz card and returns it") + fun createCardsByUserData( + dfe: DataFetchingEnvironment, + project: String, + encodedCardInfo: String + ): Boolean { // CardCreationResultModel { + val context = dfe.getContext() + val projectConfig = + context.backendConfiguration.projects.find { it.id == project } + ?: throw ProjectNotFoundException(project) + if (project != KOBLENZ_PASS_PROJECT) { + throw NotKoblenzProjectException() + } + val cardInfoBytes = encodedCardInfo.decodeBase64Bytes() + val cardInfo = Card.CardInfo.parseFrom(cardInfoBytes) + val hashedUserData = Argon2IdHasher.hashUserData(cardInfo) + return false // Will be done in #1421 + } + @GraphQLDescription("Activate a dynamic entitlement card") fun activateCard( project: String, diff --git a/backend/src/test/kotlin/app/ehrenamtskarte/backend/helper/ExampleCards.kt b/backend/src/test/kotlin/app/ehrenamtskarte/backend/helper/ExampleCards.kt index 904c87190..4dc5ccdac 100644 --- a/backend/src/test/kotlin/app/ehrenamtskarte/backend/helper/ExampleCards.kt +++ b/backend/src/test/kotlin/app/ehrenamtskarte/backend/helper/ExampleCards.kt @@ -6,24 +6,36 @@ enum class CardInfoTestSample { Nuernberg, NuernbergWithStartDay, NuernbergWithPassId, - NuernbergWithPassNr + NuernbergWithPassNr, + KoblenzPass } object ExampleCardInfo { - private val bavarianBase = buildCardInfo( - Card.CardInfo.getDefaultInstance(), - fullName = "Max Mustermann", - regionId = 16 - ) + private val bavarianBase = + buildCardInfo( + Card.CardInfo.getDefaultInstance(), + fullName = "Max Mustermann", + regionId = 16 + ) - private val nuernbergBase = buildCardInfo( - Card.CardInfo.getDefaultInstance(), - fullName = "Max Mustermann", - regionId = 93, - nuernbergPassId = 99999999, - birthDay = -365 * 10, - expirationDay = 365 * 40 // Equals 14.600 - ) + private val nuernbergBase = + buildCardInfo( + Card.CardInfo.getDefaultInstance(), + fullName = "Max Mustermann", + regionId = 93, + nuernbergPassId = 99999999, + birthDay = -365 * 10, + expirationDay = 365 * 40 // Equals 14.600 + ) + + private val koblenzBase = + buildCardInfo( + Card.CardInfo.getDefaultInstance(), + fullName = "Karla Koblenz", + regionId = 95, + koblenzPassId = "123K", + birthDay = 12213 // 10.06.2003 + ) private fun buildCardInfo( base: Card.CardInfo, @@ -34,6 +46,7 @@ object ExampleCardInfo { birthDay: Int? = null, nuernbergPassId: Int? = null, nuernbergPassIdIdentifier: Card.NuernergPassIdentifier? = null, + koblenzPassId: String? = null, startDay: Int? = null ): Card.CardInfo { val cardInfo = Card.CardInfo.newBuilder(base) @@ -49,40 +62,50 @@ object ExampleCardInfo { nuernbergPassIdIdentifier ) } + if (koblenzPassId != null) extensions.extensionKoblenzPassIdBuilder.setPassId(koblenzPassId) if (startDay != null) extensions.extensionStartDayBuilder.setStartDay(startDay) return cardInfo.buildPartial() } - fun get(cardInfoTestSample: CardInfoTestSample): Card.CardInfo { - return when (cardInfoTestSample) { - CardInfoTestSample.BavarianStandard -> buildCardInfo( - bavarianBase, - expirationDay = 365 * 40, // Equals 14.600 - bavariaCardType = Card.BavariaCardType.STANDARD - ) + fun get(cardInfoTestSample: CardInfoTestSample): Card.CardInfo = + when (cardInfoTestSample) { + CardInfoTestSample.BavarianStandard -> + buildCardInfo( + bavarianBase, + expirationDay = 365 * 40, // Equals 14.600 + bavariaCardType = Card.BavariaCardType.STANDARD + ) - CardInfoTestSample.BavarianGold -> buildCardInfo( - bavarianBase, - bavariaCardType = Card.BavariaCardType.GOLD - ) + CardInfoTestSample.BavarianGold -> + buildCardInfo( + bavarianBase, + bavariaCardType = Card.BavariaCardType.GOLD + ) CardInfoTestSample.Nuernberg -> nuernbergBase - CardInfoTestSample.NuernbergWithStartDay -> buildCardInfo( - nuernbergBase, - startDay = 365 * 2 - ) + CardInfoTestSample.NuernbergWithStartDay -> + buildCardInfo( + nuernbergBase, + startDay = 365 * 2 + ) - CardInfoTestSample.NuernbergWithPassId -> buildCardInfo( - nuernbergBase, - nuernbergPassIdIdentifier = Card.NuernergPassIdentifier.passId, - startDay = 365 * 2 - ) + CardInfoTestSample.NuernbergWithPassId -> + buildCardInfo( + nuernbergBase, + nuernbergPassIdIdentifier = Card.NuernergPassIdentifier.passId, + startDay = 365 * 2 + ) - CardInfoTestSample.NuernbergWithPassNr -> buildCardInfo( - nuernbergBase, - nuernbergPassIdIdentifier = Card.NuernergPassIdentifier.passNr, - startDay = 365 * 2 - ) + CardInfoTestSample.NuernbergWithPassNr -> + buildCardInfo( + nuernbergBase, + nuernbergPassIdIdentifier = Card.NuernergPassIdentifier.passNr, + startDay = 365 * 2 + ) + + CardInfoTestSample.KoblenzPass -> + buildCardInfo( + koblenzBase + ) } - } } diff --git a/backend/src/test/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasherTest.kt b/backend/src/test/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasherTest.kt new file mode 100644 index 000000000..419e1bf4c --- /dev/null +++ b/backend/src/test/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasherTest.kt @@ -0,0 +1,27 @@ +package app.ehrenamtskarte.backend.verification +import Argon2IdHasher +import app.ehrenamtskarte.backend.common.utils.Environment +import app.ehrenamtskarte.backend.common.webservice.KOBLENZ_PEPPER_SYS_ENV +import app.ehrenamtskarte.backend.helper.CardInfoTestSample +import app.ehrenamtskarte.backend.helper.ExampleCardInfo +import io.mockk.every +import io.mockk.mockkObject +import kotlin.test.Test +import kotlin.test.assertEquals + +internal class Argon2IdHasherTest { + @Test + fun isHashingCorrectly() { + mockkObject(Environment) + every { Environment.getVariable(KOBLENZ_PEPPER_SYS_ENV) } returns "123456789ABC" + + assertEquals(Environment.getVariable("KOBLENZ_PEPPER"), "123456789ABC") + + val userData = ExampleCardInfo.get(CardInfoTestSample.KoblenzPass) + + val hash = Argon2IdHasher.hashUserData(userData) + val expectedHash = "\$argon2id\$v=19\$m=16,t=2,p=1\$MTIzNDU2Nzg5QUJD\$xJd35mCTBZT8u+FCGWCnmOtxWzcDTb1Pnt5DHWDap7Y" // This expected output was created with https://argon2.online/ + + assertEquals(expectedHash, hash) + } +} diff --git a/backend/src/test/kotlin/app/ehrenamtskarte/backend/verification/CanonicalJsonTest.kt b/backend/src/test/kotlin/app/ehrenamtskarte/backend/verification/CanonicalJsonTest.kt index 77be8b54f..eaa1753d9 100644 --- a/backend/src/test/kotlin/app/ehrenamtskarte/backend/verification/CanonicalJsonTest.kt +++ b/backend/src/test/kotlin/app/ehrenamtskarte/backend/verification/CanonicalJsonTest.kt @@ -8,7 +8,6 @@ import kotlin.test.assertEquals import kotlin.test.assertFailsWith internal class CanonicalJsonTest { - @Test fun mapEmptyCardInfo() { val cardInfo = Card.CardInfo.newBuilder().build() @@ -18,7 +17,11 @@ internal class CanonicalJsonTest { @Test fun mapCardInfoWithFullName() { val wildName = "Biene Maja ßäЦЧШܐܳܠܰܦ" - val cardInfo = Card.CardInfo.newBuilder().setFullName(wildName).build() + val cardInfo = + Card.CardInfo + .newBuilder() + .setFullName(wildName) + .build() assertEquals(CanonicalJson.messageToMap(cardInfo), mapOf("1" to wildName)) } @@ -31,10 +34,11 @@ internal class CanonicalJsonTest { mapOf( "1" to "Max Mustermann", "2" to "14600", - "3" to mapOf( - "1" to mapOf("1" to "16"), // extensionRegion - "4" to mapOf("1" to "0") // extensionBavariaCardType - ) + "3" to + mapOf( + "1" to mapOf("1" to "16"), // extensionRegion + "4" to mapOf("1" to "0") // extensionBavariaCardType + ) ) ) } @@ -46,10 +50,11 @@ internal class CanonicalJsonTest { CanonicalJson.messageToMap(cardInfo), mapOf( "1" to "Max Mustermann", - "3" to mapOf( - "1" to mapOf("1" to "16"), // extensionRegion - "4" to mapOf("1" to "1") // extensionBavariaCardType - ) + "3" to + mapOf( + "1" to mapOf("1" to "16"), // extensionRegion + "4" to mapOf("1" to "1") // extensionBavariaCardType + ) ) ) } @@ -62,11 +67,12 @@ internal class CanonicalJsonTest { mapOf( "1" to "Max Mustermann", "2" to "14600", - "3" to mapOf( - "1" to mapOf("1" to "93"), // extensionRegion - "2" to mapOf("1" to "-3650"), // extensionBirthday - "3" to mapOf("1" to "99999999") // extensionNuernbergPassId - ) + "3" to + mapOf( + "1" to mapOf("1" to "93"), // extensionRegion + "2" to mapOf("1" to "-3650"), // extensionBirthday + "3" to mapOf("1" to "99999999") // extensionNuernbergPassId + ) ) ) } @@ -79,12 +85,30 @@ internal class CanonicalJsonTest { mapOf( "1" to "Max Mustermann", "2" to "14600", - "3" to mapOf( - "1" to mapOf("1" to "93"), // extensionRegion - "2" to mapOf("1" to "-3650"), // extensionBirthday - "3" to mapOf("1" to "99999999"), // extensionNuernbergPassId - "5" to mapOf("1" to "730") // extensionStartDay - ) + "3" to + mapOf( + "1" to mapOf("1" to "93"), // extensionRegion + "2" to mapOf("1" to "-3650"), // extensionBirthday + "3" to mapOf("1" to "99999999"), // extensionNuernbergPassId + "5" to mapOf("1" to "730") // extensionStartDay + ) + ) + ) + } + + @Test + fun mapCardInfoForKoblenzPass() { + val cardInfo = ExampleCardInfo.get(CardInfoTestSample.KoblenzPass) + assertEquals( + CanonicalJson.messageToMap(cardInfo), + mapOf( + "1" to "Karla Koblenz", + "3" to + mapOf( + "1" to mapOf("1" to "95"), // Koblenz Region + "2" to mapOf("1" to "12213"), // extensionBirthday + "6" to mapOf("1" to "123K") // extensionKoblenzPassId + ) ) ) } @@ -180,16 +204,18 @@ internal class CanonicalJsonTest { @Test fun sortsAndEscapesProperly() { // taken from the rfc: https://www.rfc-editor.org/rfc/rfc8785#section-3.2.3 - val input = mapOf( - "\u20ac" to "Euro Sign", - "\r" to "Carriage Return", - "\ufb33" to "Hebrew Letter Dalet With Dagesh", - "1" to "One", - "\ud83d\ude00" to "Emoji to Grinning Face", - "\u0080" to "Control", - "\u00f6" to "Latin Small Letter O With Diaeresis" - ) - val expected = """{ + val input = + mapOf( + "\u20ac" to "Euro Sign", + "\r" to "Carriage Return", + "\ufb33" to "Hebrew Letter Dalet With Dagesh", + "1" to "One", + "\ud83d\ude00" to "Emoji to Grinning Face", + "\u0080" to "Control", + "\u00f6" to "Latin Small Letter O With Diaeresis" + ) + val expected = + """{ "\r":"Carriage Return", "1":"One", "${"\u0080"}":"Control", @@ -197,7 +223,9 @@ internal class CanonicalJsonTest { "${"\u20ac"}":"Euro Sign", "${"\ud83d\ude00"}":"Emoji to Grinning Face", "${"\ufb33"}":"Hebrew Letter Dalet With Dagesh" - }""".split("\n").joinToString(separator = "") { it.trim() } + }""".split("\n").joinToString(separator = "") { + it.trim() + } val actual = CanonicalJson.serializeToString(input) assertEquals(expected, actual) diff --git a/docs/CreateKoblenzHash.md b/docs/CreateKoblenzHash.md new file mode 100644 index 000000000..671b491ab --- /dev/null +++ b/docs/CreateKoblenzHash.md @@ -0,0 +1,69 @@ +# Creating Hashes for Koblenz Pass Data + +## Steps + +The example data is + +| Parameter | Value | +|--------------|---------------| +| Name | Karla Koblenz | +| Geburtstag | 10.06.2003 | +| Aktenzeichen | 123K | + + +### 1. Collect all data and merge it into an object +```agsl +//Example: +full_name: "Karla Koblenz" +extensions { + extension_region { + regionId: 95 + } + extension_birthday { + birthday: 12213 + } + extension_koblenz_pass_id { + pass_id: "123K" + } +} +``` + +- The full_name must be `FirstnameSpaceLastname`. Every char must exaclty match the user input, as otherwise there is not possiblity to match the data Koblenz transfers with the input the user makes. +e.g. `Karla Koblenz` will match neither with `Karla Lisa Koblenz` nor with `Karlá Koblenz`. +- The birthday is defined in our protobuf [card.proto](../frontend/card.proto) file: It counts the days since the birthday (calculated from 1970-01-01). + All values of this field are valid, including the 0, which indicates that the birthday is on 1970-01-01. Birthdays before 1970-01-01 have negative values. +- extension_region is always 95 for Koblenz +- extension_koblenz_pass_id is set to the "Aktenzeichen" + + +### 2. Convert this object to a Canonical Json + Result should be: + ``` + {"1":"Karla Koblenz","3":{"1":{"1":"95"},"2":{"1":"12213"},"6":{"1":"123K"}}} + ``` + +### 3. Hash it with Argon2id + +Hash with Argon2id with the following parameters: + +| Parameter | Value | +|--------------|--------------------------------------------------------------------------------| +| Version | 19 | +| Iterations | 2 | +| Parallellism | 1 | +| Memory | 16 | +| Salt | Secret Salt will be shared with Koblenz
for the example use `123456789ABC` | + + +### 4. The result... +...for the example data and example salt must be: + +`$argon2id$v=19$m=16,t=2,p=1$MTIzNDU2Nzg5QUJD$KStr3PVblyAh2bIleugv796G+p4pvRNiAON0MHVufVY` + + +## Additional Information + +- Online Argon2 hasher: https://argon2.online/ +- CanonicalJson creation is done in: [CanonicalJson.kt](../backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/CanonicalJson.kt) +- Hashing is done in: [Argon2IdHasher.kt](../backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasher.kt) + diff --git a/specs/backend-api.graphql b/specs/backend-api.graphql index 8d9953438..07dbc6a0f 100644 --- a/specs/backend-api.graphql +++ b/specs/backend-api.graphql @@ -128,6 +128,8 @@ type Mutation { createAdministrator(email: String!, project: String!, regionId: Int, role: Role!, sendWelcomeMail: Boolean!): Boolean! "Creates a new digital entitlementcard and returns it" createCardsByCardInfos(applicationIdToMarkAsProcessed: Int, encodedCardInfos: [String!]!, generateStaticCodes: Boolean!, project: String!): [CardCreationResultModel!]! + "Creates a new digital koblenz card and returns it" + createCardsByUserData(encodedCardInfo: String!, project: String!): Boolean! "Deletes an existing administrator" deleteAdministrator(adminId: Int!, project: String!): Boolean! "Deletes the application with specified id" diff --git a/specs/card.proto b/specs/card.proto index fc13270a8..391db3c73 100644 --- a/specs/card.proto +++ b/specs/card.proto @@ -40,6 +40,10 @@ message NuernbergPassIdExtension { optional NuernergPassIdentifier identifier = 2; } +message KoblenzPassIdExtension { + optional string pass_id = 1; +} + enum NuernergPassIdentifier { passNr = 0; passId = 1; @@ -51,6 +55,7 @@ message CardExtensions { optional NuernbergPassIdExtension extension_nuernberg_pass_id = 3; optional BavariaCardTypeExtension extension_bavaria_card_type = 4; optional StartDayExtension extension_start_day = 5; + optional KoblenzPassIdExtension extension_koblenz_pass_id = 6; } // For our hashing approach, we require that all fields (and subfields, recursively) of CardInfo are marked 'optional'. From f67524ad8ad5b05ecf501db4f1ebc191823c9f0e Mon Sep 17 00:00:00 2001 From: ztefanie Date: Tue, 2 Jul 2024 13:45:36 +0200 Subject: [PATCH 02/10] 1433: Hash Koblenz data without cardinfo --- .../backend/user/KoblenzUser.kt | 7 ++++++ .../backend/verification/Argon2IdHasher.kt | 19 +++++----------- .../backend/verification/CanonicalJson.kt | 6 +++++ .../webservice/schema/CardMutationService.kt | 22 ------------------- .../backend/helper/ExampleCards.kt | 6 ++--- .../backend/helper/ExampleUser.kt | 5 +++++ .../verification/Argon2IdHasherTest.kt | 9 +++----- .../backend/verification/CanonicalJsonTest.kt | 19 +++++----------- docs/CreateKoblenzHash.md | 21 ++++++------------ specs/backend-api.graphql | 2 -- specs/card.proto | 6 ++--- 11 files changed, 44 insertions(+), 78 deletions(-) create mode 100644 backend/src/main/kotlin/app/ehrenamtskarte/backend/user/KoblenzUser.kt create mode 100644 backend/src/test/kotlin/app/ehrenamtskarte/backend/helper/ExampleUser.kt diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/user/KoblenzUser.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/user/KoblenzUser.kt new file mode 100644 index 000000000..75f40a0d2 --- /dev/null +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/user/KoblenzUser.kt @@ -0,0 +1,7 @@ +package app.ehrenamtskarte.backend.user + +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.annotation.JsonPropertyOrder + +@JsonPropertyOrder(alphabetic = true) +data class KoblenzUser(@get:JsonProperty("1") val fullname: String, @get:JsonProperty("2") val birthday: Int, @get:JsonProperty("3") val referenceNumber: String) diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasher.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasher.kt index b5d4ef1af..eed741823 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasher.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasher.kt @@ -1,5 +1,7 @@ + import app.ehrenamtskarte.backend.common.utils.Environment import app.ehrenamtskarte.backend.common.webservice.KOBLENZ_PEPPER_SYS_ENV +import app.ehrenamtskarte.backend.user.KoblenzUser import app.ehrenamtskarte.backend.verification.CanonicalJson import org.bouncycastle.crypto.generators.Argon2BytesGenerator import org.bouncycastle.crypto.params.Argon2Parameters @@ -48,12 +50,9 @@ class Argon2IdHasher { return stringBuilder.toString() } - fun hashUserData(cardInfo: Card.CardInfo): String? { - val canonicalJson = CanonicalJson.messageToMap(cardInfo) + fun hashKoblenzUserData(userData: KoblenzUser): String? { + val canonicalJson = CanonicalJson.koblenzUserToString(userData) val hashLength = 32 - if (!isCanonicalJsonValid(canonicalJson)) { - throw Exception("Invalid Json input for hashing") - } val pepper = Environment.getVariable(KOBLENZ_PEPPER_SYS_ENV) // TODO handle if Null val pepperByteArray = pepper?.toByteArray(StandardCharsets.UTF_8) @@ -70,16 +69,8 @@ class Argon2IdHasher { val generator = Argon2BytesGenerator() generator.init(params) val result = ByteArray(hashLength) - generator.generateBytes(CanonicalJson.serializeToString(canonicalJson).toCharArray(), result) + generator.generateBytes(canonicalJson.toCharArray(), result) return encode(result, params) } - - private fun isCanonicalJsonValid(canonicalJson: Map): Boolean { - val hasName = canonicalJson.get("1") != null - val hasExtensions = canonicalJson.get("3") as? Map - val hasKoblenzPassExtension = hasExtensions?.get("6") as? Map - val hasKoblenzPassId = hasKoblenzPassExtension?.get("1") != null - return hasName && hasKoblenzPassId - } } } diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/CanonicalJson.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/CanonicalJson.kt index 1dcd63be3..bd0282ba6 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/CanonicalJson.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/CanonicalJson.kt @@ -1,5 +1,7 @@ package app.ehrenamtskarte.backend.verification +import app.ehrenamtskarte.backend.user.KoblenzUser +import com.fasterxml.jackson.databind.ObjectMapper import com.google.protobuf.Descriptors import com.google.protobuf.Descriptors.FieldDescriptor.Type import com.google.protobuf.GeneratedMessageV3 @@ -89,6 +91,10 @@ class CanonicalJson { } } + fun koblenzUserToString(koblenzUser: KoblenzUser): String { + return ObjectMapper().writeValueAsString(koblenzUser) + } + fun serializeToString(message: GeneratedMessageV3) = serializeToString(messageToMap(message)) /** diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/webservice/schema/CardMutationService.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/webservice/schema/CardMutationService.kt index d0efacf66..a8ac4d33d 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/webservice/schema/CardMutationService.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/webservice/schema/CardMutationService.kt @@ -1,14 +1,11 @@ package app.ehrenamtskarte.backend.verification.webservice.schema -import Argon2IdHasher import Card import app.ehrenamtskarte.backend.application.database.repos.ApplicationRepository import app.ehrenamtskarte.backend.auth.database.AdministratorEntity import app.ehrenamtskarte.backend.auth.service.Authorizer import app.ehrenamtskarte.backend.common.webservice.GraphQLContext -import app.ehrenamtskarte.backend.common.webservice.KOBLENZ_PASS_PROJECT import app.ehrenamtskarte.backend.exception.service.ForbiddenException -import app.ehrenamtskarte.backend.exception.service.NotKoblenzProjectException import app.ehrenamtskarte.backend.exception.service.ProjectNotFoundException import app.ehrenamtskarte.backend.exception.service.UnauthorizedException import app.ehrenamtskarte.backend.exception.webservice.exceptions.InvalidCardHashException @@ -166,25 +163,6 @@ class CardMutationService { return activationCodes } - @GraphQLDescription("Creates a new digital koblenz card and returns it") - fun createCardsByUserData( - dfe: DataFetchingEnvironment, - project: String, - encodedCardInfo: String - ): Boolean { // CardCreationResultModel { - val context = dfe.getContext() - val projectConfig = - context.backendConfiguration.projects.find { it.id == project } - ?: throw ProjectNotFoundException(project) - if (project != KOBLENZ_PASS_PROJECT) { - throw NotKoblenzProjectException() - } - val cardInfoBytes = encodedCardInfo.decodeBase64Bytes() - val cardInfo = Card.CardInfo.parseFrom(cardInfoBytes) - val hashedUserData = Argon2IdHasher.hashUserData(cardInfo) - return false // Will be done in #1421 - } - @GraphQLDescription("Activate a dynamic entitlement card") fun activateCard( project: String, diff --git a/backend/src/test/kotlin/app/ehrenamtskarte/backend/helper/ExampleCards.kt b/backend/src/test/kotlin/app/ehrenamtskarte/backend/helper/ExampleCards.kt index 4dc5ccdac..8813f1c66 100644 --- a/backend/src/test/kotlin/app/ehrenamtskarte/backend/helper/ExampleCards.kt +++ b/backend/src/test/kotlin/app/ehrenamtskarte/backend/helper/ExampleCards.kt @@ -33,7 +33,7 @@ object ExampleCardInfo { Card.CardInfo.getDefaultInstance(), fullName = "Karla Koblenz", regionId = 95, - koblenzPassId = "123K", + koblenzReferenceNumber = "123K", birthDay = 12213 // 10.06.2003 ) @@ -46,7 +46,7 @@ object ExampleCardInfo { birthDay: Int? = null, nuernbergPassId: Int? = null, nuernbergPassIdIdentifier: Card.NuernergPassIdentifier? = null, - koblenzPassId: String? = null, + koblenzReferenceNumber: String? = null, startDay: Int? = null ): Card.CardInfo { val cardInfo = Card.CardInfo.newBuilder(base) @@ -62,7 +62,7 @@ object ExampleCardInfo { nuernbergPassIdIdentifier ) } - if (koblenzPassId != null) extensions.extensionKoblenzPassIdBuilder.setPassId(koblenzPassId) + if (koblenzReferenceNumber != null) extensions.extensionKoblenzReferenceNumberBuilder.setReferenceNumber(koblenzReferenceNumber) if (startDay != null) extensions.extensionStartDayBuilder.setStartDay(startDay) return cardInfo.buildPartial() } diff --git a/backend/src/test/kotlin/app/ehrenamtskarte/backend/helper/ExampleUser.kt b/backend/src/test/kotlin/app/ehrenamtskarte/backend/helper/ExampleUser.kt new file mode 100644 index 000000000..3000b2bc1 --- /dev/null +++ b/backend/src/test/kotlin/app/ehrenamtskarte/backend/helper/ExampleUser.kt @@ -0,0 +1,5 @@ +package app.ehrenamtskarte.backend.helper + +import app.ehrenamtskarte.backend.user.KoblenzUser + +val koblenzTestUser = KoblenzUser("Karla Koblenz", 12213, "123K") diff --git a/backend/src/test/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasherTest.kt b/backend/src/test/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasherTest.kt index 419e1bf4c..00e452a49 100644 --- a/backend/src/test/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasherTest.kt +++ b/backend/src/test/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasherTest.kt @@ -2,8 +2,7 @@ package app.ehrenamtskarte.backend.verification import Argon2IdHasher import app.ehrenamtskarte.backend.common.utils.Environment import app.ehrenamtskarte.backend.common.webservice.KOBLENZ_PEPPER_SYS_ENV -import app.ehrenamtskarte.backend.helper.CardInfoTestSample -import app.ehrenamtskarte.backend.helper.ExampleCardInfo +import app.ehrenamtskarte.backend.user.KoblenzUser import io.mockk.every import io.mockk.mockkObject import kotlin.test.Test @@ -17,10 +16,8 @@ internal class Argon2IdHasherTest { assertEquals(Environment.getVariable("KOBLENZ_PEPPER"), "123456789ABC") - val userData = ExampleCardInfo.get(CardInfoTestSample.KoblenzPass) - - val hash = Argon2IdHasher.hashUserData(userData) - val expectedHash = "\$argon2id\$v=19\$m=16,t=2,p=1\$MTIzNDU2Nzg5QUJD\$xJd35mCTBZT8u+FCGWCnmOtxWzcDTb1Pnt5DHWDap7Y" // This expected output was created with https://argon2.online/ + val hash = Argon2IdHasher.hashKoblenzUserData(KoblenzUser("Karla Koblenz", 12213, "123K")) + val expectedHash = "\$argon2id\$v=19\$m=16,t=2,p=1\$MTIzNDU2Nzg5QUJD\$UIOJZIsSL8vXcuCB82xZ5E8tpH6sQd3d4U0uC02DP40" // This expected output was created with https://argon2.online/ assertEquals(expectedHash, hash) } diff --git a/backend/src/test/kotlin/app/ehrenamtskarte/backend/verification/CanonicalJsonTest.kt b/backend/src/test/kotlin/app/ehrenamtskarte/backend/verification/CanonicalJsonTest.kt index eaa1753d9..852a4ff6c 100644 --- a/backend/src/test/kotlin/app/ehrenamtskarte/backend/verification/CanonicalJsonTest.kt +++ b/backend/src/test/kotlin/app/ehrenamtskarte/backend/verification/CanonicalJsonTest.kt @@ -3,6 +3,8 @@ package app.ehrenamtskarte.backend.verification import Card import app.ehrenamtskarte.backend.helper.CardInfoTestSample import app.ehrenamtskarte.backend.helper.ExampleCardInfo +import app.ehrenamtskarte.backend.helper.koblenzTestUser +import app.ehrenamtskarte.backend.verification.CanonicalJson.Companion.koblenzUserToString import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -97,20 +99,9 @@ internal class CanonicalJsonTest { } @Test - fun mapCardInfoForKoblenzPass() { - val cardInfo = ExampleCardInfo.get(CardInfoTestSample.KoblenzPass) - assertEquals( - CanonicalJson.messageToMap(cardInfo), - mapOf( - "1" to "Karla Koblenz", - "3" to - mapOf( - "1" to mapOf("1" to "95"), // Koblenz Region - "2" to mapOf("1" to "12213"), // extensionBirthday - "6" to mapOf("1" to "123K") // extensionKoblenzPassId - ) - ) - ) + fun mapUserInfoForKoblenzPass() { + val expected = koblenzUserToString(koblenzTestUser) + assertEquals("{\"1\":\"Karla Koblenz\",\"2\":12213,\"3\":\"123K\"}", expected) } @Test diff --git a/docs/CreateKoblenzHash.md b/docs/CreateKoblenzHash.md index 671b491ab..fa6f45514 100644 --- a/docs/CreateKoblenzHash.md +++ b/docs/CreateKoblenzHash.md @@ -14,17 +14,10 @@ The example data is ### 1. Collect all data and merge it into an object ```agsl //Example: -full_name: "Karla Koblenz" -extensions { - extension_region { - regionId: 95 - } - extension_birthday { +{ + full_name: "Karla Koblenz" birthday: 12213 - } - extension_koblenz_pass_id { - pass_id: "123K" - } + referenceNumber: "123K" } ``` @@ -32,14 +25,13 @@ extensions { e.g. `Karla Koblenz` will match neither with `Karla Lisa Koblenz` nor with `Karlá Koblenz`. - The birthday is defined in our protobuf [card.proto](../frontend/card.proto) file: It counts the days since the birthday (calculated from 1970-01-01). All values of this field are valid, including the 0, which indicates that the birthday is on 1970-01-01. Birthdays before 1970-01-01 have negative values. -- extension_region is always 95 for Koblenz -- extension_koblenz_pass_id is set to the "Aktenzeichen" +- referenceNumber is set to the "Aktenzeichen" ### 2. Convert this object to a Canonical Json Result should be: ``` - {"1":"Karla Koblenz","3":{"1":{"1":"95"},"2":{"1":"12213"},"6":{"1":"123K"}}} + {"1":"Karla Koblenz","2":12213,"3":"123K"} ``` ### 3. Hash it with Argon2id @@ -52,13 +44,14 @@ Hash with Argon2id with the following parameters: | Iterations | 2 | | Parallellism | 1 | | Memory | 16 | +| HashLength | 32 | | Salt | Secret Salt will be shared with Koblenz
for the example use `123456789ABC` | ### 4. The result... ...for the example data and example salt must be: -`$argon2id$v=19$m=16,t=2,p=1$MTIzNDU2Nzg5QUJD$KStr3PVblyAh2bIleugv796G+p4pvRNiAON0MHVufVY` +`$argon2id$v=19$m=16,t=2,p=1$MTIzNDU2Nzg5QUJD$UIOJZIsSL8vXcuCB82xZ5E8tpH6sQd3d4U0uC02DP40` ## Additional Information diff --git a/specs/backend-api.graphql b/specs/backend-api.graphql index 07dbc6a0f..8d9953438 100644 --- a/specs/backend-api.graphql +++ b/specs/backend-api.graphql @@ -128,8 +128,6 @@ type Mutation { createAdministrator(email: String!, project: String!, regionId: Int, role: Role!, sendWelcomeMail: Boolean!): Boolean! "Creates a new digital entitlementcard and returns it" createCardsByCardInfos(applicationIdToMarkAsProcessed: Int, encodedCardInfos: [String!]!, generateStaticCodes: Boolean!, project: String!): [CardCreationResultModel!]! - "Creates a new digital koblenz card and returns it" - createCardsByUserData(encodedCardInfo: String!, project: String!): Boolean! "Deletes an existing administrator" deleteAdministrator(adminId: Int!, project: String!): Boolean! "Deletes the application with specified id" diff --git a/specs/card.proto b/specs/card.proto index 391db3c73..e35742344 100644 --- a/specs/card.proto +++ b/specs/card.proto @@ -40,8 +40,8 @@ message NuernbergPassIdExtension { optional NuernergPassIdentifier identifier = 2; } -message KoblenzPassIdExtension { - optional string pass_id = 1; +message KoblenzReferenceNumberExtension { + optional string reference_number = 1; } enum NuernergPassIdentifier { @@ -55,7 +55,7 @@ message CardExtensions { optional NuernbergPassIdExtension extension_nuernberg_pass_id = 3; optional BavariaCardTypeExtension extension_bavaria_card_type = 4; optional StartDayExtension extension_start_day = 5; - optional KoblenzPassIdExtension extension_koblenz_pass_id = 6; + optional KoblenzReferenceNumberExtension extension_koblenz_reference_number = 6; } // For our hashing approach, we require that all fields (and subfields, recursively) of CardInfo are marked 'optional'. From fbe2385811aad594ec08e8becabcc95a3c15e963 Mon Sep 17 00:00:00 2001 From: ztefanie Date: Tue, 9 Jul 2024 10:05:06 +0200 Subject: [PATCH 03/10] 1433: Adjust canonical json creation --- .../app/ehrenamtskarte/backend/verification/Argon2IdHasher.kt | 2 +- .../app/ehrenamtskarte/backend/verification/CanonicalJson.kt | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasher.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasher.kt index eed741823..7bf2305e0 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasher.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasher.kt @@ -69,7 +69,7 @@ class Argon2IdHasher { val generator = Argon2BytesGenerator() generator.init(params) val result = ByteArray(hashLength) - generator.generateBytes(canonicalJson.toCharArray(), result) + generator.generateBytes(canonicalJson.toByteArray(), result) return encode(result, params) } } diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/CanonicalJson.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/CanonicalJson.kt index bd0282ba6..0572a73a4 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/CanonicalJson.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/CanonicalJson.kt @@ -1,6 +1,7 @@ package app.ehrenamtskarte.backend.verification import app.ehrenamtskarte.backend.user.KoblenzUser +import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper import com.google.protobuf.Descriptors import com.google.protobuf.Descriptors.FieldDescriptor.Type @@ -92,7 +93,8 @@ class CanonicalJson { } fun koblenzUserToString(koblenzUser: KoblenzUser): String { - return ObjectMapper().writeValueAsString(koblenzUser) + val map = ObjectMapper().convertValue(koblenzUser, object : TypeReference>() {}) + return serializeToString(map) } fun serializeToString(message: GeneratedMessageV3) = serializeToString(messageToMap(message)) From dc04b092e8c8bcd8f3d681b7b4e2c1a6cf6c4917 Mon Sep 17 00:00:00 2001 From: ztefanie Date: Thu, 11 Jul 2024 10:21:37 +0200 Subject: [PATCH 04/10] 1433: Remove salt from hash --- backend/build.gradle.kts | 1 - .../ehrenamtskarte/backend/common/webservice/Constants.kt | 2 +- .../ehrenamtskarte/backend/verification/Argon2IdHasher.kt | 5 +---- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts index e929220ec..0fb643c88 100644 --- a/backend/build.gradle.kts +++ b/backend/build.gradle.kts @@ -71,7 +71,6 @@ dependencies { implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.15.2") implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.15.2") implementation("de.grundid.opendatalab:geojson-jackson:1.14") - implementation("commons-codec:commons-codec:1.17.0") implementation("com.eatthepath:java-otp:0.4.0") // dynamic card verification implementation("com.auth0:java-jwt:4.4.0") // JSON web tokens diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/common/webservice/Constants.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/common/webservice/Constants.kt index 60d04a647..bc7a538ab 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/common/webservice/Constants.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/common/webservice/Constants.kt @@ -2,7 +2,7 @@ package app.ehrenamtskarte.backend.common.webservice const val EAK_BAYERN_PROJECT = "bayern.ehrenamtskarte.app" const val NUERNBERG_PASS_PROJECT = "nuernberg.sozialpass.app" -const val KOBLENZ_PASS_PROJECT = "koblenz.pass.app" +const val KOBLENZ_PASS_PROJECT = "koblenz.sozialpass.app" const val KOBLENZ_PEPPER_SYS_ENV = "KOBLENZ_PEPPER" const val SHOWCASE_PROJECT = "showcase.entitlementcard.app" const val DEFAULT_PROJECT = EAK_BAYERN_PROJECT diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasher.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasher.kt index 7bf2305e0..676ba06da 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasher.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasher.kt @@ -11,7 +11,7 @@ import java.util.Base64 class Argon2IdHasher { companion object { /** - * Copied from spring-security Argon2EncodingUtils.java licenced under Apache 2.0 + * Copied from spring-security Argon2EncodingUtils.java licenced under Apache 2.0, removed the salt from the result * * Encodes a raw Argon2-hash and its parameters into the standard Argon2-hash-string * as specified in the reference implementation @@ -43,9 +43,6 @@ class Argon2IdHasher { .append(parameters.iterations) .append(",p=") .append(parameters.lanes) - if (parameters.salt != null) { - stringBuilder.append("$").append(b64encoder.encodeToString(parameters.salt)) - } stringBuilder.append("$").append(b64encoder.encodeToString(hash)) return stringBuilder.toString() } From eb2d7bbd19f7ee123a4725e74ba8bb473252022f Mon Sep 17 00:00:00 2001 From: ztefanie Date: Mon, 15 Jul 2024 10:06:17 +0200 Subject: [PATCH 05/10] Update docs/CreateKoblenzHash.md Co-authored-by: Michael Markl --- docs/CreateKoblenzHash.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CreateKoblenzHash.md b/docs/CreateKoblenzHash.md index fa6f45514..1585e8b59 100644 --- a/docs/CreateKoblenzHash.md +++ b/docs/CreateKoblenzHash.md @@ -21,7 +21,7 @@ The example data is } ``` -- The full_name must be `FirstnameSpaceLastname`. Every char must exaclty match the user input, as otherwise there is not possiblity to match the data Koblenz transfers with the input the user makes. +- The full_name must be `FirstnameSpaceLastname`. Every char must exactly match the user input, as otherwise there is not possibility to match the data Koblenz transfers with the input the user makes. e.g. `Karla Koblenz` will match neither with `Karla Lisa Koblenz` nor with `Karlá Koblenz`. - The birthday is defined in our protobuf [card.proto](../frontend/card.proto) file: It counts the days since the birthday (calculated from 1970-01-01). All values of this field are valid, including the 0, which indicates that the birthday is on 1970-01-01. Birthdays before 1970-01-01 have negative values. From 092df0e170ee8a8f5c1a68e6f3faf53f6975ccff Mon Sep 17 00:00:00 2001 From: ztefanie Date: Tue, 23 Jul 2024 13:05:32 +0200 Subject: [PATCH 06/10] 1433: Small adjustments --- .../ehrenamtskarte/backend/verification/Argon2IdHasher.kt | 6 +++--- .../backend/verification/Argon2IdHasherTest.kt | 3 +-- docs/CreateKoblenzHash.md | 6 +++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasher.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasher.kt index 676ba06da..c19979576 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasher.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasher.kt @@ -20,7 +20,7 @@ class Argon2IdHasher { * {@code $argon2[$v=]$m=,t=,p=$$} **/ @Throws(IllegalArgumentException::class) - fun encode( + private fun encodeWithoutSalt( hash: ByteArray?, parameters: Argon2Parameters ): String? { @@ -60,14 +60,14 @@ class Argon2IdHasher { .withIterations(2) .withSalt(pepperByteArray) .withParallelism(1) - .withMemoryAsKB(16) + .withMemoryAsKB(19) .build() val generator = Argon2BytesGenerator() generator.init(params) val result = ByteArray(hashLength) generator.generateBytes(canonicalJson.toByteArray(), result) - return encode(result, params) + return encodeWithoutSalt(result, params) } } } diff --git a/backend/src/test/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasherTest.kt b/backend/src/test/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasherTest.kt index 00e452a49..862d2f91e 100644 --- a/backend/src/test/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasherTest.kt +++ b/backend/src/test/kotlin/app/ehrenamtskarte/backend/verification/Argon2IdHasherTest.kt @@ -17,8 +17,7 @@ internal class Argon2IdHasherTest { assertEquals(Environment.getVariable("KOBLENZ_PEPPER"), "123456789ABC") val hash = Argon2IdHasher.hashKoblenzUserData(KoblenzUser("Karla Koblenz", 12213, "123K")) - val expectedHash = "\$argon2id\$v=19\$m=16,t=2,p=1\$MTIzNDU2Nzg5QUJD\$UIOJZIsSL8vXcuCB82xZ5E8tpH6sQd3d4U0uC02DP40" // This expected output was created with https://argon2.online/ - + val expectedHash = "\$argon2id\$v=19\$m=19,t=2,p=1\$78SNlzz8llqPwetRlp8Lnag/WZGPc5XbimYbShpwVMQ" // This expected output was created with https://argon2.online/ assertEquals(expectedHash, hash) } } diff --git a/docs/CreateKoblenzHash.md b/docs/CreateKoblenzHash.md index 1585e8b59..482152048 100644 --- a/docs/CreateKoblenzHash.md +++ b/docs/CreateKoblenzHash.md @@ -43,15 +43,15 @@ Hash with Argon2id with the following parameters: | Version | 19 | | Iterations | 2 | | Parallellism | 1 | -| Memory | 16 | +| Memory | 19 | | HashLength | 32 | | Salt | Secret Salt will be shared with Koblenz
for the example use `123456789ABC` | ### 4. The result... -...for the example data and example salt must be: +...for the example data and example salt must be (output in encoded form with salt removed): -`$argon2id$v=19$m=16,t=2,p=1$MTIzNDU2Nzg5QUJD$UIOJZIsSL8vXcuCB82xZ5E8tpH6sQd3d4U0uC02DP40` +`$argon2id$v=19$m=16,t=2,p=1$UIOJZIsSL8vXcuCB82xZ5E8tpH6sQd3d4U0uC02DP40` ## Additional Information From d3879e26338be0b3a991c7099745a70e39c56b11 Mon Sep 17 00:00:00 2001 From: Andy Date: Mon, 29 Jul 2024 16:00:45 +0200 Subject: [PATCH 07/10] 1433: add project config for koblenz, fix import, adjust hash in the docs --- .../ehrenamtskarte/backend/cards/Argon2IdHasher.kt | 6 +++--- backend/src/main/resources/config/config.yml | 11 +++++++++++ .../ehrenamtskarte/backend/cards/CanonicalJsonTest.kt | 2 +- docs/CreateKoblenzHash.md | 2 +- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/cards/Argon2IdHasher.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/cards/Argon2IdHasher.kt index c19979576..373063f07 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/cards/Argon2IdHasher.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/cards/Argon2IdHasher.kt @@ -1,8 +1,8 @@ +import app.ehrenamtskarte.backend.cards.CanonicalJson import app.ehrenamtskarte.backend.common.utils.Environment import app.ehrenamtskarte.backend.common.webservice.KOBLENZ_PEPPER_SYS_ENV import app.ehrenamtskarte.backend.user.KoblenzUser -import app.ehrenamtskarte.backend.verification.CanonicalJson import org.bouncycastle.crypto.generators.Argon2BytesGenerator import org.bouncycastle.crypto.params.Argon2Parameters import java.nio.charset.StandardCharsets @@ -51,8 +51,8 @@ class Argon2IdHasher { val canonicalJson = CanonicalJson.koblenzUserToString(userData) val hashLength = 32 - val pepper = Environment.getVariable(KOBLENZ_PEPPER_SYS_ENV) // TODO handle if Null - val pepperByteArray = pepper?.toByteArray(StandardCharsets.UTF_8) + val pepper = Environment.getVariable(KOBLENZ_PEPPER_SYS_ENV) ?: throw Exception("No koblenz pepper found") + val pepperByteArray = pepper.toByteArray(StandardCharsets.UTF_8) val params = Argon2Parameters .Builder(Argon2Parameters.ARGON2_id) diff --git a/backend/src/main/resources/config/config.yml b/backend/src/main/resources/config/config.yml index cde974f35..45b864b56 100644 --- a/backend/src/main/resources/config/config.yml +++ b/backend/src/main/resources/config/config.yml @@ -38,6 +38,17 @@ projects: port: 587 username: OVERRIDE_IN_LOCAL_CONFIG password: OVERRIDE_IN_LOCAL_CONFIG + - id: koblenz.sozialpass.app + importUrl: "" + pipelineName: SozialpassKoblenz + administrationBaseUrl: https://koblenz.sozialpass.app + administrationName: Koblenz-Pass-Verwaltung + timezone: "Europe/Berlin" + smtp: + host: mail.sozialpass.app + port: 587 + username: OVERRIDE_IN_LOCAL_CONFIG + password: OVERRIDE_IN_LOCAL_CONFIG - id: showcase.entitlementcard.app importUrl: https://example.com pipelineName: BerechtigungskarteShowcase diff --git a/backend/src/test/kotlin/app/ehrenamtskarte/backend/cards/CanonicalJsonTest.kt b/backend/src/test/kotlin/app/ehrenamtskarte/backend/cards/CanonicalJsonTest.kt index 463becbd1..4a13257e9 100644 --- a/backend/src/test/kotlin/app/ehrenamtskarte/backend/cards/CanonicalJsonTest.kt +++ b/backend/src/test/kotlin/app/ehrenamtskarte/backend/cards/CanonicalJsonTest.kt @@ -1,10 +1,10 @@ package app.ehrenamtskarte.backend.cards import Card +import app.ehrenamtskarte.backend.cards.CanonicalJson.Companion.koblenzUserToString import app.ehrenamtskarte.backend.helper.CardInfoTestSample import app.ehrenamtskarte.backend.helper.ExampleCardInfo import app.ehrenamtskarte.backend.helper.koblenzTestUser -import app.ehrenamtskarte.backend.verification.CanonicalJson.Companion.koblenzUserToString import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith diff --git a/docs/CreateKoblenzHash.md b/docs/CreateKoblenzHash.md index 482152048..52f679b6c 100644 --- a/docs/CreateKoblenzHash.md +++ b/docs/CreateKoblenzHash.md @@ -51,7 +51,7 @@ Hash with Argon2id with the following parameters: ### 4. The result... ...for the example data and example salt must be (output in encoded form with salt removed): -`$argon2id$v=19$m=16,t=2,p=1$UIOJZIsSL8vXcuCB82xZ5E8tpH6sQd3d4U0uC02DP40` +`$argon2id$v=19$m=19,t=2,p=1$78SNlzz8llqPwetRlp8Lnag/WZGPc5XbimYbShpwVMQ` ## Additional Information From c630dec2b9c7998f79f077b4ef6a1f227b12b221 Mon Sep 17 00:00:00 2001 From: Andy Date: Mon, 29 Jul 2024 17:27:59 +0200 Subject: [PATCH 08/10] 1433: add regionIdentifier for koblenz, since its needed for user data import --- .../ehrenamtskarte/backend/regions/database/Setup.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/regions/database/Setup.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/regions/database/Setup.kt index 595ec7d90..8080116ad 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/regions/database/Setup.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/regions/database/Setup.kt @@ -14,6 +14,7 @@ fun insertOrUpdateRegions() { regionProjectId: String, regionName: String, regionPrefix: String, + regionKey: String?, regionWebsite: String ) { val project = @@ -25,13 +26,14 @@ fun insertOrUpdateRegions() { projectId = project.id name = regionName prefix = regionPrefix - regionIdentifier = null + regionIdentifier = regionKey website = regionWebsite } } else { region.name = regionName region.prefix = regionPrefix region.website = regionWebsite + region.regionIdentifier = regionKey } } @@ -56,8 +58,8 @@ fun insertOrUpdateRegions() { dbRegion.website = eakRegion[3] } } - - createOrUpdateRegion(NUERNBERG_PASS_PROJECT, "Nürnberg", "Stadt", "https://nuernberg.de") - createOrUpdateRegion(KOBLENZ_PASS_PROJECT, "Koblenz", "Stadt", "https://koblenz.de/") + // TODO #1551: Adjust regionidentifier_unique constraint + createOrUpdateRegion(NUERNBERG_PASS_PROJECT, "Nürnberg", "Stadt", null, "https://nuernberg.de") + createOrUpdateRegion(KOBLENZ_PASS_PROJECT, "Koblenz", "Stadt", "07111", "https://koblenz.de/") } } From 6695e769e1f05441b805eea3c44b8255def442b6 Mon Sep 17 00:00:00 2001 From: Andy Date: Mon, 29 Jul 2024 17:36:03 +0200 Subject: [PATCH 09/10] 1433: increase memory size --- .../app/ehrenamtskarte/backend/cards/Argon2IdHasher.kt | 2 +- .../app/ehrenamtskarte/backend/cards/Argon2IdHasherTest.kt | 2 +- docs/CreateKoblenzHash.md | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/cards/Argon2IdHasher.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/cards/Argon2IdHasher.kt index 373063f07..838b5f0b0 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/cards/Argon2IdHasher.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/cards/Argon2IdHasher.kt @@ -60,7 +60,7 @@ class Argon2IdHasher { .withIterations(2) .withSalt(pepperByteArray) .withParallelism(1) - .withMemoryAsKB(19) + .withMemoryAsKB(19456) .build() val generator = Argon2BytesGenerator() diff --git a/backend/src/test/kotlin/app/ehrenamtskarte/backend/cards/Argon2IdHasherTest.kt b/backend/src/test/kotlin/app/ehrenamtskarte/backend/cards/Argon2IdHasherTest.kt index 862d2f91e..58523d319 100644 --- a/backend/src/test/kotlin/app/ehrenamtskarte/backend/cards/Argon2IdHasherTest.kt +++ b/backend/src/test/kotlin/app/ehrenamtskarte/backend/cards/Argon2IdHasherTest.kt @@ -17,7 +17,7 @@ internal class Argon2IdHasherTest { assertEquals(Environment.getVariable("KOBLENZ_PEPPER"), "123456789ABC") val hash = Argon2IdHasher.hashKoblenzUserData(KoblenzUser("Karla Koblenz", 12213, "123K")) - val expectedHash = "\$argon2id\$v=19\$m=19,t=2,p=1\$78SNlzz8llqPwetRlp8Lnag/WZGPc5XbimYbShpwVMQ" // This expected output was created with https://argon2.online/ + val expectedHash = "\$argon2id\$v=19\$m=19456,t=2,p=1\$57YPIKvU/XE9h7/JA0tZFT2TzpwBQfYAW6K+ojXBh5w" // This expected output was created with https://argon2.online/ assertEquals(expectedHash, hash) } } diff --git a/docs/CreateKoblenzHash.md b/docs/CreateKoblenzHash.md index 52f679b6c..1941bccca 100644 --- a/docs/CreateKoblenzHash.md +++ b/docs/CreateKoblenzHash.md @@ -43,7 +43,7 @@ Hash with Argon2id with the following parameters: | Version | 19 | | Iterations | 2 | | Parallellism | 1 | -| Memory | 19 | +| Memory | 19456 (19MiB) | | HashLength | 32 | | Salt | Secret Salt will be shared with Koblenz
for the example use `123456789ABC` | @@ -51,8 +51,7 @@ Hash with Argon2id with the following parameters: ### 4. The result... ...for the example data and example salt must be (output in encoded form with salt removed): -`$argon2id$v=19$m=19,t=2,p=1$78SNlzz8llqPwetRlp8Lnag/WZGPc5XbimYbShpwVMQ` - +`$argon2id$v=19$m=19456,t=2,p=1$57YPIKvU/XE9h7/JA0tZFT2TzpwBQfYAW6K+ojXBh5w` ## Additional Information From 47ea4d2732f7318f34201627636c6518ee3ac422 Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 30 Jul 2024 10:02:01 +0200 Subject: [PATCH 10/10] 1433: move user class to userdata --- .../kotlin/app/ehrenamtskarte/backend/cards/Argon2IdHasher.kt | 2 +- .../kotlin/app/ehrenamtskarte/backend/cards/CanonicalJson.kt | 2 +- .../ehrenamtskarte/backend/{user => userdata}/KoblenzUser.kt | 2 +- .../app/ehrenamtskarte/backend/cards/Argon2IdHasherTest.kt | 2 +- .../kotlin/app/ehrenamtskarte/backend/helper/ExampleUser.kt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename backend/src/main/kotlin/app/ehrenamtskarte/backend/{user => userdata}/KoblenzUser.kt (87%) diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/cards/Argon2IdHasher.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/cards/Argon2IdHasher.kt index 838b5f0b0..7f3ab524b 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/cards/Argon2IdHasher.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/cards/Argon2IdHasher.kt @@ -2,7 +2,7 @@ import app.ehrenamtskarte.backend.cards.CanonicalJson import app.ehrenamtskarte.backend.common.utils.Environment import app.ehrenamtskarte.backend.common.webservice.KOBLENZ_PEPPER_SYS_ENV -import app.ehrenamtskarte.backend.user.KoblenzUser +import app.ehrenamtskarte.backend.userdata.KoblenzUser import org.bouncycastle.crypto.generators.Argon2BytesGenerator import org.bouncycastle.crypto.params.Argon2Parameters import java.nio.charset.StandardCharsets diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/cards/CanonicalJson.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/cards/CanonicalJson.kt index eb3b91b2d..12ce833f0 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/cards/CanonicalJson.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/cards/CanonicalJson.kt @@ -1,6 +1,6 @@ package app.ehrenamtskarte.backend.cards -import app.ehrenamtskarte.backend.user.KoblenzUser +import app.ehrenamtskarte.backend.userdata.KoblenzUser import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper import com.google.protobuf.Descriptors diff --git a/backend/src/main/kotlin/app/ehrenamtskarte/backend/user/KoblenzUser.kt b/backend/src/main/kotlin/app/ehrenamtskarte/backend/userdata/KoblenzUser.kt similarity index 87% rename from backend/src/main/kotlin/app/ehrenamtskarte/backend/user/KoblenzUser.kt rename to backend/src/main/kotlin/app/ehrenamtskarte/backend/userdata/KoblenzUser.kt index 75f40a0d2..f2d9983c7 100644 --- a/backend/src/main/kotlin/app/ehrenamtskarte/backend/user/KoblenzUser.kt +++ b/backend/src/main/kotlin/app/ehrenamtskarte/backend/userdata/KoblenzUser.kt @@ -1,4 +1,4 @@ -package app.ehrenamtskarte.backend.user +package app.ehrenamtskarte.backend.userdata import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonPropertyOrder diff --git a/backend/src/test/kotlin/app/ehrenamtskarte/backend/cards/Argon2IdHasherTest.kt b/backend/src/test/kotlin/app/ehrenamtskarte/backend/cards/Argon2IdHasherTest.kt index 58523d319..fcdc190ad 100644 --- a/backend/src/test/kotlin/app/ehrenamtskarte/backend/cards/Argon2IdHasherTest.kt +++ b/backend/src/test/kotlin/app/ehrenamtskarte/backend/cards/Argon2IdHasherTest.kt @@ -2,7 +2,7 @@ package app.ehrenamtskarte.backend.verification import Argon2IdHasher import app.ehrenamtskarte.backend.common.utils.Environment import app.ehrenamtskarte.backend.common.webservice.KOBLENZ_PEPPER_SYS_ENV -import app.ehrenamtskarte.backend.user.KoblenzUser +import app.ehrenamtskarte.backend.userdata.KoblenzUser import io.mockk.every import io.mockk.mockkObject import kotlin.test.Test diff --git a/backend/src/test/kotlin/app/ehrenamtskarte/backend/helper/ExampleUser.kt b/backend/src/test/kotlin/app/ehrenamtskarte/backend/helper/ExampleUser.kt index 3000b2bc1..531797dfd 100644 --- a/backend/src/test/kotlin/app/ehrenamtskarte/backend/helper/ExampleUser.kt +++ b/backend/src/test/kotlin/app/ehrenamtskarte/backend/helper/ExampleUser.kt @@ -1,5 +1,5 @@ package app.ehrenamtskarte.backend.helper -import app.ehrenamtskarte.backend.user.KoblenzUser +import app.ehrenamtskarte.backend.userdata.KoblenzUser val koblenzTestUser = KoblenzUser("Karla Koblenz", 12213, "123K")