From 4dddd769bfcb9024b9579c372d804f4705793e51 Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Wed, 19 Jul 2023 10:56:49 +0200 Subject: [PATCH 1/5] refactor: remove redundant clap annotations --- src/cli.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 098822a..f582eac 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -15,21 +15,13 @@ pub struct Opts { pub enum SubCommand { #[clap(name = "keypair")] KeyPairCmd(KeyPairCmd), - #[clap()] Inspect(Inspect), - #[clap()] InspectSnapshot(InspectSnapshot), - #[clap()] Generate(Generate), - #[clap()] Attenuate(Attenuate), - #[clap()] GenerateRequest(GenerateRequest), - #[clap()] GenerateThirdPartyBlock(GenerateThirdPartyBlock), - #[clap()] AppendThirdPartyBlock(AppendThirdPartyBlock), - #[clap()] Seal(Seal), } From b4eb590dcd93f9addbb695c292c935b3bee80f20 Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Thu, 20 Jul 2023 14:17:22 +0200 Subject: [PATCH 2/5] feat: support JSON output in `biscuit inspect` The `--json` flag on `biscuit inspect` now provides results in a structured JSON format: ```json { "token": { "sealed": false, "root_key_id": null, "blocks": [ { "code": "user(\"1234\");\n", "external_key": null, "revocation_id": "0ca759b390fbbb20461d9a2ca0b335371020262cbefc50854ee5cdc000339a877788f40baa0eb4af005eab0f26e556c0a86f063618d720c0619d7a67320de707" }, { "code": "user(\"4567\");\n", "external_key": null, "revocation_id": "c7e85b0c3bad81a1f381985042df1bd6b04f5c0029b0c38ec8565d767474953e9087af66ac396dd246f416038b7c02d2e943a1c5a5a4f282b7b3f4fcbc19fb0d" } ] }, "signatures_check": true, "auth": { "policies": [ "allow if user($u)" ], "result": { "Ok": [ 0, "allow if user($u)" ] } }, "query": { "query": "user($u) <- user($u)", "query_all": true, "facts": [ "user(\"1234\")", "user(\"4567\")" ] } } ``` If either the signature, the authorization, or the query fail during execution, a JSON value is still sent to stdout, with structured errors, an error message is sent to stderr, and the command exits with a non-zero error code. Evaluation still aborts for input errors (missing files, invalid datalog input for the authorizer, etc). In that case, nothing is sent to stdout, an error message is sent to stderr, and the command exits with a non-zero error code. This change required extensive modifications: the implementation interleaved console output with computations. This meant that even if an error was raised and execution aborted, text output would still be shown. For JSON output, this doesn't work, as all results need to be collected before being displayed. The inspection function now returns an `InspectionResults` value, that can either be rendered as text, or as JSON. To be able to generate this value, even in the case of authorization errors, the execution logic had to be updated to not abort early in case of: - failed signature checks - failed authorization - failed queries --- Cargo.lock | 475 +++++++++++++++++++++---------------------------- Cargo.toml | 4 +- src/cli.rs | 3 + src/errors.rs | 6 + src/inspect.rs | 409 +++++++++++++++++++++++++++++------------- src/main.rs | 1 + 6 files changed, 497 insertions(+), 401 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ad45c08..e360f8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,13 +4,19 @@ version = 3 [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "0f2135563fb5c609d2b2b87c1e8ce7bc41b0b45430fa9661f457981503dd5bf0" dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -22,9 +28,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.70" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "atty" @@ -32,7 +38,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi 0.1.19", + "hermit-abi", "libc", "winapi", ] @@ -73,9 +79,10 @@ dependencies = [ "rand", "rand_core", "regex", + "serde", "sha2 0.9.9", "thiserror", - "time 0.3.20", + "time", "zeroize", ] @@ -90,10 +97,12 @@ dependencies = [ "clap", "hex", "parse_duration", + "serde", + "serde_json", "shell-words", "tempfile", "thiserror", - "time 0.3.20", + "time", ] [[package]] @@ -106,8 +115,9 @@ dependencies = [ "nom", "proc-macro2", "quote", + "serde", "thiserror", - "time 0.3.20", + "time", ] [[package]] @@ -129,6 +139,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + [[package]] name = "block-buffer" version = "0.9.0" @@ -149,21 +165,24 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -173,27 +192,26 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.24" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ + "android-tzdata", "iana-time-zone", "js-sys", - "num-integer", "num-traits", - "time 0.1.45", "wasm-bindgen", - "winapi", + "windows-targets", ] [[package]] name = "clap" -version = "3.2.23" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "atty", - "bitflags", + "bitflags 1.3.2", "clap_derive", "clap_lex", "indexmap", @@ -205,9 +223,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.2.18" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" +checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" dependencies = [ "heck", "proc-macro-error", @@ -225,16 +243,6 @@ dependencies = [ "os_str_bytes", ] -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - [[package]] name = "const-oid" version = "0.9.5" @@ -243,9 +251,9 @@ checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" @@ -268,9 +276,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.0.0" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2" +checksum = "622178105f911d937a42cdb140730ba4a3ed2becd8ae6ce39c7d28b5d75d4588" dependencies = [ "cfg-if", "cpufeatures", @@ -291,51 +299,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", -] - -[[package]] -name = "cxx" -version = "1.0.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c00419335c41018365ddf7e4d5f1c12ee3659ddcf3e01974650ba1de73d038" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb8307ad413a98fff033c8545ecf133e3257747b3bae935e7602aab8aa92d4ca" -dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn 2.0.16", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc52e2eb08915cb12596d29d55f0b5384f00d697a646dbd269b6ecb0fbd9d31" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "631569015d0d8d54e6c241733f944042623ab6df7bc3be7466874b05fcdb1c5f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.16", + "syn 2.0.37", ] [[package]] @@ -348,6 +312,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "deranged" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" + [[package]] name = "digest" version = "0.9.0" @@ -393,19 +363,19 @@ dependencies = [ [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "errno" -version = "0.2.8" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" dependencies = [ "errno-dragonfly", "libc", - "winapi", + "windows-sys", ] [[package]] @@ -420,24 +390,21 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.9.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" [[package]] name = "fiat-crypto" -version = "0.1.20" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" +checksum = "d0870c84016d4b481be5c9f323c24f65e31e901ae618f0e80f4308fb00de1d2d" [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -486,12 +453,6 @@ dependencies = [ "libc", ] -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" - [[package]] name = "hex" version = "0.4.3" @@ -500,9 +461,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "iana-time-zone" -version = "0.1.54" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c17cc76786e99f8d2f055c11159e7f0091c42474dcc3189fbab96072e873e6d" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -514,44 +475,23 @@ dependencies = [ [[package]] name = "iana-time-zone-haiku" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "cxx", - "cxx-build", + "cc", ] [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", ] -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" -dependencies = [ - "hermit-abi 0.3.1", - "libc", - "windows-sys 0.45.0", -] - [[package]] name = "itertools" version = "0.10.5" @@ -563,15 +503,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -584,39 +524,27 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" - -[[package]] -name = "link-cplusplus" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" -dependencies = [ - "cc", -] +checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[package]] name = "minimal-lexical" @@ -704,18 +632,18 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", ] [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "opaque-debug" @@ -725,9 +653,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "os_str_bytes" -version = "6.5.0" +version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" +checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" [[package]] name = "parse_duration" @@ -788,9 +716,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" dependencies = [ "unicode-ident", ] @@ -830,9 +758,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.26" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -869,18 +797,30 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] name = "regex" -version = "1.7.2" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cce168fea28d3e05f158bda4576cf0c844d5045bc2cc3620fa0292ed5bb5814c" +checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" dependencies = [ "aho-corasick", "memchr", @@ -889,9 +829,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "rustc_version" @@ -904,23 +844,22 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.11" +version = "0.38.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e" +checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" dependencies = [ - "bitflags", + "bitflags 2.4.0", "errno", - "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] -name = "scratch" -version = "1.0.5" +name = "ryu" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "semver" @@ -930,9 +869,34 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.158" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "serde_json" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771d4d9c4163ee138805e12c710dd365e4f44be8be0503cb1bb9eb989425d9c9" +checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +dependencies = [ + "itoa", + "ryu", + "serde", +] [[package]] name = "sha2" @@ -1005,9 +969,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.16" +version = "2.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" dependencies = [ "proc-macro2", "quote", @@ -1016,22 +980,22 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.4.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", "fastrand", "redox_syscall", "rustix", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] name = "termcolor" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" dependencies = [ "winapi-util", ] @@ -1044,41 +1008,31 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn 2.0.37", ] [[package]] name = "time" -version = "0.1.45" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "time" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" dependencies = [ + "deranged", "itoa", "serde", "time-core", @@ -1087,36 +1041,30 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.8" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" dependencies = [ "time-core", ] [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" - -[[package]] -name = "unicode-width" -version = "0.1.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "version_check" @@ -1130,12 +1078,6 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1144,9 +1086,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1154,24 +1096,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.37", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1179,22 +1121,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.37", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "winapi" @@ -1229,42 +1171,27 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.46.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -1277,45 +1204,45 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "zeroize" diff --git a/Cargo.toml b/Cargo.toml index 7b09e9c..44d06c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ path = "src/main.rs" [dependencies] atty = "0.2.14" -biscuit-auth = "4.0.0" +biscuit-auth = { version = "4.0.0", features = ["serde-error"] } clap = { version = "^3.0", features = ["color", "derive"] } chrono = "^0.4" hex = "0.4.3" @@ -27,3 +27,5 @@ shell-words = "^1.0.0" thiserror = "1.0.32" anyhow = "1.0.61" time = "0.3.13" +serde_json = "1.0.103" +serde = { version = "1.0.173", features = ["derive"] } diff --git a/src/cli.rs b/src/cli.rs index f582eac..a5d4518 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -128,6 +128,9 @@ pub struct AppendThirdPartyBlock { /// Inspect a biscuit and optionally check its public key #[derive(Parser)] pub struct Inspect { + /// Output the results in a machine-readable format + #[clap(long)] + pub json: bool, #[clap(flatten)] pub biscuit_input_args: common_args::BiscuitInputArgs, /// Check the biscuit public key diff --git a/src/errors.rs b/src/errors.rs index 71b9fd0..99ed2aa 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -25,4 +25,10 @@ pub enum CliError { MissingPublicKeyForAuthorization, #[error("A public key is required when querying a biscuit")] MissingPublicKeyForQuerying, + #[error("Signatures check failed")] + SignaturesCheckFailed, + #[error("Authorization failed")] + AuthorizationFailed, + #[error("Querying failed")] + QueryFailed, } diff --git a/src/inspect.rs b/src/inspect.rs index 19d5912..dd307b4 100644 --- a/src/inspect.rs +++ b/src/inspect.rs @@ -1,11 +1,12 @@ use anyhow::Result; use biscuit_auth::{ - builder::{Fact, Policy, Rule}, + builder::{Fact, Rule}, datalog::RunLimits, error::{FailedCheck, Logic, MatchedPolicy, RunLimit, Token}, Authorizer, UnverifiedBiscuit, }; use chrono::offset::Utc; +use serde::Serialize; use std::fs; use std::path::PathBuf; @@ -13,12 +14,164 @@ use crate::cli::*; use crate::errors::CliError::*; use crate::input::*; +#[derive(Serialize)] +struct TokenBlock { + code: String, + external_key: Option, + revocation_id: String, +} + +#[derive(Serialize)] +struct TokenDescription { + sealed: bool, + root_key_id: Option, + blocks: Vec, +} + +impl TokenDescription { + fn render(&self) { + if self.sealed { + println!("Sealed biscuit"); + } else { + println!("Open biscuit"); + } + + for (i, block) in self.blocks.iter().enumerate() { + if i == 0 { + if let Some(root_key_id) = self.root_key_id { + println!("Authority block (root key identifier: {}):", &root_key_id); + } else { + println!("Authority block:"); + } + } else if let Some(epk) = &block.external_key { + println!("Block nΒ°{}, (third party, signed by {}):", i, epk); + } else { + println!("Block nΒ°{}:", i); + } + + println!("== Datalog =="); + println!("{}", block.code); + + println!("== Revocation id =="); + println!("{}", block.revocation_id); + println!("\n==========\n"); + } + } +} + +#[derive(Serialize)] +struct QueryResult { + query: String, + query_all: bool, + facts: std::result::Result, Token>, +} + +impl QueryResult { + fn render(&self) { + println!(); + if self.query_all { + println!("πŸ”Ž Running query on all facts: {}", &self.query); + } else { + println!("πŸ”Ž Running query: {}", &self.query); + } + match &self.facts { + Ok(facts) => { + if facts.is_empty() { + println!("❌ No results"); + } else { + for fact in facts { + println!("{}", &fact); + } + } + } + Err(_) => { + println!("❌ Query failed"); + } + } + } +} + +#[derive(Serialize)] +struct AuthResult { + policies: Vec, + result: std::result::Result<(usize, String), Token>, +} + +impl AuthResult { + fn render(&self) { + match &self.result { + Ok((_, policy)) => { + println!("βœ… Authorizer check succeeded πŸ›‘οΈ"); + println!("Matched allow policy: {}", policy); + } + Err(e) => { + println!("❌ Authorizer check failed πŸ›‘οΈ"); + match e { + Token::FailedLogic(l) => display_logic_error(&self.policies, l), + Token::RunLimit(l) => display_run_limit(l), + _ => {} + } + } + } + } +} + +#[derive(Serialize)] +pub struct InspectionResults { + token: TokenDescription, + signatures_check: Option, + auth: Option, + query: Option, +} + +impl InspectionResults { + pub fn render(&self) { + self.token.render(); + + match self.signatures_check { + None => println!("πŸ™ˆ Public key check skipped πŸ”‘"), + Some(true) => println!("βœ… Public key check succeeded πŸ”‘"), + Some(false) => println!("❌ Public key check failed πŸ”‘"), + } + + match &self.auth { + None => println!("πŸ™ˆ Datalog check skipped πŸ›‘οΈ"), + Some(auth_result) => auth_result.render(), + } + + match &self.query { + None => {} + Some(query_result) => query_result.render(), + } + } + + pub fn ensure_success(&self) -> Result<()> { + if self.signatures_check == Some(false) { + Err(SignaturesCheckFailed)?; + } + + if let Some(ref auth) = self.auth { + if auth.result.is_err() { + Err(AuthorizationFailed)?; + } + } + + if let Some(ref query) = self.query { + if query.facts.is_err() { + Err(QueryFailed)?; + } + } + + Ok(()) + } +} + fn handle_query( query: &Rule, query_all: bool, all_params: &[Param], authorizer: &mut Authorizer, -) -> Result<()> { +) -> Result { let mut rule = query.clone(); for p in all_params { @@ -32,26 +185,30 @@ fn handle_query( } } - let facts: Vec = if query_all { - authorizer.query_all(rule.clone())? + let facts: std::result::Result, Token> = if query_all { + authorizer.query_all(rule.clone()) } else { - authorizer.query(rule.clone())? + authorizer.query(rule.clone()) }; - println!(); - println!("πŸ”Ž Running query: {}", &rule); - if facts.is_empty() { - println!("❌ No results"); + Ok(QueryResult { + query: query.to_string(), + query_all, + facts: facts.map(|fs| fs.iter().map(|f| f.to_string()).collect::>()), + }) +} + +pub fn handle_inspect(inspect: &Inspect) -> Result<()> { + let res = handle_inspect_inner(inspect)?; + if inspect.json { + println!("{}", serde_json::to_string(&res)?); } else { - for fact in facts { - println!("{}", &fact); - } + res.render(); } - - Ok(()) + res.ensure_success() } -pub fn handle_inspect(inspect: &Inspect) -> Result<()> { +pub fn handle_inspect_inner(inspect: &Inspect) -> Result { let biscuit_format = if inspect.biscuit_input_args.raw_input { BiscuitFormat::RawBiscuit } else { @@ -108,137 +265,134 @@ pub fn handle_inspect(inspect: &Inspect) -> Result<()> { let biscuit = read_biscuit_from(&biscuit_from)?; let is_sealed = is_sealed(&biscuit)?; - if is_sealed { - println!("Sealed biscuit"); - } else { - println!("Open biscuit"); - } + let mut blocks = vec![]; - let content_revocation_ids = biscuit.revocation_identifiers(); + let revocation_ids = biscuit.revocation_identifiers(); let external_keys = biscuit.external_public_keys(); for i in 0..biscuit.block_count() { - if i == 0 { - if let Some(root_key_id) = biscuit.root_key_id() { - println!("Authority block (root key identifier: {}):", &root_key_id); - } else { - println!("Authority block:"); - } - } else if let Some(Some(epk)) = external_keys.get(i) { - println!( - "Block nΒ°{}, (third party, signed by {}):", - i, - hex::encode(epk) - ); - } else { - println!("Block nΒ°{}:", i); - } - - println!("== Datalog =="); - println!("{}", biscuit.print_block_source(i)?); - - println!("== Revocation id =="); - let content_id = content_revocation_ids + let external_key = external_keys .get(i) - .map(hex::encode) - .unwrap_or_else(|| "n/a".to_owned()); - println!("{}", &content_id); - println!("\n==========\n"); + .expect("Incorrect block index") + .clone() + .map(hex::encode); + blocks.push(TokenBlock { + code: biscuit.print_block_source(i)?, + external_key, + revocation_id: revocation_ids + .get(i) + .map(hex::encode) + .unwrap_or_else(|| "n/a".to_owned()), + }); } + let token_description = TokenDescription { + sealed: is_sealed, + root_key_id: biscuit.root_key_id(), + blocks, + }; + + let signatures_check; + let auth_result; + let query_result; + if let Some(key_from) = public_key_from { let key = read_public_key_from(&key_from)?; let sig_result = biscuit.check_signature(|_| key); - if sig_result.is_err() { - println!("❌ Public key check failed πŸ”‘"); - } - let biscuit = sig_result?; - println!("βœ… Public key check succeeded πŸ”‘"); - - let mut authorizer_builder = biscuit.authorizer()?; - if let Some(auth_from) = authorizer_from { - read_authorizer_from( - &auth_from, - &inspect.param_arg.param, - &mut authorizer_builder, - )?; - if inspect.authorization_args.include_time { - let now = Utc::now().to_rfc3339(); - let time_fact = format!("time({})", now); - authorizer_builder.add_fact(time_fact.as_ref())?; - } - let (_, _, _, policies) = authorizer_builder.dump(); - - let authorizer_result = authorizer_builder.authorize_with_limits(RunLimits { - max_facts: inspect - .authorization_args - .max_facts - .unwrap_or_else(|| RunLimits::default().max_facts), - max_iterations: inspect - .authorization_args - .max_iterations - .unwrap_or_else(|| RunLimits::default().max_iterations), - max_time: inspect - .authorization_args - .max_time - .map_or_else(|| RunLimits::default().max_time, |d| d.to_std().unwrap()), - }); - - match authorizer_result { - Ok(i) => { - println!("βœ… Authorizer check succeeded πŸ›‘οΈ"); - println!( - "Matched allow policy: {}", - policies.get(i).expect("Incorrect policy index") - ); - } + signatures_check = Some(sig_result.is_ok()); - Err(e) => { - println!("❌ Authorizer check failed πŸ›‘οΈ"); - match e { - Token::FailedLogic(l) => display_logic_error(&policies, &l), - Token::RunLimit(l) => display_run_limit(&l), - _ => {} + if let Ok(biscuit) = sig_result { + let mut authorizer_builder = biscuit.authorizer()?; + if let Some(auth_from) = authorizer_from { + read_authorizer_from( + &auth_from, + &inspect.param_arg.param, + &mut authorizer_builder, + )?; + if inspect.authorization_args.include_time { + let now = Utc::now().to_rfc3339(); + let time_fact = format!("time({})", now); + authorizer_builder.add_fact(time_fact.as_ref())?; + } + let (_, _, _, policies) = authorizer_builder.dump(); + + let authorizer_result = authorizer_builder.authorize_with_limits(RunLimits { + max_facts: inspect + .authorization_args + .max_facts + .unwrap_or_else(|| RunLimits::default().max_facts), + max_iterations: inspect + .authorization_args + .max_iterations + .unwrap_or_else(|| RunLimits::default().max_iterations), + max_time: inspect + .authorization_args + .max_time + .map_or_else(|| RunLimits::default().max_time, |d| d.to_std().unwrap()), + }); + + auth_result = Some(AuthResult { + policies: policies.iter().map(|p| p.to_string()).collect::>(), + result: authorizer_result.map(|i| { + ( + i, + policies.get(i).expect("Incorrect policy index").to_string(), + ) + }), + }); + + if let Some(snapshot_file) = &inspect.dump_snapshot_to { + if inspect.dump_raw_snapshot { + let bytes = authorizer_builder.to_raw_snapshot()?; + fs::write(snapshot_file, bytes)?; + } else { + let str = authorizer_builder.to_base64_snapshot()?; + fs::write(snapshot_file, str)?; } } - } - if let Some(snapshot_file) = &inspect.dump_snapshot_to { - if inspect.dump_raw_snapshot { - let bytes = authorizer_builder.to_raw_snapshot()?; - fs::write(snapshot_file, bytes)?; + + if let Some(query) = &inspect.query_args.query { + query_result = Some(handle_query( + query, + inspect.query_args.query_all, + &inspect.param_arg.param, + &mut authorizer_builder, + )?); } else { - let str = authorizer_builder.to_base64_snapshot()?; - fs::write(snapshot_file, str)?; + query_result = None; + } + } else { + auth_result = None; + if let Some(query) = &inspect.query_args.query { + query_result = Some(handle_query( + query, + inspect.query_args.query_all, + &inspect.param_arg.param, + &mut authorizer_builder, + )?); + } else { + query_result = None; } - } - - if let Some(query) = &inspect.query_args.query { - handle_query( - query, - inspect.query_args.query_all, - &inspect.param_arg.param, - &mut authorizer_builder, - )?; } } else { - println!("πŸ™ˆ Datalog check skipped πŸ›‘οΈ"); - if let Some(query) = &inspect.query_args.query { - handle_query( - query, - inspect.query_args.query_all, - &inspect.param_arg.param, - &mut authorizer_builder, - )?; - } + auth_result = None; + query_result = None; } } else { - println!("πŸ™ˆ Public key check skipped πŸ”‘"); - println!("πŸ™ˆ Datalog check skipped πŸ›‘οΈ"); + signatures_check = None; + auth_result = None; + query_result = None; + if authorizer_from.is_some() { Err(MissingPublicKeyForAuthorization)? } } - Ok(()) + Ok(InspectionResults { + token: token_description, + signatures_check, + auth: auth_result, + query: query_result, + }) } pub fn handle_inspect_snapshot(inspect_snapshot: &InspectSnapshot) -> Result<()> { @@ -315,7 +469,10 @@ pub fn handle_inspect_snapshot(inspect_snapshot: &InspectSnapshot) -> Result<()> Err(e) => { println!("❌ Authorizer check failed πŸ›‘οΈ"); match e { - Token::FailedLogic(l) => display_logic_error(&policies, &l), + Token::FailedLogic(l) => display_logic_error( + &policies.iter().map(|p| p.to_string()).collect::>(), + &l, + ), Token::RunLimit(l) => display_run_limit(&l), _ => {} } @@ -335,7 +492,7 @@ pub fn handle_inspect_snapshot(inspect_snapshot: &InspectSnapshot) -> Result<()> Ok(()) } -fn display_logic_error(policies: &[Policy], e: &Logic) { +fn display_logic_error(policies: &[String], e: &Logic) { match e { Logic::Unauthorized { policy, checks } => { display_matched_policy(policies, policy); @@ -349,7 +506,7 @@ fn display_logic_error(policies: &[Policy], e: &Logic) { } } -fn display_matched_policy(policies: &[Policy], policy: &MatchedPolicy) { +fn display_matched_policy(policies: &[String], policy: &MatchedPolicy) { match policy { MatchedPolicy::Allow(i) => { let policy = policies.get(*i); diff --git a/src/main.rs b/src/main.rs index 51fd951..1d7ce0f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use biscuit_auth::{ Biscuit, {KeyPair, PrivateKey}, }; use clap::Parser; +use serde_json; use std::io; use std::io::Write; use std::path::PathBuf; From 9811fd1b2e2895ff2313e64e9534994e9d27351c Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Thu, 20 Jul 2023 15:19:01 +0200 Subject: [PATCH 3/5] fix: improve JSON encoding of sig, auth & query errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The naΓ―ve encoding of `Result` in JSON is an object with either a `Ok` or a `Err` field, containing the JSON encoding of either the value or the error. This is a rather uncommon JSON encoding for errors and ties the output to the implementation language. This commit introduces an equivalent enum, with a different encoding: - in case of success, the value is serialized without any wrapping - in case of error, the error is wrapped in an object with a single `error` field ```json { "query": "user($u) <- user($u)", "query_all": false, "facts": [ "user(\"1234\")"] } ``` ```json { "query": "user($u) <- user($u)", "query_all": false, "facts": { "error": … } } ``` --- src/inspect.rs | 55 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/src/inspect.rs b/src/inspect.rs index dd307b4..0627fa9 100644 --- a/src/inspect.rs +++ b/src/inspect.rs @@ -59,11 +59,36 @@ impl TokenDescription { } } +#[derive(Copy, Clone, Serialize)] +#[serde(untagged)] +enum RResult { + Ok(A), + Err { error: E }, +} + +impl From> for RResult { + fn from(value: std::result::Result) -> Self { + match value { + Ok(a) => Self::Ok(a), + Err(error) => Self::Err { error }, + } + } +} + +impl RResult { + pub fn into_result(self) -> std::result::Result { + match self { + Self::Ok(a) => Ok(a), + Self::Err { error } => Err(error), + } + } +} + #[derive(Serialize)] struct QueryResult { query: String, query_all: bool, - facts: std::result::Result, Token>, + facts: RResult, Token>, } impl QueryResult { @@ -74,7 +99,7 @@ impl QueryResult { } else { println!("πŸ”Ž Running query: {}", &self.query); } - match &self.facts { + match &self.facts.clone().into_result() { Ok(facts) => { if facts.is_empty() { println!("❌ No results"); @@ -94,12 +119,12 @@ impl QueryResult { #[derive(Serialize)] struct AuthResult { policies: Vec, - result: std::result::Result<(usize, String), Token>, + result: RResult<(usize, String), Token>, } impl AuthResult { fn render(&self) { - match &self.result { + match &self.result.clone().into_result() { Ok((_, policy)) => { println!("βœ… Authorizer check succeeded πŸ›‘οΈ"); println!("Matched allow policy: {}", policy); @@ -151,13 +176,13 @@ impl InspectionResults { } if let Some(ref auth) = self.auth { - if auth.result.is_err() { + if auth.result.clone().into_result().is_err() { Err(AuthorizationFailed)?; } } if let Some(ref query) = self.query { - if query.facts.is_err() { + if query.facts.clone().into_result().is_err() { Err(QueryFailed)?; } } @@ -194,7 +219,9 @@ fn handle_query( Ok(QueryResult { query: query.to_string(), query_all, - facts: facts.map(|fs| fs.iter().map(|f| f.to_string()).collect::>()), + facts: facts + .map(|fs| fs.iter().map(|f| f.to_string()).collect::>()) + .into(), }) } @@ -332,12 +359,14 @@ pub fn handle_inspect_inner(inspect: &Inspect) -> Result { auth_result = Some(AuthResult { policies: policies.iter().map(|p| p.to_string()).collect::>(), - result: authorizer_result.map(|i| { - ( - i, - policies.get(i).expect("Incorrect policy index").to_string(), - ) - }), + result: authorizer_result + .map(|i| { + ( + i, + policies.get(i).expect("Incorrect policy index").to_string(), + ) + }) + .into(), }); if let Some(snapshot_file) = &inspect.dump_snapshot_to { From 39c6ef5b1131da495dd1e1e95ca70d7d44d61f48 Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Mon, 31 Jul 2023 17:31:40 +0200 Subject: [PATCH 4/5] return JSONified errors even when parsing fails --- src/inspect.rs | 22 ++++++++++++++++------ src/main.rs | 1 - 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/inspect.rs b/src/inspect.rs index 0627fa9..ed1548d 100644 --- a/src/inspect.rs +++ b/src/inspect.rs @@ -7,6 +7,7 @@ use biscuit_auth::{ }; use chrono::offset::Utc; use serde::Serialize; +use serde_json::json; use std::fs; use std::path::PathBuf; @@ -226,13 +227,22 @@ fn handle_query( } pub fn handle_inspect(inspect: &Inspect) -> Result<()> { - let res = handle_inspect_inner(inspect)?; - if inspect.json { - println!("{}", serde_json::to_string(&res)?); - } else { - res.render(); + match handle_inspect_inner(inspect) { + Ok(res) => { + if inspect.json { + println!("{}", serde_json::to_string(&res)?); + } else { + res.render(); + } + res.ensure_success() + } + Err(e) => { + if inspect.json { + println!("{}", json!({ "error": e.to_string() })) + } + Err(e) + } } - res.ensure_success() } pub fn handle_inspect_inner(inspect: &Inspect) -> Result { diff --git a/src/main.rs b/src/main.rs index 1d7ce0f..51fd951 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,6 @@ use biscuit_auth::{ Biscuit, {KeyPair, PrivateKey}, }; use clap::Parser; -use serde_json; use std::io; use std::io::Write; use std::path::PathBuf; From 82dba816daa64375447be555be88b494a822e52d Mon Sep 17 00:00:00 2001 From: Clement Delafargue Date: Tue, 19 Sep 2023 09:49:56 +0200 Subject: [PATCH 5/5] chore: update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e3e453..a8423e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - internal refactor, fix displayed version number (#46) - support for running authorization on a snapshot (#47) - biscuit-auth 4.0.0 (#50) +- JSON output in `biscuit inspect` (#48) # `0.4.0`