From e12bb0576e5f184fbc8ed89676b5650f0485eba1 Mon Sep 17 00:00:00 2001 From: Lukas Lechner <lukas.lechner@sap.com> Date: Thu, 27 May 2021 16:20:06 +0200 Subject: [PATCH] Improve log censoring mechanics (EXPOSUREAPP-7455, EXPOSUREAPP-7196, EXPOSUREAPP-7514) (#3280) * Catch IndexOutOfBoundsExceptions in the DebugLogger and log out information * Fix IndexOutOfBoundsException due to censored string bounds exceeding original log message. * Fix missing censoring if log lines contain the same value multiple times. * Remove unused code. * Adjust CensoredString.censor(...) to determine the bounds always on the original. * Add test for incorrectly censored message due to changed bounds (working with bounds on the censored message, instead of original message). * Implement internal censoring collision handling within each censoring module. * Remove superfluous factory method. * Move the final censor compilation step to the DebugLogger, in case of collisions we don't need to do all the work. Re-use censor container for "per module" censoring and the "top-level" combination of all modules. * Adjust censoring for values that need to be censored, but do not overlap, each other, just touch: "<ABC><DEF>". * LINTs * Don't pass all meta data to censoring, we only need to censor the line, the metadata can be added later. No unnecessary string work. * Fix typos and docs. * Remove coerce as we work only the original. * Implement equals/hashcode for replacement actions and changed collection from LIST to SET, to prevent collisions due to duplicate replacement actions. * If a censor throws an exception, censor everything as precaution. * Fix flaky test. Co-authored-by: harambasicluka <64483219+harambasicluka@users.noreply.github.com> Co-authored-by: BMItter <Berndus@gmx.de> Co-authored-by: Matthias Urhahn <matthias.urhahn@sap.com> --- .../bugreporting/censors/BugCensor.kt | 130 ++++++--- .../contactdiary/DiaryEncounterCensor.kt | 13 +- .../contactdiary/DiaryLocationCensor.kt | 17 +- .../censors/contactdiary/DiaryPersonCensor.kt | 17 +- .../censors/contactdiary/DiaryVisitCensor.kt | 13 +- .../censors/presencetracing/CheckInsCensor.kt | 15 +- .../presencetracing/TraceLocationCensor.kt | 23 +- .../censors/submission/CoronaTestCensor.kt | 15 +- .../censors/submission/PcrQrCodeCensor.kt | 7 +- .../censors/submission/RACoronaTestCensor.kt | 17 +- .../censors/submission/RatProfileCensor.kt | 27 +- .../censors/submission/RatQrCodeCensor.kt | 21 +- .../vaccination/CertificateQrCodeCensor.kt | 76 ++--- .../bugreporting/debuglog/DebugLogger.kt | 32 ++- .../bugreporting/debuglog/LogLine.kt | 17 +- .../src/test/java/android/util/Log.java | 39 +++ .../bugreporting/censors/BugCensorTest.kt | 151 +++++++++- .../censors/CensorInjectionTest.kt | 8 +- .../censors/CheckInsCensorTest.kt | 14 +- .../censors/CoronaTestCensorTest.kt | 9 +- .../censors/DiaryEncounterCensorTest.kt | 4 +- .../censors/DiaryLocationCensorTest.kt | 4 +- .../censors/DiaryPersonCensorTest.kt | 4 +- .../censors/DiaryVisitCensorTest.kt | 4 +- .../censors/PcrQrCodeCensorTest.kt | 3 +- .../censors/RACoronaTestCensorTest.kt | 4 +- .../censors/RatQrCodeCensorTest.kt | 3 +- .../censors/TraceLocationCensorTest.kt | 59 ++-- .../submission/RatProfileCensorTest.kt | 22 +- .../CertificateQrCodeCensorTest.kt | 14 +- .../bugreporting/debuglog/DebugLoggerTest.kt | 260 +++++++++++++++++- .../bugreporting/debuglog/LogLineTest.kt | 26 +- 32 files changed, 787 insertions(+), 281 deletions(-) create mode 100644 Corona-Warn-App/src/test/java/android/util/Log.java diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/BugCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/BugCensor.kt index 57cf1c85a..49754b1bc 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/BugCensor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/BugCensor.kt @@ -1,47 +1,98 @@ package de.rki.coronawarnapp.bugreporting.censors -import kotlin.math.max -import kotlin.math.min - interface BugCensor { - /** - * If there is something to censor a new log line is returned, otherwise returns null - */ - suspend fun checkLog(message: String): CensoredString? + suspend fun checkLog(message: String): CensorContainer? - data class CensoredString( - // The censored version of the string - val string: String, - // The range that we censored - // If there is a collision, this range in the original needs to be removed. - val range: IntRange? = null - ) + data class CensorContainer( + // Original String, necessary for correct censoring ranges + val original: String, + val actions: Set<Action> = emptySet() + ) { - companion object { - operator fun CensoredString.plus(newer: CensoredString?): CensoredString { - if (newer == null) return this + fun censor(toReplace: String, replacement: String): CensorContainer { + if (!original.contains(toReplace)) return this + + val start = original.indexOf(toReplace) + if (start == -1) return this // Shouldn't happen + + val end = original.lastIndexOf(toReplace) + toReplace.length - val range = when { - newer.range == null -> this.range - this.range == null -> newer.range - else -> min(this.range.first, newer.range.first)..max(this.range.last, newer.range.last) + val newAction = Action( + range = start..end, + modifier = Action.SimpleReplace(toReplace, replacement) + ) + return this.copy(actions = actions.plus(newAction)) + } + + fun compile(): CensoredString? { + val ranges = actions.map { it.range } + if (ranges.isEmpty()) return null + + val isIntersecting = ranges.any { outer -> + ranges.any { inner -> + outer !== inner && + (inner.contains(outer.first) || inner.contains(outer.last)) && + (inner.last != outer.first && inner.first != outer.last) + } } - return CensoredString(string = newer.string, range = range) + return if (isIntersecting) { + val minMin = ranges.minOf { it.first } + val maxMax = ranges.maxOf { it.last } + CensoredString( + censored = original.replaceRange(minMin, maxMax, COLLISION_STRING), + ranges = listOf(minMin..maxMax) + ) + } else { + CensoredString( + censored = actions.fold(original) { notOriginal, action -> + action.execute(notOriginal) + }, + ranges = ranges + ) + } } - fun CensoredString.censor(orig: String, replacement: String): CensoredString? { - val start = this.string.indexOf(orig) - if (start == -1) return null + fun nullIfEmpty(): CensorContainer? = if (actions.isEmpty()) null else this - val end = start + orig.length - return CensoredString( - string = this.string.replace(orig, replacement), - range = start..end - ) + data class Action( + val range: IntRange, + val modifier: StringModifier, + ) { + + fun execute(original: String) = modifier.execute(original) + + data class SimpleReplace( + val oldValue: String, + val newValue: String, + ) : StringModifier { + override val execute: (String) -> String = { it.replace(oldValue, newValue) } + } + + interface StringModifier { + val execute: (String) -> String + } } + companion object { + const val COLLISION_STRING = "<censor-collision/>" + + fun createErrorContainer(censor: BugCensor, exception: Exception): CensorContainer { + return CensorContainer("<censor-error>$censor: $exception</censor-error>") + } + } + } + + data class CensoredString( + // The censored version of the string + val censored: String, + // The range that we censored + // If there is a collision, this range in the original needs to be removed. + val ranges: List<IntRange> + ) + + companion object { fun withValidName(name: String?, action: (String) -> Unit): Boolean { if (name.isNullOrBlank()) return false if (name.length < 3) return false @@ -98,8 +149,21 @@ interface BugCensor { return true } - fun CensoredString.toNullIfUnmodified(): CensoredString? { - return if (range == null) null else this - } + fun containerForError( + censor: BugCensor, + original: String, + error: Exception + ): CensorContainer = CensorContainer( + original = original, + actions = setOf( + CensorContainer.Action( + range = 0..original.length, + modifier = CensorContainer.Action.SimpleReplace( + original, + "<censor-error>Module ${censor.javaClass.simpleName}: $error</censor-error>" + ) + ) + ) + ) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryEncounterCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryEncounterCensor.kt index 63c6cfc42..876966ae9 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryEncounterCensor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryEncounterCensor.kt @@ -2,10 +2,7 @@ package de.rki.coronawarnapp.bugreporting.censors.contactdiary import dagger.Reusable import de.rki.coronawarnapp.bugreporting.censors.BugCensor -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.plus -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNullIfUnmodified +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensorContainer import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidComment import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope import de.rki.coronawarnapp.contactdiary.model.ContactDiaryPersonEncounter @@ -34,20 +31,20 @@ class DiaryEncounterCensor @Inject constructor( .launchIn(debugScope) } - override suspend fun checkLog(message: String): CensoredString? = mutex.withLock { + override suspend fun checkLog(message: String): CensorContainer? = mutex.withLock { if (encounterHistory.isEmpty()) return null - val newMessage = encounterHistory.fold(CensoredString(message)) { orig, encounter -> + val newMessage = encounterHistory.fold(CensorContainer(message)) { orig, encounter -> var wip = orig withValidComment(encounter.circumstances) { - wip += wip.censor(it, "Encounter#${encounter.id}/Circumstances") + wip = wip.censor(it, "Encounter#${encounter.id}/Circumstances") } wip } - return newMessage.toNullIfUnmodified() + return newMessage.nullIfEmpty() } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryLocationCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryLocationCensor.kt index e1439a50c..73d7c9f77 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryLocationCensor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryLocationCensor.kt @@ -2,10 +2,7 @@ package de.rki.coronawarnapp.bugreporting.censors.contactdiary import dagger.Reusable import de.rki.coronawarnapp.bugreporting.censors.BugCensor -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.plus -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNullIfUnmodified +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensorContainer import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidEmail import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidName import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidPhoneNumber @@ -35,26 +32,26 @@ class DiaryLocationCensor @Inject constructor( .launchIn(debugScope) } - override suspend fun checkLog(message: String): CensoredString? = mutex.withLock { + override suspend fun checkLog(message: String): CensorContainer? = mutex.withLock { if (locationHistory.isEmpty()) return null - val newMessage = locationHistory.fold(CensoredString(message)) { orig, location -> + val newMessage = locationHistory.fold(CensorContainer(message)) { orig, location -> var wip = orig withValidName(location.locationName) { - wip += wip.censor(it, "Location#${location.locationId}/Name") + wip = wip.censor(it, "Location#${location.locationId}/Name") } withValidEmail(location.emailAddress) { - wip += wip.censor(it, "Location#${location.locationId}/EMail") + wip = wip.censor(it, "Location#${location.locationId}/EMail") } withValidPhoneNumber(location.phoneNumber) { - wip += wip.censor(it, "Location#${location.locationId}/PhoneNumber") + wip = wip.censor(it, "Location#${location.locationId}/PhoneNumber") } wip } - return newMessage.toNullIfUnmodified() + return newMessage.nullIfEmpty() } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryPersonCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryPersonCensor.kt index 52f542fc4..aa8125f34 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryPersonCensor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryPersonCensor.kt @@ -2,10 +2,7 @@ package de.rki.coronawarnapp.bugreporting.censors.contactdiary import dagger.Reusable import de.rki.coronawarnapp.bugreporting.censors.BugCensor -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.plus -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNullIfUnmodified +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensorContainer import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidEmail import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidName import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidPhoneNumber @@ -36,26 +33,26 @@ class DiaryPersonCensor @Inject constructor( .launchIn(debugScope) } - override suspend fun checkLog(message: String): CensoredString? = mutex.withLock { + override suspend fun checkLog(message: String): CensorContainer? = mutex.withLock { if (personHistory.isEmpty()) return null - val newMessage = personHistory.fold(CensoredString(message)) { orig, person -> + val newMessage = personHistory.fold(CensorContainer(message)) { orig, person -> var wip = orig withValidName(person.fullName) { - wip += wip.censor(it, "Person#${person.personId}/Name") + wip = wip.censor(it, "Person#${person.personId}/Name") } withValidEmail(person.emailAddress) { - wip += wip.censor(it, "Person#${person.personId}/EMail") + wip = wip.censor(it, "Person#${person.personId}/EMail") } withValidPhoneNumber(person.phoneNumber) { - wip += wip.censor(it, "Person#${person.personId}/PhoneNumber") + wip = wip.censor(it, "Person#${person.personId}/PhoneNumber") } wip } - return newMessage.toNullIfUnmodified() + return newMessage.nullIfEmpty() } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryVisitCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryVisitCensor.kt index f08bfc69d..4a6bf335f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryVisitCensor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/contactdiary/DiaryVisitCensor.kt @@ -2,10 +2,7 @@ package de.rki.coronawarnapp.bugreporting.censors.contactdiary import dagger.Reusable import de.rki.coronawarnapp.bugreporting.censors.BugCensor -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.plus -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNullIfUnmodified +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensorContainer import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope import de.rki.coronawarnapp.contactdiary.model.ContactDiaryLocationVisit import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository @@ -37,20 +34,20 @@ class DiaryVisitCensor @Inject constructor( .launchIn(debugScope) } - override suspend fun checkLog(message: String): CensoredString? = mutex.withLock { + override suspend fun checkLog(message: String): CensorContainer? = mutex.withLock { if (visitsHistory.isEmpty()) return null - val newMessage = visitsHistory.fold(CensoredString(message)) { orig, visit -> + val newMessage = visitsHistory.fold(CensorContainer(message)) { orig, visit -> var wip = orig BugCensor.withValidComment(visit.circumstances) { - wip += wip.censor(it, "Visit#${visit.id}/Circumstances") + wip = wip.censor(it, "Visit#${visit.id}/Circumstances") } wip } - return newMessage.toNullIfUnmodified() + return newMessage.nullIfEmpty() } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/presencetracing/CheckInsCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/presencetracing/CheckInsCensor.kt index 7cbede14a..e342c3caa 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/presencetracing/CheckInsCensor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/presencetracing/CheckInsCensor.kt @@ -2,10 +2,7 @@ package de.rki.coronawarnapp.bugreporting.censors.presencetracing import dagger.Reusable import de.rki.coronawarnapp.bugreporting.censors.BugCensor -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.plus -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNullIfUnmodified +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensorContainer import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidAddress import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidDescription import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope @@ -34,25 +31,25 @@ class CheckInsCensor @Inject constructor( .launchIn(debugScope) } - override suspend fun checkLog(message: String): CensoredString? = mutex.withLock { + override suspend fun checkLog(message: String): CensorContainer? = mutex.withLock { if (checkInsHistory.isEmpty()) return null - val newLogMsg = checkInsHistory.fold(CensoredString(message)) { initial, checkIn -> + val newLogMsg = checkInsHistory.fold(CensorContainer(message)) { initial, checkIn -> var acc = initial withValidDescription(checkIn.description) { description -> - acc += acc.censor(description, "CheckIn#${checkIn.id}/Description") + acc = acc.censor(description, "CheckIn#${checkIn.id}/Description") } withValidAddress(checkIn.address) { address -> - acc += acc.censor(address, "CheckIn#${checkIn.id}/Address") + acc = acc.censor(address, "CheckIn#${checkIn.id}/Address") } acc } - return newLogMsg.toNullIfUnmodified() + return newLogMsg.nullIfEmpty() } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/presencetracing/TraceLocationCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/presencetracing/TraceLocationCensor.kt index cae77e407..3b58cdfd4 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/presencetracing/TraceLocationCensor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/presencetracing/TraceLocationCensor.kt @@ -2,10 +2,7 @@ package de.rki.coronawarnapp.bugreporting.censors.presencetracing import dagger.Reusable import de.rki.coronawarnapp.bugreporting.censors.BugCensor -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.plus -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNullIfUnmodified +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensorContainer import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidAddress import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidDescription import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope @@ -43,19 +40,19 @@ class TraceLocationCensor @Inject constructor( .launchIn(debugScope) } - override suspend fun checkLog(message: String): CensoredString? = mutex.withLock { + override suspend fun checkLog(message: String): CensorContainer? = mutex.withLock { - var newLogMsg = traceLocationHistory.fold(CensoredString(message)) { initial, traceLocation -> + var newLogMsg = traceLocationHistory.fold(CensorContainer(message)) { initial, traceLocation -> var acc = initial - acc += acc.censor(traceLocation.type.name, "TraceLocation#${traceLocation.id}/Type") + acc = acc.censor(traceLocation.type.name, "TraceLocation#${traceLocation.id}/Type") withValidDescription(traceLocation.description) { description -> - acc += acc.censor(description, "TraceLocation#${traceLocation.id}/Description") + acc = acc.censor(description, "TraceLocation#${traceLocation.id}/Description") } withValidAddress(traceLocation.address) { address -> - acc += acc.censor(address, "TraceLocation#${traceLocation.id}/Address") + acc = acc.censor(address, "TraceLocation#${traceLocation.id}/Address") } acc @@ -63,18 +60,18 @@ class TraceLocationCensor @Inject constructor( val inputDataToCensor = dataToCensor if (inputDataToCensor != null) { - newLogMsg += newLogMsg.censor(inputDataToCensor.type.name, "TraceLocationUserInput#Type") + newLogMsg = newLogMsg.censor(inputDataToCensor.type.name, "TraceLocationUserInput#Type") withValidDescription(inputDataToCensor.description) { - newLogMsg += newLogMsg.censor(inputDataToCensor.description, "TraceLocationUserInput#Description") + newLogMsg = newLogMsg.censor(inputDataToCensor.description, "TraceLocationUserInput#Description") } withValidAddress(inputDataToCensor.address) { - newLogMsg += newLogMsg.censor(inputDataToCensor.address, "TraceLocationUserInput#Address") + newLogMsg = newLogMsg.censor(inputDataToCensor.address, "TraceLocationUserInput#Address") } } - return newLogMsg.toNullIfUnmodified() + return newLogMsg.nullIfEmpty() } companion object { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/CoronaTestCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/CoronaTestCensor.kt index ea9d31a4f..611232d11 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/CoronaTestCensor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/CoronaTestCensor.kt @@ -2,10 +2,7 @@ package de.rki.coronawarnapp.bugreporting.censors.submission import dagger.Reusable import de.rki.coronawarnapp.bugreporting.censors.BugCensor -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.plus -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNullIfUnmodified +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensorContainer import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope import de.rki.coronawarnapp.coronatest.CoronaTestRepository import kotlinx.coroutines.CoroutineScope @@ -40,23 +37,23 @@ class CoronaTestCensor @Inject constructor( }.launchIn(debugScope) } - override suspend fun checkLog(message: String): CensoredString? = mutex.withLock { + override suspend fun checkLog(message: String): CensorContainer? = mutex.withLock { - var newMessage = CensoredString(message) + var newMessage = CensorContainer(message) for (token in tokenHistory) { if (!message.contains(token)) continue - newMessage += newMessage.censor(token, PLACEHOLDER + token.takeLast(4)) + newMessage = newMessage.censor(token, PLACEHOLDER + token.takeLast(4)) } identifierHistory .filter { message.contains(it) } .forEach { - newMessage += newMessage.censor(it, "${it.take(11)}CoronaTest/Identifier") + newMessage = newMessage.censor(it, "${it.take(11)}CoronaTest/Identifier") } - return newMessage.toNullIfUnmodified() + return newMessage.nullIfEmpty() } companion object { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/PcrQrCodeCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/PcrQrCodeCensor.kt index 0c61dd0ca..190773e03 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/PcrQrCodeCensor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/PcrQrCodeCensor.kt @@ -2,18 +2,17 @@ package de.rki.coronawarnapp.bugreporting.censors.submission import dagger.Reusable import de.rki.coronawarnapp.bugreporting.censors.BugCensor -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensorContainer import javax.inject.Inject @Reusable class PcrQrCodeCensor @Inject constructor() : BugCensor { - override suspend fun checkLog(message: String): CensoredString? { + override suspend fun checkLog(message: String): CensorContainer? { val guid = lastGUID ?: return null if (!message.contains(guid)) return null - return CensoredString(message).censor(guid, PLACEHOLDER + guid.takeLast(4)) + return CensorContainer(message).censor(guid, PLACEHOLDER + guid.takeLast(4)).nullIfEmpty() } companion object { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/RACoronaTestCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/RACoronaTestCensor.kt index b2eff3186..e40f7085c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/RACoronaTestCensor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/RACoronaTestCensor.kt @@ -1,10 +1,7 @@ package de.rki.coronawarnapp.bugreporting.censors.submission import de.rki.coronawarnapp.bugreporting.censors.BugCensor -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.plus -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNullIfUnmodified +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensorContainer import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidName import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope import de.rki.coronawarnapp.coronatest.CoronaTestRepository @@ -38,24 +35,24 @@ class RACoronaTestCensor @Inject constructor( .launchIn(debugScope) } - override suspend fun checkLog(message: String): CensoredString? = mutex.withLock { + override suspend fun checkLog(message: String): CensorContainer? = mutex.withLock { - var newMessage = CensoredString(message) + var newMessage = CensorContainer(message) ratCoronaTestHistory.forEach { ratCoronaTest -> withValidName(ratCoronaTest.firstName) { firstName -> - newMessage += newMessage.censor(firstName, "RATest/FirstName") + newMessage = newMessage.censor(firstName, "RATest/FirstName") } withValidName(ratCoronaTest.lastName) { lastName -> - newMessage += newMessage.censor(lastName, "RATest/LastName") + newMessage = newMessage.censor(lastName, "RATest/LastName") } ratCoronaTest.dateOfBirth?.toString(dayOfBirthFormatter)?.let { dateOfBirthString -> - newMessage += newMessage.censor(dateOfBirthString, "RATest/DateOfBirth") + newMessage = newMessage.censor(dateOfBirthString, "RATest/DateOfBirth") } } - return newMessage.toNullIfUnmodified() + return newMessage.nullIfEmpty() } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/RatProfileCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/RatProfileCensor.kt index b33838de5..71b9eaae0 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/RatProfileCensor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/RatProfileCensor.kt @@ -1,10 +1,7 @@ package de.rki.coronawarnapp.bugreporting.censors.submission import de.rki.coronawarnapp.bugreporting.censors.BugCensor -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.plus -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNullIfUnmodified +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensorContainer import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidAddress import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidCity import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidName @@ -26,7 +23,7 @@ class RatProfileCensor @Inject constructor( private val dayOfBirthFormatter = DateTimeFormat.forPattern("yyyy-MM-dd") private val ratProfileHistory = mutableSetOf<RATProfile>() - override suspend fun checkLog(message: String): CensoredString? = mutex.withLock { + override suspend fun checkLog(message: String): CensorContainer? = mutex.withLock { val ratProfile = ratProfileSettings.profile.flow.first() // store the profile in a property so we still have a reference after it was deleted by the user @@ -34,46 +31,46 @@ class RatProfileCensor @Inject constructor( ratProfileHistory.add(ratProfile) } - var newMessage = CensoredString(message) + var newMessage = CensorContainer(message) ratProfileHistory.forEach { profile -> with(profile) { withValidName(firstName) { firstName -> - newMessage += newMessage.censor(firstName, "RAT-Profile/FirstName") + newMessage = newMessage.censor(firstName, "RAT-Profile/FirstName") } withValidName(lastName) { lastName -> - newMessage += newMessage.censor(lastName, "RAT-Profile/LastName") + newMessage = newMessage.censor(lastName, "RAT-Profile/LastName") } val dateOfBirthString = birthDate?.toString(dayOfBirthFormatter) if (dateOfBirthString != null) { - newMessage += newMessage.censor(dateOfBirthString, "RAT-Profile/DateOfBirth") + newMessage = newMessage.censor(dateOfBirthString, "RAT-Profile/DateOfBirth") } withValidAddress(street) { street -> - newMessage += newMessage.censor(street, "RAT-Profile/Street") + newMessage = newMessage.censor(street, "RAT-Profile/Street") } withValidCity(city) { city -> - newMessage += newMessage.censor(city, "RAT-Profile/City") + newMessage = newMessage.censor(city, "RAT-Profile/City") } withValidZipCode(zipCode) { zipCode -> - newMessage += newMessage.censor(zipCode, "RAT-Profile/Zip-Code") + newMessage = newMessage.censor(zipCode, "RAT-Profile/Zip-Code") } withValidPhoneNumber(phone) { phone -> - newMessage += newMessage.censor(phone, "RAT-Profile/Phone") + newMessage = newMessage.censor(phone, "RAT-Profile/Phone") } withValidPhoneNumber(email) { phone -> - newMessage += newMessage.censor(phone, "RAT-Profile/eMail") + newMessage = newMessage.censor(phone, "RAT-Profile/eMail") } } } - return newMessage.toNullIfUnmodified() + return newMessage.nullIfEmpty() } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/RatQrCodeCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/RatQrCodeCensor.kt index f7537dc8d..def0f07a0 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/RatQrCodeCensor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/submission/RatQrCodeCensor.kt @@ -2,10 +2,7 @@ package de.rki.coronawarnapp.bugreporting.censors.submission import dagger.Reusable import de.rki.coronawarnapp.bugreporting.censors.BugCensor -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.plus -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNullIfUnmodified +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensorContainer import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.withValidName import de.rki.coronawarnapp.coronatest.qrcode.RapidAntigenHash import org.joda.time.LocalDate @@ -17,31 +14,31 @@ class RatQrCodeCensor @Inject constructor() : BugCensor { private val dayOfBirthFormatter = DateTimeFormat.forPattern("yyyy-MM-dd") - override suspend fun checkLog(message: String): CensoredString? { + override suspend fun checkLog(message: String): CensorContainer? { val dataToCensor = dataToCensor ?: return null - var newMessage = CensoredString(message) + var newMessage = CensorContainer(message) with(dataToCensor) { - newMessage += newMessage.censor(rawString, "RatQrCode/ScannedRawString") + newMessage = newMessage.censor(rawString, "RatQrCode/ScannedRawString") - newMessage += newMessage.censor(hash, PLACEHOLDER + hash.takeLast(4)) + newMessage = newMessage.censor(hash, PLACEHOLDER + hash.takeLast(4)) withValidName(firstName) { firstName -> - newMessage += newMessage.censor(firstName, "RATest/FirstName") + newMessage = newMessage.censor(firstName, "RATest/FirstName") } withValidName(lastName) { lastName -> - newMessage += newMessage.censor(lastName, "RATest/LastName") + newMessage = newMessage.censor(lastName, "RATest/LastName") } val dateOfBirthString = dateOfBirth?.toString(dayOfBirthFormatter) ?: return@with - newMessage += newMessage.censor(dateOfBirthString, "RATest/DateOfBirth") + newMessage = newMessage.censor(dateOfBirthString, "RATest/DateOfBirth") } - return newMessage.toNullIfUnmodified() + return newMessage.nullIfEmpty() } companion object { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/vaccination/CertificateQrCodeCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/vaccination/CertificateQrCodeCensor.kt index 3b1dbda24..83c22418c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/vaccination/CertificateQrCodeCensor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/vaccination/CertificateQrCodeCensor.kt @@ -2,10 +2,7 @@ package de.rki.coronawarnapp.bugreporting.censors.vaccination import dagger.Reusable import de.rki.coronawarnapp.bugreporting.censors.BugCensor -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensoredString -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.plus -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNullIfUnmodified +import de.rki.coronawarnapp.bugreporting.censors.BugCensor.CensorContainer import de.rki.coronawarnapp.vaccination.core.certificate.VaccinationDGCV1 import de.rki.coronawarnapp.vaccination.core.qrcode.VaccinationCertificateData import java.util.LinkedList @@ -14,114 +11,119 @@ import javax.inject.Inject @Reusable class CertificateQrCodeCensor @Inject constructor() : BugCensor { - override suspend fun checkLog(message: String): CensoredString? { - var newMessage = CensoredString(message) + override suspend fun checkLog(message: String): CensorContainer? { + var newMessage = CensorContainer(message) synchronized(qrCodeStringsToCensor) { qrCodeStringsToCensor.toList() }.forEach { - newMessage += newMessage.censor(it, PLACEHOLDER + it.takeLast(4)) + newMessage = newMessage.censor(it, PLACEHOLDER + it.takeLast(4)) } synchronized(certsToCensor) { certsToCensor.toList() }.forEach { it.certificate.apply { - newMessage += newMessage.censor( - dob, - "vaccinationCertificate/dob" - ) + val dobFormatted = dateOfBirth.toString() - newMessage += newMessage.censor( - dateOfBirth.toString(), + newMessage = newMessage.censor( + dobFormatted, "vaccinationCertificate/dateOfBirth" ) + if (dobFormatted != dob) { + newMessage = newMessage.censor( + dob, + "vaccinationCertificate/dob" + ) + } - newMessage += censorNameData(nameData, newMessage) + newMessage = censorNameData(nameData, newMessage) vaccinationDatas.forEach { data -> - newMessage += censorVaccinationData(data, newMessage) + newMessage = censorVaccinationData(data, newMessage) } } } - return newMessage.toNullIfUnmodified() + return newMessage.nullIfEmpty() } private fun censorVaccinationData( vaccinationData: VaccinationDGCV1.VaccinationData, - message: CensoredString - ): CensoredString { + message: CensorContainer + ): CensorContainer { var newMessage = message - newMessage += newMessage.censor( - vaccinationData.dt, - "vaccinationData/dt" - ) - - newMessage += newMessage.censor( + newMessage = newMessage.censor( vaccinationData.marketAuthorizationHolderId, "vaccinationData/marketAuthorizationHolderId" ) - newMessage += newMessage.censor( + newMessage = newMessage.censor( vaccinationData.medicalProductId, "vaccinationData/medicalProductId" ) - newMessage += newMessage.censor( + newMessage = newMessage.censor( vaccinationData.targetId, "vaccinationData/targetId" ) - newMessage += newMessage.censor( + newMessage = newMessage.censor( vaccinationData.certificateIssuer, "vaccinationData/certificateIssuer" ) - newMessage += newMessage.censor( + newMessage = newMessage.censor( vaccinationData.uniqueCertificateIdentifier, "vaccinationData/uniqueCertificateIdentifier" ) - newMessage += newMessage.censor( + newMessage = newMessage.censor( vaccinationData.countryOfVaccination, "vaccinationData/countryOfVaccination" ) - newMessage += newMessage.censor( + newMessage = newMessage.censor( vaccinationData.vaccineId, "vaccinationData/vaccineId" ) - newMessage += newMessage.censor( - vaccinationData.vaccinatedAt.toString(), + val vaccinatedAt = vaccinationData.vaccinatedAt.toString() + newMessage = newMessage.censor( + vaccinatedAt, "vaccinationData/vaccinatedAt" ) + if (vaccinatedAt != vaccinationData.dt) { + newMessage = newMessage.censor( + vaccinationData.dt, + "vaccinationData/dt" + ) + } return newMessage } - private fun censorNameData(nameData: VaccinationDGCV1.NameData, message: CensoredString): CensoredString { + private fun censorNameData(nameData: VaccinationDGCV1.NameData, message: CensorContainer): CensorContainer { var newMessage = message nameData.familyName?.let { fName -> - newMessage += newMessage.censor( + newMessage = newMessage.censor( fName, "nameData/familyName" ) } - newMessage += newMessage.censor( + newMessage = newMessage.censor( nameData.familyNameStandardized, "nameData/familyNameStandardized" ) nameData.givenName?.let { gName -> - newMessage += newMessage.censor( + newMessage = newMessage.censor( gName, "nameData/givenName" ) } nameData.givenNameStandardized?.let { gName -> - newMessage += newMessage.censor( + newMessage = newMessage.censor( gName, "nameData/givenNameStandardized" ) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt index 5fe3b904e..ab7f0f1f6 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt @@ -165,26 +165,37 @@ class DebugLogger( delay(1000) val formattedMessage = rawLine.format() - val censored: Collection<BugCensor.CensoredString> = bugCensors.get() + val censored: Collection<BugCensor.CensorContainer> = bugCensors.get() .map { async { - it.checkLog(formattedMessage) + try { + it.checkLog(formattedMessage) + } catch (e: Exception) { + Log.e(TAG, "Error in censor module $it", e) + BugCensor.containerForError(it, formattedMessage, e) + } } } .awaitAll() .filterNotNull() - .filter { it.range != null } val toWrite: String = when (censored.size) { 0 -> formattedMessage - 1 -> censored.single().string - else -> { - val minMin = censored.minOf { it.range!!.first } - val maxMax = censored.maxOf { it.range!!.last } - formattedMessage.replaceRange(minMin, maxMax, CENSOR_COLLISION_PLACERHOLDER) - } + 1 -> censored.single().compile()?.censored ?: formattedMessage + else -> + try { + val combinedContainer = BugCensor.CensorContainer( + original = formattedMessage, + actions = censored.flatMap { it.actions }.toSet() + ) + + combinedContainer.compile()?.censored ?: formattedMessage + } catch (e: Exception) { + Log.e(TAG, "Censoring collision fail.", e) + "<censor-error>Global combination: $e</censor-error>" + } } - logWriter.write(toWrite) + logWriter.write(rawLine.formatFinal(toWrite)) } } } catch (e: CancellationException) { @@ -195,7 +206,6 @@ class DebugLogger( } companion object { - private const val CENSOR_COLLISION_PLACERHOLDER = "<censoring-collision>" internal const val TAG = "DebugLogger" } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/LogLine.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/LogLine.kt index 67664ffba..260dd10d4 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/LogLine.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/LogLine.kt @@ -13,16 +13,15 @@ data class LogLine( val throwable: Throwable? ) { - fun format(): String { - val time = Instant.ofEpochMilli(timestamp) - - val baseLine = "$time ${priorityLabel(priority)}/$tag: $message" + fun format(): String = if (throwable != null) { + message + "\n" + getStackTraceString(throwable) + } else { + message + } - return if (throwable != null) { - baseLine + "\n" + getStackTraceString(throwable) - } else { - baseLine - } + fun formatFinal(formattedLogLine: String): String { + val time = Instant.ofEpochMilli(timestamp) + return "$time ${priorityLabel(priority)}/$tag: $formattedLogLine" } companion object { diff --git a/Corona-Warn-App/src/test/java/android/util/Log.java b/Corona-Warn-App/src/test/java/android/util/Log.java new file mode 100644 index 000000000..3ec4706ce --- /dev/null +++ b/Corona-Warn-App/src/test/java/android/util/Log.java @@ -0,0 +1,39 @@ +package android.util; + +public class Log { + + public static final int VERBOSE = 2; + public static final int DEBUG = 3; + public static final int INFO = 4; + public static final int WARN = 5; + public static final int ERROR = 6; + public static final int ASSERT = 7; + + public static int d(String tag, String msg) { + System.out.println("D: " + tag + ": " + msg); + return 0; + } + + public static int i(String tag, String msg) { + System.out.println("I/: " + tag + ": " + msg); + return 0; + } + + public static int w(String tag, String msg) { + System.out.println("W/: " + tag + ": " + msg); + return 0; + } + + public static int e(String tag, String msg) { + return e(tag, msg, null); + } + + public static int e(String tag, String msg, Throwable error) { + if (error != null) { + System.out.println("E/: " + tag + ": " + msg + "Error: " + error.toString()); + } else { + System.out.println("E/: " + tag + ": " + msg); + } + return 0; + } +} \ No newline at end of file diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/BugCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/BugCensorTest.kt index bb6d1f2f7..1d4228cba 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/BugCensorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/BugCensorTest.kt @@ -1,6 +1,5 @@ package de.rki.coronawarnapp.bugreporting.censors -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.toNullIfUnmodified import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import org.junit.jupiter.api.Test @@ -99,8 +98,152 @@ class BugCensorTest : BaseTest() { } @Test - fun `cesnsor string is nulled if not modified`() { - BugCensor.CensoredString("abc", 1..2).toNullIfUnmodified() shouldNotBe null - BugCensor.CensoredString("abc", null).toNullIfUnmodified() shouldBe null + fun `censor string is nulled if not modified`() { + BugCensor.CensorContainer("abc").compile() shouldBe null + BugCensor.CensorContainer("abc").censor("abc", "123").compile() shouldNotBe null + BugCensor.CensorContainer("abc").censor("123", "abc").compile() shouldBe null + } + + @Test + fun `censoring range determination`() { + val input = "1234567890ABCDEFG" + val one = BugCensor.CensorContainer(input) + + one.censor("1234", "").apply { + actions.single().apply { + range shouldBe 0..4 + execute(original) shouldBe "567890ABCDEFG" + } + } + + one.censor("1234", "....").apply { + actions.single().apply { + execute(original) shouldBe "....567890ABCDEFG" + range shouldBe 0..4 + } + } + + one.censor("DEFG", "....").apply { + actions.single().apply { + execute(original) shouldBe "1234567890ABC...." + range shouldBe 13..(13 + 4) + } + } + + one.censor("1234567890ABCDEFG", "...").apply { + actions.single().apply { + execute(original) shouldBe "..." + range shouldBe 0..(0 + 17) + } + } + + one.censor("#", "...").actions shouldBe emptyList() + + one.censor("1234567890ABCDEFG", "1234567890ABCDEFG###").apply { + actions.single().apply { + execute(original) shouldBe "1234567890ABCDEFG###" + range shouldBe 0..(0 + 17) + } + } + + one.censor("", " ").apply { + actions.single().apply { + execute(original) shouldBe " 1 2 3 4 5 6 7 8 9 0 A B C D E F G " + range shouldBe 0..16 + } + } + } + + @Test + fun `censoring range combination`() { + val container1 = BugCensor.CensorContainer("abcdefg") + container1.actions shouldBe emptyList() + val container2 = container1.censor("cde", "345") + container2.actions.map { it.range }.toSet() shouldBe setOf(2..5) + val container3 = container2.censor("ab", "12") + container3.actions.map { it.range }.toSet() shouldBe setOf(0..2, 2..5) + } + + @Test + fun `censoring disjoint`() { + BugCensor.CensorContainer("#abcdefg*") + .censor("abc", "123") + .censor("efg", "567") + .compile()!! + .apply { + censored shouldBe "#123d567*" + ranges shouldBe setOf(1..4, 5..8) + } + } + + @Test + fun `censoring disjoint - touching`() { + BugCensor.CensorContainer("#abcefg*") + .censor("abc", "123") + .censor("efg", "567") + .compile()!! + .apply { + censored shouldBe "#123567*" + ranges shouldBe setOf(1..4, 4..7) + } + } + + @Test + fun `censoring overlap`() { + BugCensor.CensorContainer("#abcdefg*") + .censor("abcd", "1234") + .censor("defg", "4567") + .compile()!! + .apply { + censored shouldBe "#<censor-collision/>*" + ranges shouldBe setOf(1..8) + } + } + + @Test + fun `censoring complete overlap`() { + BugCensor.CensorContainer("#abcdefg*") + .censor("abc", "---") + .censor("abc", "+++") + .compile()!! + .apply { + censored shouldBe "#<censor-collision/>defg*" + ranges shouldBe setOf(1..4, 1..4) + } + } + + @Test + fun `full replacement collision`() { + BugCensor.CensorContainer("#abcdefg*") + .censor("#abcdefg*", "#1234567*") + .censor("#abcdefg*", "#*") + .compile()!! + .apply { + censored shouldBe "<censor-collision/>" + ranges shouldBe setOf(0..9, 0..9) + } + } + + @Test + fun `nested replacement collision`() { + BugCensor.CensorContainer("#abcdefg*") + .censor("#abcdefg*", "#abcdefg*") + .censor("abcdefg", "abcdefg") + .compile()!! + .apply { + censored shouldBe "<censor-collision/>" + ranges shouldBe setOf(0..9) + } + } + + @Test + fun `string length boundary check`() { + BugCensor.CensorContainer("#abcdefg*") + .censor("*", "**") + .compile()!! + .apply { + censored shouldBe "#abcdefg**" + ranges shouldBe setOf(8..9) + } } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CensorInjectionTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CensorInjectionTest.kt index 915ca4bb0..cc4c29283 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CensorInjectionTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CensorInjectionTest.kt @@ -69,7 +69,7 @@ class MockProvider { @Singleton @Provides - fun diary(): ContactDiaryRepository = mockk() { + fun diary(): ContactDiaryRepository = mockk { every { people } returns flowOf(emptyList()) every { personEncounters } returns flowOf(emptyList()) every { locations } returns flowOf(emptyList()) @@ -82,19 +82,19 @@ class MockProvider { @Singleton @Provides - fun coronaTestRepository(): CoronaTestRepository = mockk() { + fun coronaTestRepository(): CoronaTestRepository = mockk { every { coronaTests } returns flowOf(emptySet()) } @Singleton @Provides - fun checkInRepository(): CheckInRepository = mockk() { + fun checkInRepository(): CheckInRepository = mockk { every { allCheckIns } returns flowOf(emptyList()) } @Singleton @Provides - fun traceLocationRepository(): TraceLocationRepository = mockk() { + fun traceLocationRepository(): TraceLocationRepository = mockk { every { allTraceLocations } returns flowOf(emptyList()) } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CheckInsCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CheckInsCensorTest.kt index b49f0ac8a..d2ed248a9 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CheckInsCensorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CheckInsCensorTest.kt @@ -65,7 +65,7 @@ internal class CheckInsCensorTest : BaseTest() { Who needs the Kwik-E-Mart in Some Street, 12345 Springfield? I doooo! """.trimIndent() - censor.checkLog(logLineToCensor)!!.string shouldBe """ + censor.checkLog(logLineToCensor)!!.compile()!!.censored shouldBe """ Let's go to CheckIn#1/Description in CheckIn#1/Address. Who needs the CheckIn#2/Description in CheckIn#2/Address? I doooo! """.trimIndent() @@ -92,11 +92,11 @@ internal class CheckInsCensorTest : BaseTest() { checkInDescription = "Moe's Tavern", checkInAddress = "Near 742 Evergreen Terrace, 12345 Springfield" ), - /* deleted: mockCheckIn( - checkInId = 2, - checkInDescription = "Kwik-E-Mart", - checkInAddress = "Some Street, 12345 Springfield" - )*/ + /* deleted: mockCheckIn( + checkInId = 2, + checkInDescription = "Kwik-E-Mart", + checkInAddress = "Some Street, 12345 Springfield" + )*/ ) ) @@ -108,7 +108,7 @@ internal class CheckInsCensorTest : BaseTest() { Who needs the Kwik-E-Mart in Some Street, 12345 Springfield? I doooo! """.trimIndent() - censor.checkLog(logLineToCensor)!!.string shouldBe """ + censor.checkLog(logLineToCensor)!!.compile()!!.censored shouldBe """ Let's go to CheckIn#1/Description in CheckIn#1/Address. Who needs the CheckIn#2/Description in CheckIn#2/Address? I doooo! """.trimIndent() diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CoronaTestCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CoronaTestCensorTest.kt index e61a463d4..4522f2a9d 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CoronaTestCensorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/CoronaTestCensorTest.kt @@ -54,7 +54,8 @@ class CoronaTestCensorTest : BaseTest() { fun `censoring replaces the logline message`() = runBlockingTest { val instance = createInstance() val filterMe = "I'm a shy registration token: $testToken and we are extrovert $pcrIdentifier and $ratIdentifier" - instance.checkLog(filterMe)!!.string shouldBe "I'm a shy registration token: ########-####-####-####-########3a2f and we are extrovert qrcode-pcr-CoronaTest/Identifier and qrcode-rat-CoronaTest/Identifier" + instance.checkLog(filterMe)!! + .compile()!!.censored shouldBe "I'm a shy registration token: ########-####-####-####-########3a2f and we are extrovert qrcode-pcr-CoronaTest/Identifier and qrcode-rat-CoronaTest/Identifier" verify { coronaTestRepository.coronaTests } } @@ -83,11 +84,13 @@ class CoronaTestCensorTest : BaseTest() { val filterMe = "I'm a shy registration token: $testToken and we are extrovert $pcrIdentifier and $ratIdentifier" - censor.checkLog(filterMe)!!.string shouldBe "I'm a shy registration token: ########-####-####-####-########3a2f and we are extrovert qrcode-pcr-CoronaTest/Identifier and qrcode-rat-CoronaTest/Identifier" + censor.checkLog(filterMe)!! + .compile()!!.censored shouldBe "I'm a shy registration token: ########-####-####-####-########3a2f and we are extrovert qrcode-pcr-CoronaTest/Identifier and qrcode-rat-CoronaTest/Identifier" // delete all tests coronaTests.value = emptySet() - censor.checkLog(filterMe)!!.string shouldBe "I'm a shy registration token: ########-####-####-####-########3a2f and we are extrovert qrcode-pcr-CoronaTest/Identifier and qrcode-rat-CoronaTest/Identifier" + censor.checkLog(filterMe)!! + .compile()!!.censored shouldBe "I'm a shy registration token: ########-####-####-####-########3a2f and we are extrovert qrcode-pcr-CoronaTest/Identifier and qrcode-rat-CoronaTest/Identifier" } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryEncounterCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryEncounterCensorTest.kt index 75740a998..48037eeaf 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryEncounterCensorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryEncounterCensorTest.kt @@ -57,7 +57,7 @@ class DiaryEncounterCensorTest : BaseTest() { everyone disliked that. """.trimIndent() - instance.checkLog(censorMe)!!.string shouldBe + instance.checkLog(censorMe)!!.compile()!!.censored shouldBe """ On Encounter#2/Circumstances, two persons Encounter#3/Circumstances, @@ -88,7 +88,7 @@ class DiaryEncounterCensorTest : BaseTest() { everyone disliked that. """.trimIndent() - instance.checkLog(censorMe)!!.string shouldBe + instance.checkLog(censorMe)!!.compile()!!.censored shouldBe """ On Encounter#2/Circumstances, two persons Encounter#3/Circumstances, diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryLocationCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryLocationCensorTest.kt index fbc4cd1af..17610b27d 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryLocationCensorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryLocationCensorTest.kt @@ -59,7 +59,7 @@ class DiaryLocationCensorTest : BaseTest() { Both agreed that their emails (bürgermeister@münchen.de|karl@aachen.de) are awesome, and that Bielefeld doesn't exist as it has neither phonenumber (null) nor email (null). """.trimIndent() - instance.checkLog(censorMe)!!.string shouldBe + instance.checkLog(censorMe)!!.compile()!!.censored shouldBe """ Bürgermeister of Location#1/Name (Location#1/PhoneNumber) and Karl of Location#3/Name [Location#3/PhoneNumber] called each other. Both agreed that their emails (Location#1/EMail|Location#3/EMail) are awesome, @@ -89,7 +89,7 @@ class DiaryLocationCensorTest : BaseTest() { Both agreed that their emails (bürgermeister@münchen.de|karl@aachen.de) are awesome, and that Bielefeld doesn't exist as it has neither phonenumber (null) nor email (null). """.trimIndent() - instance.checkLog(censorMe)!!.string shouldBe + instance.checkLog(censorMe)!!.compile()!!.censored shouldBe """ Bürgermeister of Location#1/Name (Location#1/PhoneNumber) and Karl of Location#3/Name [Location#3/PhoneNumber] called each other. Both agreed that their emails (Location#1/EMail|Location#3/EMail) are awesome, diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryPersonCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryPersonCensorTest.kt index 1d8ccac54..e94759b31 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryPersonCensorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryPersonCensorTest.kt @@ -60,7 +60,7 @@ class DiaryPersonCensorTest : BaseTest() { but Matthias thought he had enough has had enough for today. A quick mail to luka@sap.com confirmed this. """.trimIndent() - instance.checkLog(censorMe)!!.string shouldBe + instance.checkLog(censorMe)!!.compile()!!.censored shouldBe """ Person#2/Name requested more coffee from Person#1/PhoneNumber, but Person#3/Name thought he had enough has had enough for today. @@ -91,7 +91,7 @@ class DiaryPersonCensorTest : BaseTest() { A quick mail to luka@sap.com confirmed this. """.trimIndent() - instance.checkLog(censorMe)!!.string shouldBe + instance.checkLog(censorMe)!!.compile()!!.censored shouldBe """ Person#2/Name requested more coffee from Person#1/PhoneNumber, but Person#3/Name thought he had enough has had enough for today. diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryVisitCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryVisitCensorTest.kt index 3f554b853..d88f8847a 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryVisitCensorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryVisitCensorTest.kt @@ -55,7 +55,7 @@ class DiaryVisitCensorTest : BaseTest() { I got my beard shaved without mask, only to find out the supermarket was out of toiletpaper. """.trimIndent() - instance.checkLog(censorMe)!!.string shouldBe + instance.checkLog(censorMe)!!.compile()!!.censored shouldBe """ After having a Visit#1/Circumstances, I got my Visit#2/Circumstances, @@ -84,7 +84,7 @@ class DiaryVisitCensorTest : BaseTest() { I got my beard shaved without mask, only to find out the supermarket was out of toiletpaper. """.trimIndent() - instance.checkLog(censorMe)!!.string shouldBe + instance.checkLog(censorMe)!!.compile()!!.censored shouldBe """ After having a Visit#1/Circumstances, I got my Visit#2/Circumstances, diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/PcrQrCodeCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/PcrQrCodeCensorTest.kt index f7551bb70..fdf33d073 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/PcrQrCodeCensorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/PcrQrCodeCensorTest.kt @@ -30,7 +30,8 @@ class PcrQrCodeCensorTest : BaseTest() { PcrQrCodeCensor.lastGUID = testGUID val instance = createInstance() val censored = "I'm a shy qrcode: $testGUID" - instance.checkLog(censored)!!.string shouldBe "I'm a shy qrcode: ########-####-####-####-########3a2f" + instance.checkLog(censored)!! + .compile()!!.censored shouldBe "I'm a shy qrcode: ########-####-####-####-########3a2f" } @Test diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RACoronaTestCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RACoronaTestCensorTest.kt index fa2b323d3..960614377 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RACoronaTestCensorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RACoronaTestCensorTest.kt @@ -50,7 +50,7 @@ internal class RACoronaTestCensorTest : BaseTest() { Hello! My name is John. My friends call me Mister Doe and I was born on 2020-01-01. """.trimIndent() - censor.checkLog(logLineToCensor)!!.string shouldBe + censor.checkLog(logLineToCensor)!!.compile()!!.censored shouldBe """ Hello! My name is RATest/FirstName. My friends call me Mister RATest/LastName and I was born on RATest/DateOfBirth. """.trimIndent() @@ -77,7 +77,7 @@ internal class RACoronaTestCensorTest : BaseTest() { Hello! My name is John. My friends call me Mister Doe and I was born on 2020-01-01. """.trimIndent() - censor.checkLog(logLineToCensor)!!.string shouldBe + censor.checkLog(logLineToCensor)!!.compile()!!.censored shouldBe """ Hello! My name is RATest/FirstName. My friends call me Mister RATest/LastName and I was born on RATest/DateOfBirth. """.trimIndent() diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RatQrCodeCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RatQrCodeCensorTest.kt index afef21806..443db598f 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RatQrCodeCensorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RatQrCodeCensorTest.kt @@ -41,7 +41,8 @@ internal class RatQrCodeCensorTest { val logLineToCensor = "Here comes the hash: $testHash of the rat test of Milhouse Van Houten. He was born on 1980-07-01" - censor.checkLog(logLineToCensor)!!.string shouldBe "Here comes the hash: SHA256HASH-ENDING-WITH-15ad of the rat test of RATest/FirstName RATest/LastName. He was born on RATest/DateOfBirth" + censor.checkLog(logLineToCensor)!! + .compile()!!.censored shouldBe "Here comes the hash: SHA256HASH-ENDING-WITH-15ad of the rat test of RATest/FirstName RATest/LastName. He was born on RATest/DateOfBirth" } @Test diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/TraceLocationCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/TraceLocationCensorTest.kt index cb123802a..f3d274d7c 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/TraceLocationCensorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/TraceLocationCensorTest.kt @@ -51,38 +51,39 @@ internal class TraceLocationCensorTest : BaseTest() { } @Test - fun `checkLog() should return LogLine with censored trace location information from repository`() = runBlockingTest { - every { traceLocationRepo.allTraceLocations } returns flowOf( - listOf( - mockTraceLocation( - traceLocationId = 1, - traceLocationType = TraceLocationOuterClass.TraceLocationType.LOCATION_TYPE_PERMANENT_FOOD_SERVICE, - traceLocationDescription = "Sushi Place", - traceLocationAddress = "Sushi Street 123, 12345 Fish Town" - ), - mockTraceLocation( - traceLocationId = 2, - traceLocationType = TraceLocationOuterClass.TraceLocationType.LOCATION_TYPE_TEMPORARY_CULTURAL_EVENT, - traceLocationDescription = "Rick Astley Concert", - traceLocationAddress = "Never gonna give you up street 1, 12345 RickRoll City" + fun `checkLog() should return LogLine with censored trace location information from repository`() = + runBlockingTest { + every { traceLocationRepo.allTraceLocations } returns flowOf( + listOf( + mockTraceLocation( + traceLocationId = 1, + traceLocationType = TraceLocationOuterClass.TraceLocationType.LOCATION_TYPE_PERMANENT_FOOD_SERVICE, + traceLocationDescription = "Sushi Place", + traceLocationAddress = "Sushi Street 123, 12345 Fish Town" + ), + mockTraceLocation( + traceLocationId = 2, + traceLocationType = TraceLocationOuterClass.TraceLocationType.LOCATION_TYPE_TEMPORARY_CULTURAL_EVENT, + traceLocationDescription = "Rick Astley Concert", + traceLocationAddress = "Never gonna give you up street 1, 12345 RickRoll City" + ) ) ) - ) - val censor = createInstance(this) + val censor = createInstance(this) - val logLineToCensor = - """ - The type is LOCATION_TYPE_TEMPORARY_CULTURAL_EVENT. Yesterday we went to the Rick Astley Concert. The spectacle took place in Never gonna give you up street 1, 12345 RickRoll City. - Afterwards we had some food in Sushi Place in Sushi Street 123, 12345 Fish Town. It a nice LOCATION_TYPE_PERMANENT_FOOD_SERVICE. - """.trimIndent() + val logLineToCensor = + """ + The type is LOCATION_TYPE_TEMPORARY_CULTURAL_EVENT. Yesterday we went to the Rick Astley Concert. The spectacle took place in Never gonna give you up street 1, 12345 RickRoll City. + Afterwards we had some food in Sushi Place in Sushi Street 123, 12345 Fish Town. It a nice LOCATION_TYPE_PERMANENT_FOOD_SERVICE. + """.trimIndent() - censor.checkLog(logLineToCensor)!!.string shouldBe - """ - The type is TraceLocation#2/Type. Yesterday we went to the TraceLocation#2/Description. The spectacle took place in TraceLocation#2/Address. - Afterwards we had some food in TraceLocation#1/Description in TraceLocation#1/Address. It a nice TraceLocation#1/Type. - """.trimIndent() - } + censor.checkLog(logLineToCensor)!!.compile()!!.censored shouldBe + """ + The type is TraceLocation#2/Type. Yesterday we went to the TraceLocation#2/Description. The spectacle took place in TraceLocation#2/Address. + Afterwards we had some food in TraceLocation#1/Description in TraceLocation#1/Address. It a nice TraceLocation#1/Type. + """.trimIndent() + } @Test fun `censoring should still work after the user deletes his trace locations`() = runBlockingTest { @@ -126,7 +127,7 @@ internal class TraceLocationCensorTest : BaseTest() { Afterwards we had some food in Sushi Place in Sushi Street 123, 12345 Fish Town. It a nice LOCATION_TYPE_PERMANENT_FOOD_SERVICE. """.trimIndent() - censor.checkLog(logLineToCensor)!!.string shouldBe + censor.checkLog(logLineToCensor)!!.compile()!!.censored shouldBe """ The type is TraceLocation#2/Type. Yesterday we went to the TraceLocation#2/Description. The spectacle took place in TraceLocation#2/Address. Afterwards we had some food in TraceLocation#1/Description in TraceLocation#1/Address. It a nice TraceLocation#1/Type. @@ -154,7 +155,7 @@ internal class TraceLocationCensorTest : BaseTest() { top secret address as the address. The type is LOCATION_TYPE_TEMPORARY_PRIVATE_EVENT. """.trimIndent() - censor.checkLog(logLineToCensor)!!.string shouldBe + censor.checkLog(logLineToCensor)!!.compile()!!.censored shouldBe """ The user just created a new traceLocation with TraceLocationUserInput#Description as the description and TraceLocationUserInput#Address as the address. The type is TraceLocationUserInput#Type. diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/submission/RatProfileCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/submission/RatProfileCensorTest.kt index 9954cd867..e516437fe 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/submission/RatProfileCensorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/submission/RatProfileCensorTest.kt @@ -59,7 +59,7 @@ internal class RatProfileCensorTest : BaseTest() { "Mister First name who is also known as Last name and is born on 1950-08-01 lives in Main street, " + "12132 in the beautiful city of London. You can reach him by phone: 111111111 or email: email@example.com" - censor.checkLog(logLine)!!.string shouldBe + censor.checkLog(logLine)!!.compile()!!.censored shouldBe "Mister RAT-Profile/FirstName who is also known as RAT-Profile/LastName and is born on RAT-Profile/DateOfBirth lives in RAT-Profile/Street, " + "RAT-Profile/Zip-Code in the beautiful city of RAT-Profile/City. You can reach him by phone: RAT-Profile/Phone or email: RAT-Profile/eMail" } @@ -74,11 +74,29 @@ internal class RatProfileCensorTest : BaseTest() { "Mister First name who is also known as Last name and is born on 1950-08-01 lives in Main street, " + "12132 in the beautiful city of London. You can reach him by phone: 111111111 or email: email@example.com" - censor.checkLog(logLine)!!.string shouldBe + censor.checkLog(logLine)!!.compile()!!.censored shouldBe "Mister RAT-Profile/FirstName who is also known as RAT-Profile/LastName and is born on RAT-Profile/DateOfBirth lives in RAT-Profile/Street, " + "RAT-Profile/Zip-Code in the beautiful city of RAT-Profile/City. You can reach him by phone: RAT-Profile/Phone or email: RAT-Profile/eMail" } + @Test + fun `self overlap`() = runBlockingTest { + val selfOverlap = profile.copy( + lastName = "Berlin", + city = "Berlin Kreuzberg" + ) + every { ratProfileSettings.profile.flow } returns flowOf(selfOverlap, null) + + val censor = createInstance() + + val logLine = + "Mister First name who is also known as Last name and is born on 1950-08-01 lives in Main street, " + + "12132 in the beautiful city of Berlin Kreuzberg. You can reach him by phone: 111111111 or email: email@example.com, " + + "NotCensored" + + censor.checkLog(logLine)!!.compile()!!.censored shouldBe "Mister <censor-collision/>, NotCensored" + } + private val formatter = DateTimeFormat.forPattern("yyyy-MM-dd") private val profile = RATProfile( diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/vaccination/CertificateQrCodeCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/vaccination/CertificateQrCodeCensorTest.kt index a5509d80b..5d2f3d05c 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/vaccination/CertificateQrCodeCensorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/vaccination/CertificateQrCodeCensorTest.kt @@ -20,9 +20,9 @@ internal class CertificateQrCodeCensorTest { version = "1", nameData = VaccinationDGCV1.NameData( familyName = "Kevin", - familyNameStandardized = "Kevin2", + familyNameStandardized = "KEVIN", givenName = "Bob", - givenNameStandardized = "Bob2" + givenNameStandardized = "BOB" ), dob = "1969-11-16", vaccinationDatas = listOf( @@ -64,16 +64,18 @@ internal class CertificateQrCodeCensorTest { val logLineToCensor = "Here comes the rawString: $testRawString of the vaccine certificate" - censor.checkLog(logLineToCensor)!!.string shouldBe "Here comes the rawString: ########-####-####-####-########C\$AH of the vaccine certificate" + censor.checkLog(logLineToCensor)!! + .compile()!!.censored shouldBe "Here comes the rawString: ########-####-####-####-########C\$AH of the vaccine certificate" val certDataToCensor = "Hello my name is Kevin Bob, i was born at 1969-11-16, i have been " + "vaccinated with: 12345 1214765 aaEd/easd ASD-2312 1969-04-20 DE Herbert" + " urn:uvci:01:NL:PlA8UWS60Z4RZXVALl6GAZ" - censor.checkLog(certDataToCensor)!!.string shouldBe "Hello my name is nameData/familyName nameData/givenName, i was born at " + - "vaccinationCertificate/dob, i have been vaccinated with: vaccinationData/targetId " + + censor.checkLog(certDataToCensor)!! + .compile()!!.censored shouldBe "Hello my name is nameData/familyName nameData/givenName, i was born at " + + "vaccinationCertificate/dateOfBirth, i have been vaccinated with: vaccinationData/targetId " + "vaccinationData/vaccineId vaccinationData/medicalProductId" + - " vaccinationData/marketAuthorizationHolderId vaccinationData/dt" + + " vaccinationData/marketAuthorizationHolderId vaccinationData/vaccinatedAt" + " vaccinationData/countryOfVaccination vaccinationData/certificateIssuer" + " vaccinationData/uniqueCertificateIdentifier" } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt index 24b467db1..329aed6fb 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerTest.kt @@ -3,19 +3,20 @@ package de.rki.coronawarnapp.bugreporting.debuglog import android.app.Application import dagger.Lazy import de.rki.coronawarnapp.bugreporting.censors.BugCensor -import de.rki.coronawarnapp.bugreporting.censors.BugCensor.Companion.censor -import de.rki.coronawarnapp.bugreporting.censors.submission.CoronaTestCensor import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebugLogTree import de.rki.coronawarnapp.util.CWADebug import de.rki.coronawarnapp.util.di.ApplicationComponent import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe +import io.kotest.matchers.string.shouldEndWith +import io.kotest.matchers.string.shouldStartWith import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.mockkObject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay import kotlinx.coroutines.test.runBlockingTest import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach @@ -26,13 +27,13 @@ import testhelpers.logging.JUnitTree import timber.log.Timber import java.io.File -@Suppress("BlockingMethodInNonBlockingContext") +@Suppress("BlockingMethodInNonBlockingContext", "MaxLineLength") class DebugLoggerTest : BaseIOTest() { @MockK lateinit var application: Application @MockK lateinit var component: ApplicationComponent - @MockK lateinit var coronaTestCensor1: CoronaTestCensor - @MockK lateinit var coronaTestCensor2: CoronaTestCensor + @MockK lateinit var coronaTestCensor1: BugCensor + @MockK lateinit var coronaTestCensor2: BugCensor private val testDir = File(IO_TEST_BASEDIR, this::class.simpleName!!) private val cacheDir = File(testDir, "cache") @@ -240,7 +241,7 @@ class DebugLoggerTest : BaseIOTest() { coEvery { coronaTestCensor1.checkLog(any()) } answers { val msg = arg<String>(0) - BugCensor.CensoredString(msg).censor("says: A hot coffee", "says: A hot tea") + BugCensor.CensorContainer(msg).censor("says: A hot coffee", "says: A hot tea") } instance.start() @@ -248,17 +249,260 @@ class DebugLoggerTest : BaseIOTest() { Timber.tag("Test").v(logMsg) advanceTimeBy(2000L) + delay(5000) + runningLog.readLines().last().substring(25) shouldBe "V/Test: Lukas says: A hot tea is really nice!" coEvery { coronaTestCensor2.checkLog(any()) } answers { val msg = arg<String>(0) - BugCensor.CensoredString(msg).censor("says:", "sings:") + BugCensor.CensorContainer(msg).censor("says:", "sings:") } Timber.tag("Test").v(logMsg) advanceTimeBy(2000L) - runningLog.readLines().last().substring(25) shouldBe "V/Test: Lukas <censoring-collision> is really nice!" + runningLog.readLines().last().substring(25) shouldBe "V/Test: Lukas <censor-collision/> is really nice!" + + instance.stop() + advanceUntilIdle() + } + + @Test + fun `censoring collision handling for multiple values in the same string`() = runBlockingTest { + val before = + """ + RACoronaTest( + identifier=qrcode-RAPID_ANTIGEN-9a9a35fa1cf3261be3349fc50a37b58280634bf42487c8e4eca060c48f259eb7, + registeredAt=2021-05-25T10:18:05.275Z, + registrationToken=b0d451f9-a4ea-45ea-b634-f503458a64c9, + isSubmitted=false, isViewed=true, isAdvancedConsentGiven=true, isJournalEntryCreated=false, + isResultAvailableNotificationSent=true, + testResultReceivedAt=2021-05-25T10:18:41.616Z, + lastUpdatedAt=2021-05-25T10:18:41.152Z, + testResult=RAT_POSITIVE(7), + testedAt=2021-05-25T10:17:51.000Z, + firstName=Rüdiger, lastName=Müller, + dateOfBirth=1994-05-18, isProcessing=true, + lastError=null + ) + updated to + RACoronaTest( + identifier=qrcode-RAPID_ANTIGEN-9a9a35fa1cf3261be3349fc50a37b58280634bf42487c8e4eca060c48f259eb7, + registeredAt=2021-05-25T10:18:05.275Z, + registrationToken=b0d451f9-a4ea-45ea-b634-f503458a64c9, + isSubmitted=true, isViewed=true, isAdvancedConsentGiven=true, isJournalEntryCreated=false, + isResultAvailableNotificationSent=true, + testResultReceivedAt=2021-05-25T10:18:41.616Z, + lastUpdatedAt=2021-05-25T10:18:41.152Z, + testResult=RAT_POSITIVE(7), + testedAt=2021-05-25T10:17:51.000Z, + firstName=Rüdiger, lastName=Müller, + dateOfBirth=1994-05-18, isProcessing=false, + lastError=IOException() + ) + """.trimIndent() + val after = + """ + RACoronaTest( + identifier=<censor-collision/>, + dateOfBirth=1994-05-18, isProcessing=false, + lastError=IOException() + ) + """.trimIndent() + + coEvery { coronaTestCensor1.checkLog(any()) } answers { + val msg = arg<String>(0) + BugCensor.CensorContainer(msg).censor( + "firstName=Rüdiger, lastName=Müller", + "firstName=FIRSTNAME, lastName=LASTNAME" + ) + } + coEvery { coronaTestCensor2.checkLog(any()) } answers { + val msg = arg<String>(0) + BugCensor.CensorContainer(msg).censor( + "qrcode-RAPID_ANTIGEN-9a9a35fa1cf3261be3349fc50a37b58280634bf42487c8e4eca060c48f259eb7", + "IDENTIFIER" + ) + } + + val instance = createInstance(scope = this).apply { + init() + setInjectionIsReady(component) + } + + instance.start() + + Timber.tag("Test").v(before) + advanceTimeBy(2000L) + + val rawWritten = runningLog.readText() + val cleanedWritten = rawWritten.substring(rawWritten.indexOf("RACoronaTest")) + + cleanedWritten shouldBe after + "\n" + + instance.stop() + advanceUntilIdle() + } + + @Test + fun `censoring collision with larger than original index bounds`() = runBlockingTest { + val before = "shortBefore" // Without timestamp + + coEvery { coronaTestCensor1.checkLog(any()) } answers { + val msg = arg<String>(0) + BugCensor.CensorContainer(msg).censor("Before", "After") + } + coEvery { coronaTestCensor2.checkLog(any()) } answers { + val msg = arg<String>(0) + var orig = BugCensor.CensorContainer(msg) + + orig = orig.censor("ortBef", "thisReallyIsNotShortAnymore") + orig = orig.censor("Anymore", "Nevermore") + orig + } + + val instance = createInstance(scope = this).apply { + init() + setInjectionIsReady(component) + } + + instance.start() + + Timber.tag("Test").v(before) + advanceTimeBy(2000L) + + runningLog.readLines().last().substring(25) shouldBe "V/Test: sh<censor-collision/>" + + instance.stop() + advanceUntilIdle() + } + + // Censoring bounds need to be determined on the original string + @Test + fun `censoring collision with missmatching original and replacements`() = runBlockingTest { + val before = "StrawBerryCake" // Without timestamp + + coEvery { coronaTestCensor1.checkLog(any()) } answers { + val msg = arg<String>(0) + BugCensor.CensorContainer(msg).censor("Berry", "Banana") + } + coEvery { coronaTestCensor2.checkLog(any()) } answers { + val msg = arg<String>(0) + var orig = BugCensor.CensorContainer(msg) + + orig = orig.censor("StrawBerry", "StrawBerryBananaPie") + orig = orig.censor("StrawBerryBananaPie", "Apple") + orig + } + + val instance = createInstance(scope = this).apply { + init() + setInjectionIsReady(component) + } + + instance.start() + + Timber.tag("Test").v(before) + advanceTimeBy(2000L) + + runningLog.readLines().last().substring(25) shouldBe "V/Test: <censor-collision/>Cake" + + instance.stop() + advanceUntilIdle() + } + + @Test + fun `censoring collision without overlap`() = runBlockingTest { + val before = "StrawBerryCakeWithCream" // Without timestamp + + coEvery { coronaTestCensor1.checkLog(any()) } answers { + val msg = arg<String>(0) + BugCensor.CensorContainer(msg) + .censor("Straw", "Strap") + .censor("With", "More") + } + coEvery { coronaTestCensor2.checkLog(any()) } answers { + val msg = arg<String>(0) + BugCensor.CensorContainer(msg) + .censor("Berry", "Barry") + .censor("Cream", "Sugar") + } + + val instance = createInstance(scope = this).apply { + init() + setInjectionIsReady(component) + } + + instance.start() + + Timber.tag("Test").v(before) + advanceTimeBy(2000L) + + runningLog.readLines() + .last() + .substring(25) shouldBe "V/Test: StrapBarryCakeMoreSugar" + + instance.stop() + advanceUntilIdle() + } + + @Test + fun `exception during single bugcensor execution`() = runBlockingTest { + val before = "StrawberryCake" // Without timestamp + + coEvery { coronaTestCensor1.checkLog(any()) } answers { + null + } + coEvery { coronaTestCensor2.checkLog(any()) } answers { + throw IllegalArgumentException("I give up") + } + + val instance = createInstance(scope = this).apply { + init() + setInjectionIsReady(component) + } + + instance.start() + + Timber.tag("Test").v(before) + advanceTimeBy(2000L) + + runningLog.readLines() + .last() + .substring(25).apply { + this shouldStartWith "V/Test: <censor-error>Module BugCensor" + this shouldEndWith "lang.IllegalArgumentException: I give up</censor-error>" + } + + instance.stop() + advanceUntilIdle() + } + + @Test + fun `exception during multi bugcensor execution`() = runBlockingTest { + val before = "StrawberryCake" // Without timestamp + + coEvery { coronaTestCensor1.checkLog(any()) } answers { + val msg = arg<String>(0) + BugCensor.CensorContainer(msg).censor("Cake", "Pancake") + } + coEvery { coronaTestCensor2.checkLog(any()) } answers { + throw IllegalArgumentException("I give up") + } + + val instance = createInstance(scope = this).apply { + init() + setInjectionIsReady(component) + } + + instance.start() + + Timber.tag("Test").v(before) + advanceTimeBy(2000L) + + runningLog.readLines() + .last() + .substring(25) shouldBe "V/Test: <censor-collision/>" instance.stop() advanceUntilIdle() diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/LogLineTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/LogLineTest.kt index 50b05b41b..ecdbc7785 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/LogLineTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/LogLineTest.kt @@ -17,9 +17,14 @@ class LogLineTest : BaseTest() { tag = "IamATag", message = "Low storage check failed.", throwable = null - ).format() shouldBe """ - 1970-01-01T00:00:00.123Z E/IamATag: Low storage check failed. - """.trimIndent() + ).apply { + format() shouldBe """ + Low storage check failed. + """.trimIndent() + formatFinal("abc") shouldBe """ + 1970-01-01T00:00:00.123Z E/IamATag: abc + """.trimIndent() + } } @Test @@ -30,10 +35,15 @@ class LogLineTest : BaseTest() { tag = "IamATag", message = "Low storage check failed.", throwable = IOException() - ).format().trimToLength(182) shouldBe """ - 1970-01-01T00:00:00.123Z E/IamATag: Low storage check failed. - java.io.IOException - at de.rki.coronawarnapp.bugreporting.debuglog.LogLineTest.log formatting with error(LogLineTest.kt: - """.trimIndent() + ).apply { + format().trimToLength(146) shouldBe """ + Low storage check failed. + java.io.IOException + at de.rki.coronawarnapp.bugreporting.debuglog.LogLineTest.log formatting with error(LogLineTest.kt: + """.trimIndent() + formatFinal("abc") shouldBe """ + 1970-01-01T00:00:00.123Z E/IamATag: abc + """.trimIndent() + } } } -- GitLab