diff --git a/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/WotCLI.kt b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/WotCLI.kt index dce33d46..23de6cb7 100644 --- a/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/WotCLI.kt +++ b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/WotCLI.kt @@ -9,12 +9,14 @@ import org.pgpainless.certificate_store.PGPainlessCertD import org.pgpainless.util.DateUtil import org.pgpainless.wot.KeyRingCertificateStore import org.pgpainless.wot.WebOfTrust +import org.pgpainless.wot.api.Binding import org.pgpainless.wot.api.WoTAPI import org.pgpainless.wot.cli.subcommands.* import org.pgpainless.wot.network.Fingerprint import org.pgpainless.wot.network.ReferenceTime import org.pgpainless.wot.network.Root import org.pgpainless.wot.network.Roots +import org.pgpainless.wot.query.Path import pgp.cert_d.PGPCertificateDirectory import pgp.cert_d.PGPCertificateStoreAdapter import pgp.cert_d.SpecialNames @@ -223,7 +225,6 @@ class WotCLI: Callable { @JvmStatic val dateFormat: SimpleDateFormat = SimpleDateFormat("yyyy-MM-dd") - } override fun toString(): String { diff --git a/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/AuthenticateCmd.kt b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/AuthenticateCmd.kt index 201667d7..0aba2056 100644 --- a/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/AuthenticateCmd.kt +++ b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/AuthenticateCmd.kt @@ -54,7 +54,7 @@ class AuthenticateCmd: Callable { override fun call(): Int { val result = parent.api.authenticate(AuthenticateAPI.Arguments( Fingerprint(fingerprint), userId, email)) - print(formatResult(result)) + formatResult(result) if (result.percentage < 100) { return -1 } @@ -64,30 +64,10 @@ class AuthenticateCmd: Callable { /** * Format the [AuthenticateAPI.Result] as a [String] which can be printed to standard out. */ - internal fun formatResult(result: AuthenticateAPI.Result): String { + internal fun formatResult(result: AuthenticateAPI.Result) { if (result.percentage < 100) { - return "No paths found." + println("No paths found.") } - - val sb = StringBuilder() - sb.appendLine("[✓] ${result.fingerprint} ${result.userId}: fully authenticated (${result.percentage}%)") - for ((pIndex, path: Path) in result.paths.paths.withIndex()) { - sb.appendLine(" Path #${pIndex + 1} of ${result.paths.paths.size}, trust amount ${path.amount}:") - for ((cIndex, certification) in path.certifications.withIndex()) { - val issuerUserId = certification.issuer.userIds.keys.firstOrNull()?.let { " (\"${it}\")" } ?: "" - when (cIndex) { - 0 -> { - sb.appendLine(" ◯ ${certification.issuer.fingerprint}${issuerUserId}") - } - else -> { - sb.appendLine(" ├ ${certification.issuer.fingerprint}${issuerUserId}") - } - } - sb.appendLine(" │ certified the following binding on ${WotCLI.dateFormat.format(certification.creationTime)}") - } - sb.appendLine(" └ ${result.fingerprint} \"${result.userId}\"") - } - - return sb.toString() + println(result.binding.toConsoleOut(result.targetAmount, WotCLI.dateFormat)) } } \ No newline at end of file diff --git a/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/IdentifyCmd.kt b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/IdentifyCmd.kt index b39a9d1f..07292c66 100644 --- a/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/IdentifyCmd.kt +++ b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/IdentifyCmd.kt @@ -10,6 +10,7 @@ import org.pgpainless.wot.network.Fingerprint import picocli.CommandLine import picocli.CommandLine.Command import picocli.CommandLine.Parameters +import java.text.SimpleDateFormat import java.util.concurrent.Callable @Command(name = "identify") @@ -29,35 +30,24 @@ class IdentifyCmd: Callable { override fun call(): Int { val api = parent.api val result = api.identify(IdentifyAPI.Arguments(Fingerprint(fingerprint))) - println(formatResult(result)) + println(formatResult(result, api.trustAmount, WotCLI.dateFormat)) return exitCode(result) } - fun formatResult(result: IdentifyAPI.Result): String { - if (result.target == null || result.paths.isEmpty()) { + fun formatResult(result: IdentifyAPI.Result, targetAmount: Int, dateFormat: SimpleDateFormat): String { + if (result.bindings.isEmpty()) { return "No paths found." } return buildString { - result.paths.keys.forEach { userId -> - val target = result.target!! - appendLine("[✓] ${target.fingerprint} $userId: fully authenticated: (${result.percentage(userId)}%)") - result.paths[userId]!!.paths.forEach {path -> - val root = path.root - val userIdString = if (root.userIds.isEmpty()) "" else " (${root.userIds.keys.first()})" - appendLine(" ◯ ${root.fingerprint}$userIdString") - path.certifications.forEachIndexed { index, edge -> - appendLine(" │ certified the following binding on ${WotCLI.dateFormat.format(edge.creationTime)}") - append(" ").append(if (index == path.certifications.lastIndex) "└" else "├") - .appendLine(" ${edge.target.fingerprint} \"${edge.userId}\"") - } - } + result.bindings.forEach { + appendLine(it.toConsoleOut(targetAmount, dateFormat)) } } } fun exitCode(result: IdentifyAPI.Result): Int { - return if(result.paths.isEmpty()) -1 else 0 + return if(result.bindings.isEmpty()) -1 else 0 } } \ No newline at end of file diff --git a/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/ListCmd.kt b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/ListCmd.kt index 978d0cbb..6069c54d 100644 --- a/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/ListCmd.kt +++ b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/ListCmd.kt @@ -22,6 +22,12 @@ class ListCmd: Callable { */ override fun call(): Int { val api = parent.api - TODO("Not yet implemented") + val result = api.list() + println(buildString { + result.bindings.forEach { + appendLine(it.toConsoleOut(api.trustAmount, WotCLI.dateFormat)) + } + }) + return 0 } } \ No newline at end of file diff --git a/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/LookupCmd.kt b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/LookupCmd.kt index 4725b9e1..3ea3ec2a 100644 --- a/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/LookupCmd.kt +++ b/pgpainless-wot-cli/src/main/kotlin/org/pgpainless/wot/cli/subcommands/LookupCmd.kt @@ -4,6 +4,7 @@ package org.pgpainless.wot.cli.subcommands +import org.pgpainless.wot.api.LookupAPI import org.pgpainless.wot.cli.WotCLI import picocli.CommandLine.* import java.util.concurrent.Callable @@ -27,6 +28,10 @@ class LookupCmd: Callable { */ override fun call(): Int { val api = parent.api - TODO("Not yet implemented") + val result = api.lookup(LookupAPI.Arguments(userId, email)) + result.bindings.forEach { + println(it.toConsoleOut(api.trustAmount, WotCLI.dateFormat)) + } + return if (result.bindings.isEmpty()) -1 else 0 } } \ No newline at end of file diff --git a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/AuthenticateAPI.kt b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/AuthenticateAPI.kt index e90f8008..c2d91eb0 100644 --- a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/AuthenticateAPI.kt +++ b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/AuthenticateAPI.kt @@ -5,7 +5,6 @@ package org.pgpainless.wot.api import org.pgpainless.wot.network.Fingerprint -import org.pgpainless.wot.query.Paths /** * Authenticate a binding. @@ -36,13 +35,9 @@ interface AuthenticateAPI { * @param targetAmount the targeted trust amount required to achieve full authentication * @param paths the number of paths */ - data class Result(val fingerprint: Fingerprint, val userId: String, private val targetAmount: Int, val paths: Paths) { - - /** - * Percentage of authentication. 100% means fully authenticated binding. - */ + data class Result(val binding: Binding, val targetAmount: Int) { val percentage: Int - get() = paths.amount * 100 / targetAmount + get() = binding.percentage(targetAmount) } } diff --git a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/Binding.kt b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/Binding.kt new file mode 100644 index 00000000..7c5e94fd --- /dev/null +++ b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/Binding.kt @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.wot.api + +import org.pgpainless.wot.network.Fingerprint +import org.pgpainless.wot.query.Path +import org.pgpainless.wot.query.Paths +import java.text.SimpleDateFormat + +data class Binding(val fingerprint: Fingerprint, val userId: String, val paths: Paths) { + /** + * Percentage of authentication. 100% means fully authenticated binding. + */ + fun percentage(targetAmount: Int): Int { + return paths.amount * 100 / targetAmount + } + + fun toConsoleOut(targetAmount: Int, dateFormat: SimpleDateFormat): String { + return buildString { + val percentage = percentage(targetAmount) + val authLevel = when (paths.amount) { + in 0..39 -> "not authenticated" + in 40..119 -> "partially authenticated" + in 120 .. 239 -> "fully authenticated" + else -> {if (percentage < 0) "not authenticated" else "doubly authenticated"} + } + append(if (percentage >= 100) "[✓] " else "[ ] ") + appendLine("$fingerprint $userId: $authLevel (${percentage(targetAmount)}%)") + for ((pIndex, path: Path) in paths.paths.withIndex()) { + appendLine(" Path #${pIndex + 1} of ${paths.paths.size}, trust amount ${path.amount}:") + for ((cIndex, certification) in path.certifications.withIndex()) { + val issuerUserId = certification.issuer.userIds.keys.firstOrNull()?.let { " (\"$it\")" } ?: "" + when (cIndex) { + 0 -> { + appendLine(" ◯ ${certification.issuer.fingerprint}$issuerUserId") + } + else -> { + appendLine(" ├ ${certification.issuer.fingerprint}$issuerUserId") + } + } + appendLine(" │ certified the following binding on ${dateFormat.format(certification.creationTime)}") + } + appendLine(" └ $fingerprint \"$userId\"") + } + } + } +} \ No newline at end of file diff --git a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/IdentifyAPI.kt b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/IdentifyAPI.kt index 317e21c8..1a189496 100644 --- a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/IdentifyAPI.kt +++ b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/IdentifyAPI.kt @@ -14,12 +14,5 @@ interface IdentifyAPI { data class Arguments(val fingerprint: Fingerprint) - data class Result(val paths: Map, val target: Node?, val targetAmount: Int) { - fun percentage(userId: String): Int? { - if (paths[userId] == null) { - return null - } - return paths[userId]!!.amount * 100 / targetAmount - } - } + data class Result(val bindings: List, val targetAmount: Int) } diff --git a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/ListAPI.kt b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/ListAPI.kt index 3c321d50..1f8be058 100644 --- a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/ListAPI.kt +++ b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/ListAPI.kt @@ -4,11 +4,9 @@ package org.pgpainless.wot.api -import org.pgpainless.wot.query.Paths - interface ListAPI { fun list(): Result - data class Result(val paths: Paths) + data class Result(val bindings: List) } diff --git a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/LookupAPI.kt b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/LookupAPI.kt index bb4f5725..5ad47955 100644 --- a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/LookupAPI.kt +++ b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/LookupAPI.kt @@ -4,13 +4,11 @@ package org.pgpainless.wot.api -import org.pgpainless.wot.query.Paths - interface LookupAPI { fun lookup(arguments: Arguments): Result data class Arguments(val userId: String, val email: Boolean = false) - data class Result(val paths: Paths) + data class Result(val bindings: List) } diff --git a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/WoTAPI.kt b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/WoTAPI.kt index ed8366cf..13b77e59 100644 --- a/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/WoTAPI.kt +++ b/wot-dijkstra/src/main/kotlin/org/pgpainless/wot/api/WoTAPI.kt @@ -5,11 +5,7 @@ package org.pgpainless.wot.api import org.pgpainless.wot.dijkstra.Query -import org.pgpainless.wot.network.Fingerprint -import org.pgpainless.wot.network.Network -import org.pgpainless.wot.network.ReferenceTime -import org.pgpainless.wot.network.Roots -import org.pgpainless.wot.query.Path +import org.pgpainless.wot.network.* import org.pgpainless.wot.query.Paths /** @@ -21,7 +17,6 @@ import org.pgpainless.wot.query.Paths * @param certificationNetwork if true, all certifications are treated as delegations with infinite trust depth and no regular expressions * @param trustAmount minimum trust amount * @param referenceTime reference time at which the web of trust is evaluated - * @param knownNotationRegistry registry of known notations */ class WoTAPI( val network: Network, @@ -46,32 +41,87 @@ class WoTAPI( override fun authenticate(arguments: AuthenticateAPI.Arguments): AuthenticateAPI.Result { val query = Query(network, trustRoots, certificationNetwork) val paths = query.authenticate(arguments.fingerprint, arguments.userId, trustAmount) - return AuthenticateAPI.Result(arguments.fingerprint, arguments.userId, trustAmount, paths) + return AuthenticateAPI.Result(Binding(arguments.fingerprint, arguments.userId, paths), trustAmount) } override fun identify(arguments: IdentifyAPI.Arguments): IdentifyAPI.Result { - val cert = network.nodes[arguments.fingerprint] ?: return IdentifyAPI.Result(mutableMapOf(), null, trustAmount) - val allPaths = mutableMapOf() + val cert = network.nodes[arguments.fingerprint] + ?: return IdentifyAPI.Result(listOf(), trustAmount) + + val bindings = mutableListOf() cert.userIds.keys.toList().forEach { val query = Query(network, trustRoots, certificationNetwork) val paths = query.authenticate(arguments.fingerprint, it, trustAmount) if (paths.amount != 0) { - allPaths[it] = paths + bindings.add(Binding(arguments.fingerprint, it, paths)) } } - return IdentifyAPI.Result(allPaths, cert, trustAmount) + return IdentifyAPI.Result(bindings, trustAmount) } override fun list(): ListAPI.Result { - TODO("Not yet implemented") + val bindings = mutableListOf() + network.nodes.forEach { + bindings.addAll(identify(IdentifyAPI.Arguments(it.key)).bindings) + } + return ListAPI.Result(bindings) } override fun lookup(arguments: LookupAPI.Arguments): LookupAPI.Result { - TODO("Not yet implemented") + val userId = arguments.userId + val email = arguments.email + + println("Looking up $userId email=$email") + + val candidates = network.nodes.values.mapNotNull { node -> + val matches = node.mapToMatchingUserIds(userId, email) + if (matches.isEmpty()) { + null + } else { + node to matches + } + } + + println("found ${candidates.size} candidates:") + candidates.joinToString { + "${it.first.fingerprint} ${it.second.joinToString { u -> u }}" + } + + val results = mutableListOf() + candidates.forEach { + val node = it.first + val userIds = it.second + + for (mUserId in userIds) { + authenticate(AuthenticateAPI.Arguments(node.fingerprint, mUserId, email)).let { result -> + if (result.binding.paths.paths.isNotEmpty()) { + results.add(result.binding) + } + } + } + } + + return LookupAPI.Result(results) } override fun path(arguments: PathAPI.Arguments): PathAPI.Result { TODO("Not yet implemented") } + private fun Node.mapToMatchingUserIds(userId: String, email: Boolean): List { + val list = mutableListOf() + userIds.forEach { entry -> + if (email) { + if (entry.key.contains("<$userId>")) { + list.add(entry.key) + } + } else { + if (entry.key == userId) { + list.add(entry.key) + } + } + } + return list + } + } \ No newline at end of file