From ba616303f18c0925c2ab471fafeeabd3b2855141 Mon Sep 17 00:00:00 2001 From: muji Date: Mon, 26 Feb 2024 17:07:41 +0800 Subject: [PATCH] CLI Demos (#354) * Update demos, bump dependency. * Remove end demo include. * Add failing test spec for server command. Need to disable the networking for tests and demos. * Prepare for offline mode. * Write pending enrollment to disc. Remove when enrollment finishes successfully. * Add test spec for pending enrollment file. * Add re-export for PendingEnrollment. * Support offline argument for account creation. * Move constructors out of trait. Now that the network and local constructors have diverged to account for the offline option it's better they are not defined in the trait. * Tweak server boot output. Avoid using tracing for startup messages. * Prepare for folders demo. * Tidy success message handling. * Move shell specs to individual files. * Improve folders demo. * Sort folders list, accept comments in shell. * Invert sort order. * Update shell demo. * Add quit to shell demo. * Add security-report and audit specs/demos. Improve handling of SOS_OFFLINE. * Fix RUST_LOG for audit logs demo. * Add device list to specs and demos. * Tidy default device meta data. * Support preferences command. * Add preferences command test spec. * Add preferences demo, prepare environment command. * Support path filter for env paths command. * Add integrity check demo. * Add events command demo. * Tidy device list output. Just print the entire slice when verbose. * Tidy audit log test script. Use new `env` command. * Add commands for printing enum variants. * Add demo of the environment command. --- Cargo.lock | 120 +++++--- Cargo.toml | 4 +- Makefile.toml | 7 +- TEST.md | 16 +- sandbox/config.toml | 3 + scripts/cli/demos.sh | 14 +- scripts/cli/env.sh | 11 +- scripts/cli/shell.sh | 1 - scripts/cli/specs.sh | 11 +- src/cli/sos.rs | 47 +-- src/commands/account.rs | 12 +- src/commands/audit.rs | 2 +- src/commands/check.rs | 6 +- src/commands/device.rs | 23 +- src/commands/environment.rs | 166 ++++++++++ src/commands/file.rs | 5 +- src/commands/folder.rs | 14 +- src/commands/mod.rs | 5 +- src/commands/preferences.rs | 194 ++++++++++++ src/commands/secret.rs | 23 +- src/commands/security_report.rs | 3 +- src/commands/server.rs | 20 +- src/commands/shell/repl.rs | 96 ++---- src/commands/sync.rs | 9 +- src/error.rs | 18 +- src/helpers/account.rs | 38 ++- src/helpers/secret.rs | 126 +++----- tests/access_control/allow.rs | 1 + tests/access_control/deny.rs | 1 + tests/command_line/main.rs | 19 +- tests/command_line/scripts/audit/logs.sh | 11 + tests/command_line/scripts/check/events.sh | 3 +- tests/command_line/scripts/check/header.sh | 3 +- tests/command_line/scripts/check/keys.sh | 3 +- tests/command_line/scripts/check/vault.sh | 3 +- .../scripts/demos/accounts-basic.sh | 20 -- .../command_line/scripts/demos/audit-logs.sh | 1 + tests/command_line/scripts/demos/check.sh | 4 + .../command_line/scripts/demos/device-list.sh | 1 + .../command_line/scripts/demos/environment.sh | 11 + tests/command_line/scripts/demos/events.sh | 11 + .../scripts/demos/folder-basic.sh | 31 ++ tests/command_line/scripts/demos/help.sh | 2 +- .../command_line/scripts/demos/preferences.sh | 7 + .../{secrets-basic.sh => secret-basic.sh} | 8 +- .../scripts/demos/security-report.sh | 20 ++ .../scripts/demos/server-basic.sh | 15 + tests/command_line/scripts/demos/server.sh | 2 +- .../scripts/demos/setup/account-basic.sh | 21 ++ .../command_line/scripts/demos/shell-basic.sh | 28 ++ tests/command_line/scripts/device/list.sh | 9 + .../scripts/environment/paths-filter.sh | 3 + .../command_line/scripts/environment/paths.sh | 3 + .../scripts/environment/print-path-filters.sh | 3 + .../command_line/scripts/environment/vars.sh | 3 + tests/command_line/scripts/events/account.sh | 3 + tests/command_line/scripts/events/device.sh | 3 + tests/command_line/scripts/events/file.sh | 3 + tests/command_line/scripts/events/folder.sh | 3 + tests/command_line/scripts/folder/commits.sh | 2 +- .../scripts/folder/history/check.sh | 2 +- .../scripts/folder/history/compact.sh | 2 +- .../scripts/folder/history/list.sh | 2 +- tests/command_line/scripts/folder/info.sh | 2 +- tests/command_line/scripts/folder/keys.sh | 2 +- tests/command_line/scripts/folder/new.sh | 2 +- tests/command_line/scripts/folder/remove.sh | 2 +- tests/command_line/scripts/folder/rename.sh | 4 +- .../command_line/scripts/preferences/clear.sh | 5 + .../command_line/scripts/preferences/list.sh | 4 + .../scripts/preferences/remove.sh | 5 + .../scripts/preferences/set-bool.sh | 5 + .../scripts/preferences/set-number.sh | 5 + .../scripts/preferences/set-string-list.sh | 5 + .../scripts/preferences/set-string.sh | 5 + tests/command_line/scripts/server/add.sh | 4 + tests/command_line/scripts/server/list.sh | 3 + tests/command_line/scripts/server/remove.sh | 4 + tests/command_line/scripts/shell/account.sh | 34 ++ tests/command_line/scripts/shell/basic.sh | 11 + tests/command_line/scripts/shell/contacts.sh | 8 + tests/command_line/scripts/shell/folder.sh | 49 +++ tests/command_line/scripts/shell/migrate.sh | 25 ++ tests/command_line/scripts/shell/secret.sh | 181 +++++++++++ tests/command_line/scripts/shell/switch.sh | 18 ++ tests/command_line/scripts/specs/audit.sh | 1 + tests/command_line/scripts/specs/device.sh | 1 + .../command_line/scripts/specs/environment.sh | 4 + tests/command_line/scripts/specs/events.sh | 4 + .../command_line/scripts/specs/preferences.sh | 7 + .../scripts/specs/security-report.sh | 4 + tests/command_line/scripts/specs/server.sh | 3 + tests/command_line/scripts/specs/shell.sh | 290 +----------------- tests/pairing/device_revoke.rs | 4 +- tests/pairing/main.rs | 3 + tests/pairing/pairing_protocol.rs | 4 +- tests/pairing/pending_enrollment.rs | 48 +++ workspace/net/Cargo.toml | 1 + .../net/src/client/account/network_account.rs | 57 +++- workspace/net/src/client/account/sync.rs | 22 ++ workspace/net/src/client/hashcheck.rs | 12 +- workspace/net/src/client/mod.rs | 6 + .../net/src/client/pairing/enrollment.rs | 24 ++ workspace/net/src/client/pairing/mod.rs | 2 +- workspace/net/src/server/server.rs | 20 +- workspace/sdk/Cargo.toml | 1 + workspace/sdk/src/account/account.rs | 62 ++-- workspace/sdk/src/account/preferences.rs | 22 +- workspace/sdk/src/constants.rs | 15 + workspace/sdk/src/date_time.rs | 5 + workspace/sdk/src/device.rs | 6 - workspace/sdk/src/migrate/import/mod.rs | 3 +- workspace/sdk/src/storage/paths.rs | 19 +- workspace/test_utils/src/network.rs | 2 + workspace/test_utils/src/pairing.rs | 9 +- 115 files changed, 1614 insertions(+), 691 deletions(-) create mode 100644 src/commands/environment.rs create mode 100644 src/commands/preferences.rs create mode 100644 tests/command_line/scripts/audit/logs.sh delete mode 100644 tests/command_line/scripts/demos/accounts-basic.sh create mode 100644 tests/command_line/scripts/demos/audit-logs.sh create mode 100644 tests/command_line/scripts/demos/check.sh create mode 100644 tests/command_line/scripts/demos/device-list.sh create mode 100644 tests/command_line/scripts/demos/environment.sh create mode 100644 tests/command_line/scripts/demos/events.sh create mode 100644 tests/command_line/scripts/demos/folder-basic.sh create mode 100644 tests/command_line/scripts/demos/preferences.sh rename tests/command_line/scripts/demos/{secrets-basic.sh => secret-basic.sh} (71%) create mode 100644 tests/command_line/scripts/demos/security-report.sh create mode 100644 tests/command_line/scripts/demos/server-basic.sh create mode 100644 tests/command_line/scripts/demos/setup/account-basic.sh create mode 100644 tests/command_line/scripts/demos/shell-basic.sh create mode 100644 tests/command_line/scripts/device/list.sh create mode 100644 tests/command_line/scripts/environment/paths-filter.sh create mode 100644 tests/command_line/scripts/environment/paths.sh create mode 100644 tests/command_line/scripts/environment/print-path-filters.sh create mode 100644 tests/command_line/scripts/environment/vars.sh create mode 100644 tests/command_line/scripts/events/account.sh create mode 100644 tests/command_line/scripts/events/device.sh create mode 100644 tests/command_line/scripts/events/file.sh create mode 100644 tests/command_line/scripts/events/folder.sh create mode 100644 tests/command_line/scripts/preferences/clear.sh create mode 100644 tests/command_line/scripts/preferences/list.sh create mode 100644 tests/command_line/scripts/preferences/remove.sh create mode 100644 tests/command_line/scripts/preferences/set-bool.sh create mode 100644 tests/command_line/scripts/preferences/set-number.sh create mode 100644 tests/command_line/scripts/preferences/set-string-list.sh create mode 100644 tests/command_line/scripts/preferences/set-string.sh create mode 100644 tests/command_line/scripts/server/add.sh create mode 100644 tests/command_line/scripts/server/list.sh create mode 100644 tests/command_line/scripts/server/remove.sh create mode 100644 tests/command_line/scripts/shell/account.sh create mode 100644 tests/command_line/scripts/shell/basic.sh create mode 100644 tests/command_line/scripts/shell/contacts.sh create mode 100644 tests/command_line/scripts/shell/folder.sh create mode 100644 tests/command_line/scripts/shell/migrate.sh create mode 100644 tests/command_line/scripts/shell/secret.sh create mode 100644 tests/command_line/scripts/shell/switch.sh create mode 100644 tests/command_line/scripts/specs/audit.sh create mode 100644 tests/command_line/scripts/specs/device.sh create mode 100644 tests/command_line/scripts/specs/environment.sh create mode 100644 tests/command_line/scripts/specs/events.sh create mode 100644 tests/command_line/scripts/specs/preferences.sh create mode 100644 tests/command_line/scripts/specs/security-report.sh create mode 100644 tests/command_line/scripts/specs/server.sh create mode 100644 tests/pairing/pending_enrollment.rs diff --git a/Cargo.lock b/Cargo.lock index cb7f58ff5a..2961e06a5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -204,17 +204,22 @@ dependencies = [ [[package]] name = "anticipate" -version = "0.8.1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527a4816019990ba61cc1b43f2208865a9e8a612bfaf50db1b6337b2e8307dc2" dependencies = [ "conpty", "nix 0.26.4", "ptyprocess", "regex", + "thiserror", ] [[package]] name = "anticipate-runner" -version = "0.4.0" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "092b50675bfca2765af4162662642f7e55dcab7865cb087a0b6a2c2cda97cce1" dependencies = [ "anticipate", "comma", @@ -634,9 +639,9 @@ checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" [[package]] name = "bumpalo" -version = "3.15.2" +version = "3.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3b1be7772ee4501dba05acbe66bb1e8760f6a6c474a36035631638e4415f130" +checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" [[package]] name = "byte-slice-cast" @@ -732,7 +737,7 @@ dependencies = [ "iana-time-zone", "num-traits", "serde", - "windows-targets 0.52.0", + "windows-targets 0.52.3", ] [[package]] @@ -1086,12 +1091,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.6" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c376d08ea6aa96aafe61237c7200d1241cb177b7d3a542d791f2d118e9cbb955" +checksum = "3a5d17510e4a1a87f323de70b7b1eaac1ee0e37866c6720b2d279452d0edf389" dependencies = [ - "darling_core 0.20.6", - "darling_macro 0.20.6", + "darling_core 0.20.7", + "darling_macro 0.20.7", ] [[package]] @@ -1124,9 +1129,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.6" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33043dcd19068b8192064c704b3f83eb464f91f1ff527b44a4e2b08d9cdb8855" +checksum = "a98eea36a7ff910fa751413d0895551143a8ea41d695d9798ec7d665df7f7f5e" dependencies = [ "fnv", "ident_case", @@ -1160,11 +1165,11 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.6" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5a91391accf613803c2a9bf9abccdbaa07c54b4244a5b64883f9c3c137c86be" +checksum = "d6a366a3f90c5d59a4b91169775f88e52e8f71a0e7804cc98a8db2932cf4ed57" dependencies = [ - "darling_core 0.20.6", + "darling_core 0.20.7", "quote 1.0.35", "syn 2.0.50", ] @@ -1390,6 +1395,26 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +[[package]] +name = "enum-iterator" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "600536cfe9e2da0820aa498e570f6b2b9223eec3ce2f835c8ae4861304fa4794" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03cdc46ec28bd728e67540c528013c6a10eb69a02eb31078a1bda695438cbfb8" +dependencies = [ + "proc-macro2 1.0.78", + "quote 1.0.35", + "syn 2.0.50", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -2212,9 +2237,9 @@ dependencies = [ [[package]] name = "image" -version = "0.24.8" +version = "0.24.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034bbe799d1909622a74d1193aa50147769440040ff36cb2baa947609b0a4e23" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" dependencies = [ "bytemuck", "byteorder", @@ -2837,9 +2862,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.100" +version = "0.9.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae94056a791d0e1217d18b6cbdccb02c61e3054fc69893607f4067e3bb0b1fd1" +checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" dependencies = [ "cc", "libc", @@ -3898,7 +3923,7 @@ version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "865f9743393e638991566a8b7a479043c2c8da94a33e0a31f18214c9cae0a64d" dependencies = [ - "darling 0.20.6", + "darling 0.20.7", "proc-macro2 1.0.78", "quote 1.0.35", "syn 2.0.50", @@ -4084,6 +4109,7 @@ dependencies = [ "crossterm", "csv-async", "ctrlc", + "enum-iterator", "futures", "http 1.0.0", "human_bytes", @@ -4143,6 +4169,7 @@ dependencies = [ "axum-server", "binary-stream", "bs58", + "colored", "file-guard", "futures", "hex", @@ -4199,6 +4226,7 @@ dependencies = [ "chbs", "csv-async", "ed25519-dalek", + "enum-iterator", "filetime", "futures", "futures-util", @@ -5162,9 +5190,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vsss-rs" -version = "3.3.4" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196bbee60607a195bc850e94f0e040bd090e45794ad8df0e9c5a422b9975a00f" +checksum = "14d9b5a415d1bbbf1a9c5982babc0d8054fa10effe9c5b7086cd2fdd36be79d9" dependencies = [ "elliptic-curve", "rand", @@ -5423,7 +5451,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.3", ] [[package]] @@ -5450,7 +5478,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.3", ] [[package]] @@ -5485,17 +5513,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.3", + "windows_aarch64_msvc 0.52.3", + "windows_i686_gnu 0.52.3", + "windows_i686_msvc 0.52.3", + "windows_x86_64_gnu 0.52.3", + "windows_x86_64_gnullvm 0.52.3", + "windows_x86_64_msvc 0.52.3", ] [[package]] @@ -5512,9 +5540,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" [[package]] name = "windows_aarch64_msvc" @@ -5530,9 +5558,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" [[package]] name = "windows_i686_gnu" @@ -5548,9 +5576,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" [[package]] name = "windows_i686_msvc" @@ -5566,9 +5594,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" [[package]] name = "windows_x86_64_gnu" @@ -5584,9 +5612,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" [[package]] name = "windows_x86_64_gnullvm" @@ -5602,9 +5630,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" [[package]] name = "windows_x86_64_msvc" @@ -5620,9 +5648,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" [[package]] name = "winnow" diff --git a/Cargo.toml b/Cargo.toml index 091fe2fc51..e6810e40ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,6 +62,7 @@ parking_lot = "0.12" indexmap = { version = "2.2", features = ["serde"] } toml = "0.8" bitflags = { version = "2", features = ["serde"] } +enum-iterator = "2" [workspace.dependencies.binary-stream] version = "8.3.0" @@ -81,6 +82,7 @@ parking_lot.workspace = true once_cell.workspace = true toml.workspace = true serde.workspace = true +enum-iterator.workspace = true human_bytes = "0.4" tempfile = "3.5" shell-words = "1" @@ -116,7 +118,7 @@ copy_dir = "0.1" maplit2 = "1" sos_test_utils = { path = "workspace/test_utils" } pretty_assertions = "1.4" -anticipate-runner = { version = "0.4" } +anticipate-runner = { version = "0.5.1" } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen-test = "0.3" diff --git a/Makefile.toml b/Makefile.toml index 092705dbda..f0fb21662f 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -82,6 +82,11 @@ args = ["test", "audit"] command = "cargo" args = ["test", "backup_archive"] +[tasks.test-command-line] +command = "cargo" +args = ["test", "command_line", "--features", "enable-cli-tests", "--", "--nocapture"] +dependencies = ["clean-cli"] + [tasks.test-event-log] command = "cargo" args = ["test", "event_log_"] @@ -121,7 +126,7 @@ args = ["test", "system_messages_"] [tasks.clean-cli] script = ''' rm -rf target/accounts -rm -f target/*.{zip,vcf,heic,txt} +rm -f target/*.{zip,vcf,heic,txt,toml,csv,json} ''' [tasks.test-cli] diff --git a/TEST.md b/TEST.md index 5ba79c917c..a0a7af61c3 100644 --- a/TEST.md +++ b/TEST.md @@ -24,8 +24,22 @@ The CLI test specs can take a long time with the debug build so if you want to s cargo make test-lite ``` +To run just the command line tests which would be included in test coverage: + +``` +cargo make test-command-line +``` + +These tests always use a debug version of the executable. Run with `ANTICIPATE_ECHO` to debug: + +``` +ANTICIPATE_ECHO=true cargo make test-command-line +``` + ## Test Scripts +The test scripts make it much faster to run tests by using a release version installed with `cargo install --path .`. + To run the CLI test specs using the first version of `sos` in `PATH`: ``` @@ -45,7 +59,7 @@ ANTICIPATE_ECHO=true cargo make test-cli ANTICIPATE_ECHO=true cargo make test-shell ``` -Run `cargo install --path .` to install a release version and make these tests much faster. +Use the `SPEC` variable to run a specific test: ## Notes diff --git a/sandbox/config.toml b/sandbox/config.toml index 47a2ad4823..7d173653ae 100644 --- a/sandbox/config.toml +++ b/sandbox/config.toml @@ -2,6 +2,9 @@ path = "./accounts" #[access] +#allow = [ + #"0x6f4e977644ca8f21d335ab13271616b615ea28cb" +#] #deny = [ #"0x6f4e977644ca8f21d335ab13271616b615ea28cb" #] diff --git a/scripts/cli/demos.sh b/scripts/cli/demos.sh index 5b6792c023..09b6b1da51 100755 --- a/scripts/cli/demos.sh +++ b/scripts/cli/demos.sh @@ -4,17 +4,15 @@ set -e source scripts/cli/env.sh -rm target/*.{zip,vcf,heic} +scripts=tests/command_line/scripts +SPECS=($scripts/demos/*.sh) +SPEC=${SPEC:-${SPECS[@]}} -scripts=tests/command_line/scripts/demos anticipate \ record \ - --parallel \ --overwrite \ + --parallel \ --print-comments \ - --setup $scripts/accounts-basic.sh \ + --setup $scripts/demos/setup/account-basic.sh \ demos \ - $scripts/version.sh \ - $scripts/help.sh \ - $scripts/server.sh \ - $scripts/secrets-basic.sh + $SPEC diff --git a/scripts/cli/env.sh b/scripts/cli/env.sh index 6e44a22516..acbc2e3c4b 100644 --- a/scripts/cli/env.sh +++ b/scripts/cli/env.sh @@ -1,6 +1,11 @@ -#!/bin/sh +export SOS_PROMPT='➜ ' +export SOS_OFFLINE=1 +export DEMO_SERVER="https://demo.saveoursecrets.com" +export RUST_LOG="sos_net=error,sos_sdk=error,sos=info" -set -e +# suppress the bash is replaced by zsh on macos +# SEE: https://support.apple.com/en-us/102360 +export BASH_SILENCE_DEPRECATION_WARNING=1 export SOS_DATA_DIR="target/accounts" export ACCOUNT_NAME="Demo" @@ -11,7 +16,7 @@ export ACCOUNT_CONTACTS="tests/fixtures/contacts.vcf" export CONTACTS_EXPORT="target/demo-contacts.vcf" export DEFAULT_FOLDER_NAME="Documents" -export FOLDER_NAME="mock-folder" +export FOLDER_NAME="Demo Folder" export NEW_FOLDER_NAME="mock-folder-renamed" export FILE_INPUT="tests/fixtures/sample.heic" diff --git a/scripts/cli/shell.sh b/scripts/cli/shell.sh index 8b67517bec..3ac9651e22 100755 --- a/scripts/cli/shell.sh +++ b/scripts/cli/shell.sh @@ -4,7 +4,6 @@ set -e source scripts/cli/env.sh export NO_COLOR=1 -export SOS_PROMPT='➜ ' if [ -n "$DEBUG" ]; then export PATH="target/debug:$PATH" diff --git a/scripts/cli/specs.sh b/scripts/cli/specs.sh index f3da6bd4be..d54a88127e 100755 --- a/scripts/cli/specs.sh +++ b/scripts/cli/specs.sh @@ -4,7 +4,6 @@ set -e source scripts/cli/env.sh export NO_COLOR=1 -export SOS_PROMPT='➜ ' if [ -n "$DEBUG" ]; then export PATH="target/debug:$PATH" @@ -12,8 +11,12 @@ fi command -v sos +scripts=tests/command_line/scripts +SPECS=($scripts/specs/*.sh) +SPEC=${SPEC:-${SPECS[@]}} + anticipate \ run \ - --setup tests/command_line/scripts/setup.sh \ - --teardown tests/command_line/scripts/teardown.sh \ - tests/command_line/scripts/specs/*.sh + --setup $scripts/setup.sh \ + --teardown $scripts/teardown.sh \ + $SPEC diff --git a/src/cli/sos.rs b/src/cli/sos.rs index 23a63d7953..d8600416d6 100644 --- a/src/cli/sos.rs +++ b/src/cli/sos.rs @@ -4,11 +4,13 @@ use std::path::PathBuf; use crate::{ commands::{ - account, audit, check, device, events, file, folder, secret, + account, audit, check, device, environment, events, file, folder, + preferences, secret, security_report::{self, SecurityReportFormat}, server, shell, sync, AccountCommand, AuditCommand, CheckCommand, - DeviceCommand, EventsCommand, FileCommand, FolderCommand, - SecretCommand, ServerCommand, SyncCommand, + DeviceCommand, EnvironmentCommand, EventsCommand, FileCommand, + FolderCommand, PreferenceCommand, SecretCommand, ServerCommand, + SyncCommand, }, helpers::{PROGRESS_MONITOR, USER}, CommandTree, Result, @@ -85,25 +87,26 @@ pub enum Command { /// Inspect all passwords in an account and report /// passwords with an entropy score less than 3 or /// passwords that are breached. - /// - /// Use the --include-all option to include passwords - /// that appear to be safe in the report. SecurityReport { /// Force overwrite if the file exists. - #[clap(short, long)] + #[clap(long)] force: bool, /// Account name or address. #[clap(short, long)] account: Option, - /// Include all passwords. + /// Include all entries. + /// + /// Security reports by default only include + /// entries that fail, use this option to include + /// entries that passed the security threshold. #[clap(short, long)] include_all: bool, /// Output format: csv or json. #[clap(short, long, default_value = "csv")] - output_format: SecurityReportFormat, + format: SecurityReportFormat, /// Write report to this file. file: PathBuf, @@ -132,6 +135,18 @@ pub enum Command { /// Account name or address. account: Option, }, + /// View and edit account preferences. + #[clap(alias = "prefs")] + Preferences { + #[clap(subcommand)] + cmd: PreferenceCommand, + }, + /// Print environment and paths. + #[clap(alias = "env")] + Environment { + #[clap(subcommand)] + cmd: EnvironmentCommand, + }, } pub async fn run() -> Result<()> { @@ -180,18 +195,12 @@ pub async fn run() -> Result<()> { Command::SecurityReport { account, force, - output_format, + format, include_all, file, } => { - security_report::run( - account, - force, - output_format, - include_all, - file, - ) - .await? + security_report::run(account, force, format, include_all, file) + .await? } Command::Audit { cmd } => audit::run(cmd).await?, Command::Check { cmd } => check::run(cmd).await?, @@ -199,6 +208,8 @@ pub async fn run() -> Result<()> { Command::Shell { account, folder } => { shell::run(account, folder).await? } + Command::Preferences { cmd } => preferences::run(cmd).await?, + Command::Environment { cmd } => environment::run(cmd).await?, } Ok(()) } diff --git a/src/commands/account.rs b/src/commands/account.rs index aafb03a08f..8df8850a5e 100644 --- a/src/commands/account.rs +++ b/src/commands/account.rs @@ -1,6 +1,6 @@ use clap::Subcommand; use std::{path::PathBuf, sync::Arc}; - +use enum_iterator::all; use sos_net::{ client::NetworkAccount, sdk::{ @@ -161,6 +161,8 @@ pub enum MigrateCommand { /// Input file to import. input: PathBuf, }, + /// Print available import formats. + PrintImportFormats, } #[derive(Subcommand, Debug)] @@ -215,9 +217,9 @@ pub async fn run(cmd: Command) -> Result<()> { success("Account renamed"); } Command::Migrate { account, cmd } => { - let user = resolve_user(account.as_ref(), false).await?; match cmd { MigrateCommand::Export { output, force } => { + let user = resolve_user(account.as_ref(), false).await?; let exported = migrate_export(user, output, force).await?; if exported { @@ -229,9 +231,15 @@ pub async fn run(cmd: Command) -> Result<()> { format, name, } => { + let user = resolve_user(account.as_ref(), false).await?; migrate_import(user, input, format, name).await?; success("File imported"); } + MigrateCommand::PrintImportFormats => { + for variant in all::() { + println!("{}", variant); + } + } } } Command::Contacts { account, cmd } => { diff --git a/src/commands/audit.rs b/src/commands/audit.rs index ebef07da16..069441a13c 100644 --- a/src/commands/audit.rs +++ b/src/commands/audit.rs @@ -21,7 +21,7 @@ pub enum Command { #[clap(short, long)] reverse: bool, - /// Limit events to displayed to this count + /// Limit events displayed to this amount #[clap(short, long)] count: Option, diff --git a/src/commands/check.rs b/src/commands/check.rs index 1c0e7bf15a..d435d4c5ea 100644 --- a/src/commands/check.rs +++ b/src/commands/check.rs @@ -8,7 +8,7 @@ use sos_net::sdk::{ vfs, }; -use crate::{Error, Result}; +use crate::{helpers::messages::success, Error, Result}; #[derive(Subcommand, Debug)] pub enum Command { @@ -68,7 +68,7 @@ async fn verify_vault(file: PathBuf, verbose: bool) -> Result<()> { } }) .await?; - println!("Verified ✓"); + success("Verified"); Ok(()) } @@ -88,7 +88,7 @@ async fn verify_events(file: PathBuf, verbose: bool) -> Result<()> { println!("root: {}", root); } } - println!("Verified {} commit(s) ✓", tree.len()); + success(format!("Verified {} commit(s)", tree.len())); Ok(()) } diff --git a/src/commands/device.rs b/src/commands/device.rs index d6b1788b46..c49634e20e 100644 --- a/src/commands/device.rs +++ b/src/commands/device.rs @@ -8,7 +8,9 @@ use sos_net::{ }; use crate::{ - helpers::{account::resolve_user, readline::read_flag}, + helpers::{ + account::resolve_user, messages::success, readline::read_flag, + }, Error, Result, }; @@ -41,9 +43,7 @@ async fn resolve_device( id: &str, ) -> Result> { let owner = user.read().await; - let storage = owner.storage().await?; - let storage = storage.read().await; - let devices = storage.list_trusted_devices(); + let devices = owner.trusted_devices().await?; for device in devices { if device.public_id()? == id { return Ok(Some(device.clone())); @@ -58,14 +58,11 @@ pub async fn run(cmd: Command) -> Result<()> { let user = resolve_user(account.as_ref(), false).await?; let owner = user.read().await; let devices = owner.trusted_devices().await?; - - for device in devices { - println!("{}", device.public_id()?); - if verbose { - println!( - "{}", - serde_json::to_string_pretty(device.extra_info())? - ); + if verbose { + println!("{}", serde_json::to_string_pretty(&devices)?); + } else { + for device in devices { + println!("{}", device.public_id()?); } } } @@ -78,7 +75,7 @@ pub async fn run(cmd: Command) -> Result<()> { if read_flag(Some(&prompt))? { let mut owner = user.write().await; owner.revoke_device(device.public_key()).await?; - println!("Device revoked ✓"); + success("Device revoked"); } } else { return Err(Error::DeviceNotFound(id)); diff --git a/src/commands/environment.rs b/src/commands/environment.rs new file mode 100644 index 0000000000..4c313c6373 --- /dev/null +++ b/src/commands/environment.rs @@ -0,0 +1,166 @@ +use crate::{helpers::account::resolve_account_address, Result}; +use clap::Subcommand; +use enum_iterator::{all, Sequence}; +use sos_net::sdk::prelude::*; +use std::{fmt, str::FromStr}; + +/// Filter used for printing paths. +#[derive(Debug, Clone, Sequence)] +pub enum PathFilter { + /// Root data directory. + Data, + /// Directory for identity folders. + Identity, + /// Account storage directory. + Accounts, + /// Logs directory. + Logs, + /// Audit file. + Audit, + /// User directory. + User, + /// User files directory. + Files, + /// User folders directory. + Folders, + /// Device vault. + Device, + /// Identity vault. + IdentityVault, + /// Identity events. + IdentityEvents, + /// Account events. + AccountEvents, + /// Device events. + DeviceEvents, + /// File events. + FileEvents, +} + +impl PathFilter { + fn print(&self, paths: &Paths) { + match self { + Self::Data => println!("{}", paths.documents_dir().display()), + Self::Identity => println!("{}", paths.identity_dir().display()), + Self::Accounts => println!("{}", paths.local_dir().display()), + Self::Logs => println!("{}", paths.logs_dir().display()), + Self::Audit => println!("{}", paths.audit_file().display()), + Self::User => println!("{}", paths.user_dir().display()), + Self::Files => println!("{}", paths.files_dir().display()), + Self::Folders => println!("{}", paths.vaults_dir().display()), + Self::Device => println!("{}", paths.device_file().display()), + Self::IdentityVault => { + println!("{}", paths.identity_vault().display()) + } + Self::IdentityEvents => { + println!("{}", paths.identity_events().display()) + } + Self::AccountEvents => { + println!("{}", paths.account_events().display()) + } + Self::DeviceEvents => { + println!("{}", paths.device_events().display()) + } + Self::FileEvents => println!("{}", paths.file_events().display()), + } + } +} + +impl fmt::Display for PathFilter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Data => write!(f, "data"), + Self::Identity => write!(f, "identity"), + Self::Accounts => write!(f, "accounts"), + Self::Logs => write!(f, "logs"), + Self::Audit => write!(f, "audit"), + Self::User => write!(f, "user"), + Self::Files => write!(f, "files"), + Self::Folders => write!(f, "folders"), + Self::Device => write!(f, "device"), + Self::IdentityVault => write!(f, "identity-vault"), + Self::IdentityEvents => write!(f, "identity-events"), + Self::AccountEvents => write!(f, "account-events"), + Self::DeviceEvents => write!(f, "device-events"), + Self::FileEvents => write!(f, "file-events"), + } + } +} + +impl FromStr for PathFilter { + type Err = crate::Error; + + fn from_str(s: &str) -> Result { + Ok(match s { + "data" => Self::Data, + "identity" => Self::Identity, + "accounts" => Self::Accounts, + "logs" => Self::Logs, + "audit" => Self::Audit, + "user" => Self::User, + "files" => Self::Files, + "folders" => Self::Folders, + "device" => Self::Device, + "identity-vault" => Self::IdentityVault, + "identity-events" => Self::IdentityEvents, + "account-events" => Self::AccountEvents, + "device-events" => Self::DeviceEvents, + "file-events" => Self::FileEvents, + _ => return Err(crate::Error::UnknownPathFilter(s.to_string())), + }) + } +} + +#[derive(Subcommand, Debug)] +pub enum Command { + /// Print environment variables. + #[clap(alias = "var")] + Vars, + /// Print account paths. + #[clap(alias = "path")] + Paths { + /// Account name or address. + #[clap(short, long)] + account: Option, + + /// Filter paths to print. + #[clap(short, long)] + filter: Vec, + }, + /// Print available path filters. + PrintPathFilters, +} + +/// Handle env commands. +pub async fn run(cmd: Command) -> Result<()> { + match cmd { + Command::Vars => { + let vars = [SOS_DATA_DIR, SOS_OFFLINE, SOS_PROMPT]; + for var in vars { + print!("{}=", var); + match std::env::var(var) { + Ok(val) => println!(r#""{}""#, val), + Err(_) => println!("unset"), + } + } + } + Command::Paths { account, filter } => { + let address = resolve_account_address(account.as_ref()).await?; + let paths = Paths::new(Paths::data_dir()?, address.to_string()); + if filter.is_empty() { + let value = toml::to_string_pretty(&paths)?; + print!("{}", value); + } else { + for item in filter { + item.print(&paths); + } + } + } + Command::PrintPathFilters => { + for variant in all::() { + println!("{}", variant); + } + } + } + Ok(()) +} diff --git a/src/commands/file.rs b/src/commands/file.rs index c4326e8782..c744574255 100644 --- a/src/commands/file.rs +++ b/src/commands/file.rs @@ -1,6 +1,7 @@ use crate::{ helpers::{ - account::resolve_user, readline::clear_screen, PROGRESS_MONITOR, + account::resolve_user, messages::success, readline::clear_screen, + PROGRESS_MONITOR, }, Result, }; @@ -85,7 +86,7 @@ pub async fn run(cmd: Command) -> Result<()> { clear_screen()?; if failures.is_empty() { - println!("Files ok ✓"); + success("Files ok"); } else { for (_, failure) in failures { match failure { diff --git a/src/commands/folder.rs b/src/commands/folder.rs index c3865b88b3..418e4aa50f 100644 --- a/src/commands/folder.rs +++ b/src/commands/folder.rs @@ -12,6 +12,7 @@ use sos_net::sdk::{ use crate::{ helpers::{ account::{cd_folder, resolve_folder, resolve_user, USER}, + messages::success, readline::read_flag, }, Error, Result, @@ -165,7 +166,7 @@ pub async fn run(cmd: Command) -> Result<()> { let FolderCreate { folder, .. } = writer.create_folder(name).await?; - println!("Folder created ✓"); + success("Folder created"); drop(writer); if cwd { let target = Some(FolderRef::Id(*folder.id())); @@ -198,7 +199,7 @@ pub async fn run(cmd: Command) -> Result<()> { if read_flag(Some(&prompt))? { let mut owner = user.write().await; owner.delete_folder(&summary).await?; - println!("Folder deleted ✓"); + success("Folder deleted"); drop(owner); // Removing current folder so try to use @@ -211,9 +212,8 @@ pub async fn run(cmd: Command) -> Result<()> { Command::List { account, verbose } => { let user = resolve_user(account.as_ref(), false).await?; let owner = user.read().await; - let storage = owner.storage().await?; - let reader = storage.read().await; - let folders = reader.list_folders(); + let mut folders = owner.list_folders().await?; + folders.sort_by(|a, b| b.name().partial_cmp(a.name()).unwrap()); for summary in folders { if verbose { println!("{} {}", summary.id(), summary.name()); @@ -291,7 +291,7 @@ pub async fn run(cmd: Command) -> Result<()> { let mut writer = user.write().await; writer.rename_folder(&summary, name.clone()).await?; - println!("{} -> {} ✓", summary.name(), name); + success(format!("{} -> {}", summary.name(), name)); } Command::History { cmd } => { @@ -350,7 +350,7 @@ pub async fn run(cmd: Command) -> Result<()> { .current_folder() .ok_or(Error::NoVaultSelected)?; reader.verify(&summary).await?; - println!("Verified ✓"); + success("Verified"); } History::List { verbose, .. } => { let owner = user.read().await; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index b018733b9f..f339033a4c 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,11 +1,12 @@ pub mod account; pub mod audit; -//pub mod changes; pub mod check; pub mod device; +pub mod environment; pub mod events; pub mod file; pub mod folder; +pub mod preferences; pub mod secret; pub mod security_report; pub mod server; @@ -16,9 +17,11 @@ pub use account::Command as AccountCommand; pub use audit::Command as AuditCommand; pub use check::Command as CheckCommand; pub use device::Command as DeviceCommand; +pub use environment::Command as EnvironmentCommand; pub use events::Command as EventsCommand; pub use file::Command as FileCommand; pub use folder::Command as FolderCommand; +pub use preferences::Command as PreferenceCommand; pub use secret::Command as SecretCommand; pub use server::Command as ServerCommand; pub use sync::Command as SyncCommand; diff --git a/src/commands/preferences.rs b/src/commands/preferences.rs new file mode 100644 index 0000000000..8aae4bd57d --- /dev/null +++ b/src/commands/preferences.rs @@ -0,0 +1,194 @@ +use crate::{ + helpers::{ + account::resolve_user, + messages::{fail, success}, + }, + Result, +}; +use clap::Subcommand; +use sos_net::sdk::prelude::*; + +#[derive(Subcommand, Debug)] +pub enum Command { + /// List preferences. + #[clap(alias = "ls")] + List { + /// Account name or address. + #[clap(short, long)] + account: Option, + }, + /// Print a preference. + Get { + /// Account name or address. + #[clap(short, long)] + account: Option, + + /// Preference key. + key: String, + }, + /// Remove a preference. + #[clap(alias = "rm")] + Remove { + /// Account name or address. + #[clap(short, long)] + account: Option, + + /// Preference key. + key: String, + }, + /// Set a boolean preference. + #[clap(alias = "bool")] + Boolean { + /// Account name or address. + #[clap(short, long)] + account: Option, + + /// Preference key. + key: String, + + /// Boolean value. + value: String, + }, + /// Set a number preference. + #[clap(alias = "num")] + Number { + /// Account name or address. + #[clap(short, long)] + account: Option, + + /// Preference key. + key: String, + + /// Numeric value (IEEE754). + value: f64, + }, + /// Set a string preference. + #[clap(alias = "str")] + String { + /// Account name or address. + #[clap(short, long)] + account: Option, + + /// Preference key. + key: String, + + /// String value. + value: String, + }, + /// Set a string list preference. + StringList { + /// Account name or address. + #[clap(short, long)] + account: Option, + + /// Preference key. + key: String, + + /// String values. + value: Vec, + }, + /// Remove all preferences. + Clear { + /// Account name or address. + #[clap(short, long)] + account: Option, + }, +} + +/// Handle preferences commands. +pub async fn run(cmd: Command) -> Result<()> { + match cmd { + Command::List { account } => { + let user = resolve_user(account.as_ref(), false).await?; + let owner = user.read().await; + let paths = owner.paths(); + let mut prefs = Preferences::new(&*paths); + prefs.load().await?; + if prefs.is_empty() { + println!("No preferences yet"); + } else { + for (key, pref) in prefs.iter() { + println!("{}={}", key, pref); + } + } + } + Command::Get { account, key } => { + let user = resolve_user(account.as_ref(), false).await?; + let owner = user.read().await; + let paths = owner.paths(); + let mut prefs = Preferences::new(&*paths); + prefs.load().await?; + if let Some(pref) = prefs.get_unchecked(&key) { + println!("{}={}", key, pref); + } else { + fail(format!("preference {} not found", key)); + } + } + Command::Remove { account, key } => { + let user = resolve_user(account.as_ref(), false).await?; + let owner = user.read().await; + let paths = owner.paths(); + let mut prefs = Preferences::new(&*paths); + prefs.load().await?; + let pref = prefs.remove(&key).await?; + if pref.is_some() { + success(format!("Removed preference {}", key)); + } else { + fail(format!("preference {} not found", key)); + } + } + Command::Boolean { + account, + key, + value, + } => { + let value: bool = value.parse()?; + set_pref(account, key, Preference::Bool(value)).await?; + } + Command::Number { + account, + key, + value, + } => { + set_pref(account, key, Preference::Number(value)).await?; + } + Command::String { + account, + key, + value, + } => { + set_pref(account, key, Preference::String(value)).await?; + } + Command::StringList { + account, + key, + value, + } => { + set_pref(account, key, Preference::StringList(value)).await?; + } + Command::Clear { account } => { + let user = resolve_user(account.as_ref(), false).await?; + let owner = user.read().await; + let paths = owner.paths(); + let mut prefs = Preferences::new(&*paths); + prefs.clear().await?; + success("Cleared all preferences"); + } + } + Ok(()) +} + +async fn set_pref( + account: Option, + key: String, + pref: Preference, +) -> Result<()> { + let user = resolve_user(account.as_ref(), false).await?; + let owner = user.read().await; + let paths = owner.paths(); + let mut prefs = Preferences::new(&*paths); + prefs.load().await?; + prefs.insert(key.clone(), pref).await?; + success(format!("Set {}", key)); + Ok(()) +} diff --git a/src/commands/secret.rs b/src/commands/secret.rs index 0b805d10d8..2f5bd7d32d 100644 --- a/src/commands/secret.rs +++ b/src/commands/secret.rs @@ -2,6 +2,7 @@ use crate::{ helpers::{ account::{resolve_folder, resolve_user, verify, Owner, USER}, editor, + messages::success, readline::{read_flag, read_line}, secret::{ add_file, add_link, add_list, add_login, add_note, add_password, @@ -733,7 +734,7 @@ pub async fn run(cmd: Command) -> Result<()> { owner.create_secret(meta, secret, options).await?; let _ = shutdown_tx.send(()).await; let _ = closed_rx.await; - println!("Secret created ✓"); + success("Secret created"); } else { let _ = shutdown_tx.send(()).await; let _ = closed_rx.await; @@ -774,7 +775,7 @@ pub async fn run(cmd: Command) -> Result<()> { owner.read_secret(&resolved.secret_id, None).await?; let copied = copy_secret_text(data.secret())?; if copied { - println!("Copied to clipboard ✓"); + success("Copied to clipboard"); } else { return Err(Error::ClipboardCopy); } @@ -946,7 +947,7 @@ pub async fn run(cmd: Command) -> Result<()> { .await?; let _ = shutdown_tx.send(()).await; let _ = closed_rx.await; - println!("Secret updated ✓"); + success("Secret updated"); // If the edited result was borrowed // it indicates that no changes were made } else { @@ -980,7 +981,7 @@ pub async fn run(cmd: Command) -> Result<()> { ) .await?; let state = if value { "on" } else { "off" }; - println!("Favorite {} ✓", state); + success(format!("Favorite {}", state)); } } Command::Rename { @@ -1008,7 +1009,7 @@ pub async fn run(cmd: Command) -> Result<()> { None, ) .await?; - println!("Secret renamed ✓"); + success("Secret renamed"); } } Command::Move { @@ -1038,7 +1039,7 @@ pub async fn run(cmd: Command) -> Result<()> { Default::default(), ) .await?; - println!("Secret moved ✓"); + success("Secret moved"); } } Command::Remove { @@ -1065,7 +1066,7 @@ pub async fn run(cmd: Command) -> Result<()> { Default::default(), ) .await?; - println!("Secret deleted ✓"); + success("Secret deleted"); } } } @@ -1125,7 +1126,7 @@ pub async fn run(cmd: Command) -> Result<()> { None, ) .await?; - println!("Secret updated ✓"); + success("Secret updated"); } } } @@ -1177,7 +1178,7 @@ pub async fn run(cmd: Command) -> Result<()> { Default::default(), ) .await?; - println!("Moved to archive ✓"); + success("Moved to archive"); } } Command::Unarchive { account, secret } => { @@ -1215,7 +1216,7 @@ pub async fn run(cmd: Command) -> Result<()> { Default::default(), ) .await?; - println!("Restored from archive ✓"); + success("Restored from archive"); if let Some(folder) = original_folder { owner.open_folder(&folder).await?; } @@ -1438,7 +1439,7 @@ async fn attachment(cmd: AttachCommand) -> Result<()> { .await?; let _ = shutdown_tx.send(()).await; let _ = closed_rx.await; - println!("Secret updated ✓"); + success("Secret updated"); } } diff --git a/src/commands/security_report.rs b/src/commands/security_report.rs index 0f2e201e6f..611c3e4dfc 100644 --- a/src/commands/security_report.rs +++ b/src/commands/security_report.rs @@ -101,7 +101,6 @@ pub async fn run( } } - success("Created security report"); - + success("Generated security report"); Ok(()) } diff --git a/src/commands/server.rs b/src/commands/server.rs index f4a6a45b1f..c1ee9fc06d 100644 --- a/src/commands/server.rs +++ b/src/commands/server.rs @@ -1,4 +1,10 @@ -use crate::{helpers::account::resolve_user, Error, Result}; +use crate::{ + helpers::{ + account::resolve_user, + messages::{fail, success}, + }, + Error, Result, +}; use clap::Subcommand; use sos_net::{ client::{RemoteSync, SyncOptions}, @@ -44,12 +50,13 @@ pub async fn run(cmd: Command) -> Result<()> { let options = SyncOptions { origins: vec![origin.clone()], }; + let sync_error = owner.sync_with_options(&options).await; - if sync_error.is_some() { + if let Some(err) = sync_error { owner.remove_server(&origin).await?; - return Err(Error::InitialSync); + return Err(Error::InitialSync(err)); } else { - println!("Added {} ✓", origin.url()); + success(format!("Added {}", origin.url())); } } Command::List { account } => { @@ -70,10 +77,11 @@ pub async fn run(cmd: Command) -> Result<()> { let mut owner = user.write().await; let origin: Origin = url.into(); let remote = owner.remove_server(&origin).await?; + if remote.is_some() { - println!("Removed {} ✓", origin.url()); + success(format!("Removed {}", origin.url())); } else { - println!("Server {} does not exist", origin.url()); + fail(format!("server {} does not exist", origin.url())); } } } diff --git a/src/commands/shell/repl.rs b/src/commands/shell/repl.rs index 412b1844f7..799f521b9b 100644 --- a/src/commands/shell/repl.rs +++ b/src/commands/shell/repl.rs @@ -8,8 +8,8 @@ use sos_net::sdk::{ use crate::{ commands::{ - AccountCommand, FileCommand, FolderCommand, - SecretCommand, ServerCommand, SyncCommand, + AccountCommand, EnvironmentCommand, FileCommand, FolderCommand, + PreferenceCommand, SecretCommand, ServerCommand, SyncCommand, }, helpers::account::{cd_folder, switch, Owner}, }; @@ -59,6 +59,18 @@ enum ShellCommand { #[clap(subcommand)] cmd: FileCommand, }, + /// View and edit account preferences. + #[clap(alias = "prefs")] + Preferences { + #[clap(subcommand)] + cmd: PreferenceCommand, + }, + /// Print environment and paths. + #[clap(alias = "env")] + Environment { + #[clap(subcommand)] + cmd: EnvironmentCommand, + }, /// Set a folder as the current working directory. Cd { /// Folder name or id. @@ -182,78 +194,15 @@ async fn exec_program(program: Shell, user: Owner) -> Result<()> { } ShellCommand::Sync { cmd } => crate::commands::sync::run(cmd).await, ShellCommand::File { cmd } => crate::commands::file::run(cmd).await, + ShellCommand::Preferences { cmd } => { + crate::commands::preferences::run(cmd).await + } + ShellCommand::Environment { cmd } => { + crate::commands::environment::run(cmd).await + } ShellCommand::Cd { folder } => cd_folder(user, folder.as_ref()).await, /* - ShellCommand::Status { verbose } => { - let owner = user.read().await; - let keeper = - owner.storage.current().ok_or(Error::NoVaultSelected)?; - let summary = keeper.summary().clone(); - drop(owner); - - let mut owner = user.write().await; - let (status, pending_events) = - owner.storage.status(&summary).await?; - if verbose { - let pair = status.pair(); - println!("local = {}", pair.local.root_hex()); - println!("remote = {}", pair.remote.root_hex()); - } - if let Some(pending_events) = pending_events { - println!("{} event(s) have not been saved", pending_events); - } - println!("{}", status); - Ok(()) - } - ShellCommand::Pull { force } => { - let mut owner = user.write().await; - let keeper = - owner.storage.current().ok_or(Error::NoVaultSelected)?; - let summary = keeper.summary().clone(); - let result = owner.storage.pull(&summary, force).await?; - match result.status { - SyncKind::Equal => println!("Up to date"), - SyncKind::Safe => { - if let Some(proof) = result.after { - println!("Pull complete {}", proof.root_hex()); - } - } - SyncKind::Force => { - if let Some(proof) = result.after { - println!("Force pull complete {}", proof.root_hex()); - } - } - SyncKind::Unsafe => { - println!("Cannot pull safely, use the --force option if you are sure."); - } - } - Ok(()) - } - ShellCommand::Push { force } => { - let mut owner = user.write().await; - let keeper = - owner.storage.current().ok_or(Error::NoVaultSelected)?; - let summary = keeper.summary().clone(); - let result = owner.storage.push(&summary, force).await?; - match result.status { - SyncKind::Equal => println!("Up to date"), - SyncKind::Safe => { - if let Some(proof) = result.after { - println!("Push complete {}", proof.root_hex()); - } - } - SyncKind::Force => { - if let Some(proof) = result.after { - println!("Force push complete {}", proof.root_hex()); - } - } - SyncKind::Unsafe => { - println!("Cannot push safely, use the --force option if you are sure."); - } - } - Ok(()) - } ShellCommand::Password => { let mut owner = user.write().await; let keeper = @@ -370,6 +319,11 @@ where /// Execute a line of input in the context of the shell program. pub async fn exec(line: &str, user: Owner) -> Result<()> { + // ignore comments + if line.trim().starts_with("#") { + return Ok(()); + } + if !line.trim().is_empty() { let mut sanitized = shell_words::split(line.trim_end_matches(' '))?; sanitized.insert(0, String::from("sos-shell")); diff --git a/src/commands/sync.rs b/src/commands/sync.rs index c52a17616e..181bab9a1d 100644 --- a/src/commands/sync.rs +++ b/src/commands/sync.rs @@ -1,4 +1,7 @@ -use crate::{helpers::account::resolve_user, Error, Result}; +use crate::{ + helpers::{account::resolve_user, messages::success}, + Error, Result, +}; use clap::Subcommand; use sos_net::{ client::{NetworkAccount, RemoteSync, SyncOptions}, @@ -85,7 +88,7 @@ pub async fn run(cmd: Command) -> Result<()> { } } - println!("Synced ✓"); + success("Synced"); } Command::Status { account, url } => { let user = resolve_user(account.as_ref(), false).await?; @@ -232,7 +235,7 @@ fn print_commit_state( let comparison = local_tree.compare(&remote.1)?; let detail = match &comparison { - Comparison::Equal => String::from("✓"), + Comparison::Equal => String::from("up to date"), Comparison::Contains(_, _) => { let amount = local.1.len() - remote.1.len(); format!("{} commit(s) ahead", amount) diff --git a/src/error.rs b/src/error.rs index 55ed4059e2..7c55aa00de 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,7 @@ -use sos_net::sdk::{vault::secret::SecretRef, vcard4}; +use sos_net::{ + client, + sdk::{sync::SyncError, vault::secret::SecretRef, vcard4}, +}; use std::path::PathBuf; use thiserror::Error; @@ -22,8 +25,8 @@ pub enum Error { #[error(r#"attachment "{0}" not found"#)] FieldNotFound(SecretRef), - #[error(r#"initial sync has errors"#)] - InitialSync, + #[error(r#"initial sync has errors: {0}"#)] + InitialSync(SyncError), #[error(r#"sync failed"#)] SyncFail, @@ -124,6 +127,9 @@ pub enum Error { #[error("unknown report format '{0}'")] UnknownReportFormat(String), + #[error("unknown path filter '{0}'")] + UnknownPathFilter(String), + /// Error generated converting to fixed length slice. #[error(transparent)] TryFromSlice(#[from] std::array::TryFromSliceError), @@ -163,6 +169,9 @@ pub enum Error { #[error(transparent)] ShellWords(#[from] shell_words::ParseError), + #[error(transparent)] + Bool(#[from] std::str::ParseBoolError), + #[error(transparent)] Vcard(#[from] vcard4::Error), @@ -180,6 +189,9 @@ pub enum Error { #[error(transparent)] Ctrlc(#[from] ctrlc::Error), + + #[error(transparent)] + TomlSer(#[from] toml::ser::Error), } impl Error { diff --git a/src/helpers/account.rs b/src/helpers/account.rs index 659c7e2710..92a9d736ce 100644 --- a/src/helpers/account.rs +++ b/src/helpers/account.rs @@ -2,7 +2,7 @@ use std::{borrow::Cow, sync::Arc}; use sos_net::{ - client::NetworkAccount, + client::{is_offline, NetworkAccount}, sdk::{ account::Account, constants::DEFAULT_VAULT_NAME, @@ -10,6 +10,7 @@ use sos_net::{ identity::{AccountRef, Identity, PublicIdentity}, passwd::diceware::generate_passphrase, secrecy::{ExposeSecret, SecretString}, + signer::ecdsa::Address, vault::{FolderRef, Summary}, Paths, }, @@ -112,6 +113,31 @@ pub async fn resolve_account( account.cloned() } +pub async fn resolve_account_address( + account: Option<&AccountRef>, +) -> Result
{ + let account = resolve_account(account) + .await + .ok_or_else(|| Error::NoAccountFound)?; + + let accounts = Identity::list_accounts(None).await?; + for info in accounts { + match account { + AccountRef::Name(ref name) => { + if info.label() == name { + return Ok(info.address().clone()); + } + } + AccountRef::Address(address) => { + if info.address() == &address { + return Ok(info.address().clone()); + } + } + } + } + Err(Error::NoAccountFound) +} + pub async fn resolve_folder( user: &Owner, folder: Option<&FolderRef>, @@ -212,9 +238,12 @@ pub async fn sign_in( .ok_or(Error::NoAccount(account.to_string()))?; let passphrase = read_password(Some("Password: "))?; - let mut owner = - NetworkAccount::new_unauthenticated(account.address().clone(), None) - .await?; + let mut owner = NetworkAccount::new_unauthenticated( + account.address().clone(), + None, + is_offline(), + ) + .await?; let key: AccessKey = passphrase.clone().into(); owner.sign_in(&key).await?; @@ -338,6 +367,7 @@ pub async fn new_account( account_name.clone(), passphrase.clone(), None, + false, |builder| { builder .create_contacts(true) diff --git a/src/helpers/secret.rs b/src/helpers/secret.rs index d514ae8e5e..54f6f49262 100644 --- a/src/helpers/secret.rs +++ b/src/helpers/secret.rs @@ -20,9 +20,12 @@ use sos_net::sdk::{ }; use crate::{ - helpers::readline::{ - read_flag, read_line, read_line_allow_empty, read_multiline, - read_option, read_password, + helpers::{ + messages::success, + readline::{ + read_flag, read_line, read_line_allow_empty, read_multiline, + read_option, read_password, + }, }, Error, Result, TARGET, }; @@ -260,16 +263,7 @@ pub fn add_note( let name = read_name(name)?; multiline_banner("NOTE", &name); - let text = if option_env!("CI").is_some() { - std::env::var("SOS_NOTE").ok() - } else { - if let Some(note) = read_multiline(None)? { - Some(note) - } else { - None - } - }; - + let text = read_multiline(None)?; if let Some(note) = text { let text = note.trim_end_matches('\n').to_string(); let secret: Secret = text.into(); @@ -288,24 +282,14 @@ pub fn add_link( tags: Option, ) -> Result> { let name = read_name(name)?; - - let link = if option_env!("CI").is_some() { - std::env::var("SOS_LINK").ok() - } else { - Some(read_line(Some("URL: "))?) - }; - - if let Some(link) = link { - let url: Url = link.parse().map_err(|_| Error::InvalidUrl)?; - let secret: Secret = url.into(); - let mut secret_meta = SecretMeta::new(name, secret.kind()); - if let Some(tags) = normalize_tags(tags) { - secret_meta.set_tags(tags); - } - Ok(Some((secret_meta, secret))) - } else { - Ok(None) + let link = read_line(Some("URL: "))?; + let url: Url = link.parse().map_err(|_| Error::InvalidUrl)?; + let secret: Secret = url.into(); + let mut secret_meta = SecretMeta::new(name, secret.kind()); + if let Some(tags) = normalize_tags(tags) { + secret_meta.set_tags(tags); } + Ok(Some((secret_meta, secret))) } pub fn add_password( @@ -314,24 +298,13 @@ pub fn add_password( ) -> Result> { let name = read_name(name)?; - let password = if option_env!("CI").is_some() { - std::env::var("SOS_PASSWORD_VALUE") - .ok() - .map(SecretString::new) - } else { - Some(read_password(None)?) - }; - - if let Some(password) = password { - let secret: Secret = password.into(); - let mut secret_meta = SecretMeta::new(name, secret.kind()); - if let Some(tags) = normalize_tags(tags) { - secret_meta.set_tags(tags); - } - Ok(Some((secret_meta, secret))) - } else { - Ok(None) + let password = read_password(None)?; + let secret: Secret = password.into(); + let mut secret_meta = SecretMeta::new(name, secret.kind()); + if let Some(tags) = normalize_tags(tags) { + secret_meta.set_tags(tags); } + Ok(Some((secret_meta, secret))) } /* @@ -371,30 +344,24 @@ pub fn add_list( ) -> Result> { let name = read_name(name)?; - let credentials = if option_env!("CI").is_some() { - let list = std::env::var("SOS_LIST").ok().unwrap_or_default(); - Secret::decode_list(&list)? - } else { - let mut credentials: HashMap = HashMap::new(); - loop { - let mut name = read_line(Some("Key: "))?; - while credentials.get(&name).is_some() { - tracing::error!( - target: TARGET, - "name '{}' already exists", - &name - ); - name = read_line(Some("Key: "))?; - } - let value = read_password(Some("Value: "))?; - credentials.insert(name, value); - let prompt = Some("Add more credentials (y/n)? "); - if !read_flag(prompt)? { - break; - } + let mut credentials: HashMap = HashMap::new(); + loop { + let mut name = read_line(Some("Key: "))?; + while credentials.get(&name).is_some() { + tracing::error!( + target: TARGET, + "name '{}' already exists", + &name + ); + name = read_line(Some("Key: "))?; } - credentials - }; + let value = read_password(Some("Value: "))?; + credentials.insert(name, value); + let prompt = Some("Add more credentials (y/n)? "); + if !read_flag(prompt)? { + break; + } + } if !credentials.is_empty() { let secret: Secret = credentials.into(); @@ -414,20 +381,9 @@ pub fn add_login( ) -> Result> { let name = read_name(name)?; - let (account, url, password) = if option_env!("CI").is_some() { - ( - std::env::var("SOS_LOGIN_USERNAME").ok().unwrap_or_default(), - std::env::var("SOS_LOGIN_URL").ok(), - SecretString::new( - std::env::var("SOS_LOGIN_PASSWORD").ok().unwrap_or_default(), - ), - ) - } else { - let account = read_line(Some("Username: "))?; - let url = read_option(Some("Website: "))?; - let password = read_password(Some("Password: "))?; - (account, url, password) - }; + let account = read_line(Some("Username: "))?; + let url = read_option(Some("Website: "))?; + let password = read_password(Some("Password: "))?; let url: Option = if let Some(url) = url { Some(url.parse().map_err(|_| Error::InvalidUrl)?) @@ -512,7 +468,7 @@ pub(crate) async fn download_file_secret( vfs::write(file, buffer.expose_secret()).await?; } } - println!("Download complete ✓"); + success("Download complete"); Ok(()) } else { Err(Error::NotFileContent) diff --git a/tests/access_control/allow.rs b/tests/access_control/allow.rs index f8c29e7e0e..ca000dd602 100644 --- a/tests/access_control/allow.rs +++ b/tests/access_control/allow.rs @@ -28,6 +28,7 @@ async fn access_control_allow() -> Result<()> { TEST_ID.to_owned(), password.clone(), Some(data_dir.clone()), + false, ) .await?; let denied_address = denied.address().clone(); diff --git a/tests/access_control/deny.rs b/tests/access_control/deny.rs index c596f9ae2c..6f006323f9 100644 --- a/tests/access_control/deny.rs +++ b/tests/access_control/deny.rs @@ -28,6 +28,7 @@ async fn access_control_deny() -> Result<()> { TEST_ID.to_owned(), password.clone(), Some(data_dir.clone()), + false, ) .await?; let denied_address = denied.address().clone(); diff --git a/tests/command_line/main.rs b/tests/command_line/main.rs index c7a2c71099..247011962d 100644 --- a/tests/command_line/main.rs +++ b/tests/command_line/main.rs @@ -2,7 +2,10 @@ mod cli { use anticipate_runner::{InterpreterOptions, ScriptFile}; use anyhow::Result; - use std::{env::{set_var, var}, path::Path}; + use std::{ + env::{set_var, var}, + path::Path, + }; #[test] fn command_line() -> Result<()> { @@ -12,9 +15,16 @@ mod cli { "tests/command_line/scripts/setup.sh", "tests/command_line/scripts/specs/account.sh", "tests/command_line/scripts/specs/check.sh", + "tests/command_line/scripts/specs/device.sh", + "tests/command_line/scripts/specs/environment.sh", + "tests/command_line/scripts/specs/events.sh", "tests/command_line/scripts/specs/folder.sh", "tests/command_line/scripts/specs/secret.sh", + "tests/command_line/scripts/specs/security-report.sh", + "tests/command_line/scripts/specs/server.sh", "tests/command_line/scripts/specs/shell.sh", + "tests/command_line/scripts/specs/preferences.sh", + "tests/command_line/scripts/specs/audit.sh", "tests/command_line/scripts/teardown.sh", ]; @@ -34,7 +44,7 @@ mod cli { .into_owned(); let echo = var("ANTICIPATE_ECHO").is_ok(); let script = ScriptFile::parse(input_file)?; - let mut options = InterpreterOptions::new(5000, echo, false, false); + let mut options = InterpreterOptions::new(15000, echo, false, false); options.id = Some(file_name.to_owned()); script.run(options)?; Ok(()) @@ -49,9 +59,14 @@ mod cli { }; set_var("PATH", format!("{}:{}", prefix, path)); + set_var("BASH_SILENCE_DEPRECATION_WARNING", "1"); set_var("NO_COLOR", "1"); + set_var("SOS_DATA_DIR", "target/accounts"); set_var("SOS_PROMPT", "➜ "); + set_var("SOS_OFFLINE", "1"); + set_var("DEMO_SERVER", "https://demo.saveoursecrets.com"); + set_var("ACCOUNT_NAME", "Demo"); set_var("ACCOUNT_NAME_ALT", "Demo Account"); set_var("ACCOUNT_PASSWORD", "demo-test-password-case"); diff --git a/tests/command_line/scripts/audit/logs.sh b/tests/command_line/scripts/audit/logs.sh new file mode 100644 index 0000000000..70896f480b --- /dev/null +++ b/tests/command_line/scripts/audit/logs.sh @@ -0,0 +1,11 @@ +# print the first 5 audit log events +sos audit logs -c 5 $(sos env paths -f audit) +#$ wait + +# print the last 5 audit log events +sos audit logs -r -c 5 $(sos env paths -f audit) +#$ wait + +# print the events as JSON +sos audit logs -c 5 -j $(sos env paths -f audit) +#$ wait diff --git a/tests/command_line/scripts/check/events.sh b/tests/command_line/scripts/check/events.sh index 969f5cb6d1..f988b8222d 100644 --- a/tests/command_line/scripts/check/events.sh +++ b/tests/command_line/scripts/check/events.sh @@ -1,2 +1,3 @@ -sos check events $(find target/accounts/identity -name '*.events') +# verify event log integrity +sos check events $(sos env paths -f identity-events) #$ wait diff --git a/tests/command_line/scripts/check/header.sh b/tests/command_line/scripts/check/header.sh index 988d9a7ab7..dd8d8bb382 100644 --- a/tests/command_line/scripts/check/header.sh +++ b/tests/command_line/scripts/check/header.sh @@ -1,2 +1,3 @@ -sos check header $(find target/accounts/identity -name '*.vault') +# print vault header information +sos check header $(sos env paths -f identity-vault) #$ wait diff --git a/tests/command_line/scripts/check/keys.sh b/tests/command_line/scripts/check/keys.sh index fe692b48b3..0dc78441a8 100644 --- a/tests/command_line/scripts/check/keys.sh +++ b/tests/command_line/scripts/check/keys.sh @@ -1,2 +1,3 @@ -sos check keys $(find target/accounts/identity -name '*.vault') +# print keys in a vault +sos check keys $(sos env paths -f identity-vault) #$ wait diff --git a/tests/command_line/scripts/check/vault.sh b/tests/command_line/scripts/check/vault.sh index db0ae5383b..0976a3204a 100644 --- a/tests/command_line/scripts/check/vault.sh +++ b/tests/command_line/scripts/check/vault.sh @@ -1,2 +1,3 @@ -sos check vault $(find target/accounts/identity -name '*.vault') +# verify vault integrity +sos check vault $(sos env paths -f identity-vault) #$ wait diff --git a/tests/command_line/scripts/demos/accounts-basic.sh b/tests/command_line/scripts/demos/accounts-basic.sh deleted file mode 100644 index 97dad92b0a..0000000000 --- a/tests/command_line/scripts/demos/accounts-basic.sh +++ /dev/null @@ -1,20 +0,0 @@ -# create an account - -#$ include ../setup/account.sh -#$ include ../includes/screen.sh - -# list accounts -#$ include ../account/list.sh - -# create a backup archive -#$ include ../account/backup.sh - -# now we will always need to sign in - -# account info shows the folder list -#$ include ../account/info.sh - -#$ include ../includes/screen.sh - -# show account statistics -#$ include ../account/stats.sh diff --git a/tests/command_line/scripts/demos/audit-logs.sh b/tests/command_line/scripts/demos/audit-logs.sh new file mode 100644 index 0000000000..3f19d9277f --- /dev/null +++ b/tests/command_line/scripts/demos/audit-logs.sh @@ -0,0 +1 @@ +#$ include ../audit/logs.sh diff --git a/tests/command_line/scripts/demos/check.sh b/tests/command_line/scripts/demos/check.sh new file mode 100644 index 0000000000..88ee3e8afc --- /dev/null +++ b/tests/command_line/scripts/demos/check.sh @@ -0,0 +1,4 @@ +#$ include ../check/vault.sh +#$ include ../check/keys.sh +#$ include ../check/header.sh +#$ include ../check/events.sh diff --git a/tests/command_line/scripts/demos/device-list.sh b/tests/command_line/scripts/demos/device-list.sh new file mode 100644 index 0000000000..acc2efdde0 --- /dev/null +++ b/tests/command_line/scripts/demos/device-list.sh @@ -0,0 +1 @@ +#$ include ../device/list.sh diff --git a/tests/command_line/scripts/demos/environment.sh b/tests/command_line/scripts/demos/environment.sh new file mode 100644 index 0000000000..8414bd959b --- /dev/null +++ b/tests/command_line/scripts/demos/environment.sh @@ -0,0 +1,11 @@ +#$ include ../environment/vars.sh +#$ include ../includes/screen.sh + +#$ include ../environment/paths.sh +#$ include ../includes/screen.sh + +#$ include ../environment/print-path-filters.sh +#$ include ../includes/screen.sh + +#$ include ../environment/paths-filter.sh +#$ include ../includes/screen.sh diff --git a/tests/command_line/scripts/demos/events.sh b/tests/command_line/scripts/demos/events.sh new file mode 100644 index 0000000000..d7b71a1310 --- /dev/null +++ b/tests/command_line/scripts/demos/events.sh @@ -0,0 +1,11 @@ +#$ include ../events/account.sh +#$ include ../includes/screen.sh + +#$ include ../events/device.sh +#$ include ../includes/screen.sh + +#$ include ../events/folder.sh +#$ include ../includes/screen.sh + +#$ include ../events/file.sh +#$ include ../includes/screen.sh diff --git a/tests/command_line/scripts/demos/folder-basic.sh b/tests/command_line/scripts/demos/folder-basic.sh new file mode 100644 index 0000000000..e07d59cadf --- /dev/null +++ b/tests/command_line/scripts/demos/folder-basic.sh @@ -0,0 +1,31 @@ +# create a new folder +#$ include ../folder/new.sh + +# list folders +#$ include ../folder/list.sh + +# get information about a folder +#$ include ../folder/info.sh + +# add a secret to the folder +sos secret add login -f "$FOLDER_NAME" --name "$LOGIN_NAME" +#$ include ../includes/signin.sh +#$ expect Username: +$LOGIN_SERVICE_NAME +#$ expect Website: +$LOGIN_URL +#$ expect Password: +$LOGIN_PASSWORD +#$ regex (?i)created +#$ wait + +#$ include ../includes/screen.sh + +# print secret identifiers +#$ include ../folder/keys.sh + +# inspect folder commit hashes +#$ include ../folder/commits.sh + +# remove a folder +#$ include ../folder/remove.sh diff --git a/tests/command_line/scripts/demos/help.sh b/tests/command_line/scripts/demos/help.sh index c246f2f5bc..414e42dee6 100644 --- a/tests/command_line/scripts/demos/help.sh +++ b/tests/command_line/scripts/demos/help.sh @@ -1,2 +1,2 @@ sos --help -#$ expect Print version +#$ wait diff --git a/tests/command_line/scripts/demos/preferences.sh b/tests/command_line/scripts/demos/preferences.sh new file mode 100644 index 0000000000..8b18cddcf5 --- /dev/null +++ b/tests/command_line/scripts/demos/preferences.sh @@ -0,0 +1,7 @@ +#$ include ../preferences/set-bool.sh +#$ include ../preferences/set-number.sh +#$ include ../preferences/set-string.sh +#$ include ../preferences/set-string-list.sh +#$ include ../preferences/list.sh +#$ include ../preferences/remove.sh +#$ include ../preferences/clear.sh diff --git a/tests/command_line/scripts/demos/secrets-basic.sh b/tests/command_line/scripts/demos/secret-basic.sh similarity index 71% rename from tests/command_line/scripts/demos/secrets-basic.sh rename to tests/command_line/scripts/demos/secret-basic.sh index 067bd94a40..75ad3bd223 100644 --- a/tests/command_line/scripts/demos/secrets-basic.sh +++ b/tests/command_line/scripts/demos/secret-basic.sh @@ -3,7 +3,9 @@ #$ include ../includes/screen.sh # now we can list the secrets in the folder -#$ include ../secret/list.sh +sos secret ls +#$ include ../includes/signin.sh +#$ wait # and view the secret #$ include ../secret/get-note.sh @@ -16,6 +18,8 @@ #$ include ../includes/screen.sh # rename a secret -#$ include ../secret/rename.sh +sos secret rename --name "Demo Note" "$NOTE_NAME" +#$ include ../includes/signin.sh +#$ wait #$ include ../includes/screen.sh diff --git a/tests/command_line/scripts/demos/security-report.sh b/tests/command_line/scripts/demos/security-report.sh new file mode 100644 index 0000000000..f3e711cc34 --- /dev/null +++ b/tests/command_line/scripts/demos/security-report.sh @@ -0,0 +1,20 @@ +# generate a CSV security report +sos security-report --force target/report.csv +#$ include ../includes/signin.sh +#$ regex (?i)generated +#$ wait + +# or JSON if you prefer +sos security-report --format json --force target/report.json +#$ include ../includes/signin.sh +#$ regex (?i)generated +#$ wait + +# to include entries that passed the threshold +sos security-report \ + --include-all \ + --format json \ + --force target/report.json +#$ include ../includes/signin.sh +#$ regex (?i)generated +#$ wait diff --git a/tests/command_line/scripts/demos/server-basic.sh b/tests/command_line/scripts/demos/server-basic.sh new file mode 100644 index 0000000000..b0704b3808 --- /dev/null +++ b/tests/command_line/scripts/demos/server-basic.sh @@ -0,0 +1,15 @@ +# add a server this account will sync to +sos server add $DEMO_SERVER +#$ include ../includes/signin.sh +#$ regex (?i)added + +# list servers +sos server ls +#$ include ../includes/signin.sh +#$ wait + +# remove a server +sos server rm $DEMO_SERVER +#$ include ../includes/signin.sh +#$ regex (?i)removed +#$ wait diff --git a/tests/command_line/scripts/demos/server.sh b/tests/command_line/scripts/demos/server.sh index 6654060d06..473b76a15a 100644 --- a/tests/command_line/scripts/demos/server.sh +++ b/tests/command_line/scripts/demos/server.sh @@ -11,6 +11,6 @@ cat target/config.toml # start the server sos-server start target/config.toml -#$ regex tls +#$ regex (?i)tls #$ sendcontrol ^C diff --git a/tests/command_line/scripts/demos/setup/account-basic.sh b/tests/command_line/scripts/demos/setup/account-basic.sh new file mode 100644 index 0000000000..b8698bbb1b --- /dev/null +++ b/tests/command_line/scripts/demos/setup/account-basic.sh @@ -0,0 +1,21 @@ +# create an account + +#$ include ../../setup.sh +#$ include ../../includes/screen.sh + +# list accounts +sos account ls +#$ wait + +# create a backup archive +#$ include ../../account/backup.sh + +# now we will always need to sign in + +# account info shows the folder list +#$ include ../../account/info.sh + +#$ include ../../includes/screen.sh + +# show account statistics +#$ include ../../account/stats.sh diff --git a/tests/command_line/scripts/demos/shell-basic.sh b/tests/command_line/scripts/demos/shell-basic.sh new file mode 100644 index 0000000000..04e014a978 --- /dev/null +++ b/tests/command_line/scripts/demos/shell-basic.sh @@ -0,0 +1,28 @@ +sos shell +#$ include ../includes/signin.sh +#$ wait + +# type "help" to see the shell commands +help + +#$ include ../shell/basic.sh +#$ include ../shell/account.sh + +# import passwords from a Safari/MacOS CSV file +account migrate import --name "MacOS Passwords" --format macos.csv $MIGRATE_MACOS +#$ regex (?i)imported +#$ wait + +# export unencrypted secrets to migrate to another app +account migrate export --force target/demo-export.zip +#$ regex (?i)export unencrypted +y +#$ wait + +#$ include ../shell/contacts.sh +#$ include ../shell/folder.sh +#$ include ../shell/secret.sh + +# exit the shell session +quit +#$ wait diff --git a/tests/command_line/scripts/device/list.sh b/tests/command_line/scripts/device/list.sh new file mode 100644 index 0000000000..33279647e3 --- /dev/null +++ b/tests/command_line/scripts/device/list.sh @@ -0,0 +1,9 @@ +# list device public keys +sos device ls +#$ include ../includes/signin.sh +#$ wait + +# print trusted devices as JSON +sos device ls -v +#$ include ../includes/signin.sh +#$ wait diff --git a/tests/command_line/scripts/environment/paths-filter.sh b/tests/command_line/scripts/environment/paths-filter.sh new file mode 100644 index 0000000000..a70b0ee1f6 --- /dev/null +++ b/tests/command_line/scripts/environment/paths-filter.sh @@ -0,0 +1,3 @@ +# print a particular path using a filter +sos env paths -f account-events +#$ wait diff --git a/tests/command_line/scripts/environment/paths.sh b/tests/command_line/scripts/environment/paths.sh new file mode 100644 index 0000000000..fb89fa093c --- /dev/null +++ b/tests/command_line/scripts/environment/paths.sh @@ -0,0 +1,3 @@ +# print paths for an account +sos env paths +#$ wait diff --git a/tests/command_line/scripts/environment/print-path-filters.sh b/tests/command_line/scripts/environment/print-path-filters.sh new file mode 100644 index 0000000000..5aa46d4a1f --- /dev/null +++ b/tests/command_line/scripts/environment/print-path-filters.sh @@ -0,0 +1,3 @@ +# print list of filters for the paths subcommand +sos env print-path-filters +#$ wait diff --git a/tests/command_line/scripts/environment/vars.sh b/tests/command_line/scripts/environment/vars.sh new file mode 100644 index 0000000000..26f2609d3a --- /dev/null +++ b/tests/command_line/scripts/environment/vars.sh @@ -0,0 +1,3 @@ +# print environment variables +sos env vars +#$ wait diff --git a/tests/command_line/scripts/events/account.sh b/tests/command_line/scripts/events/account.sh new file mode 100644 index 0000000000..82e97169c3 --- /dev/null +++ b/tests/command_line/scripts/events/account.sh @@ -0,0 +1,3 @@ +# print account event log records +sos events account "$(sos env paths -f account-events)" +#$ wait diff --git a/tests/command_line/scripts/events/device.sh b/tests/command_line/scripts/events/device.sh new file mode 100644 index 0000000000..bf26b8cf0e --- /dev/null +++ b/tests/command_line/scripts/events/device.sh @@ -0,0 +1,3 @@ +# print device event log records +sos events device "$(sos env paths -f device-events)" +#$ wait diff --git a/tests/command_line/scripts/events/file.sh b/tests/command_line/scripts/events/file.sh new file mode 100644 index 0000000000..8da3512311 --- /dev/null +++ b/tests/command_line/scripts/events/file.sh @@ -0,0 +1,3 @@ +# print file event log records +sos events file "$(sos env paths -f file-events)" +#$ wait diff --git a/tests/command_line/scripts/events/folder.sh b/tests/command_line/scripts/events/folder.sh new file mode 100644 index 0000000000..243fa086c9 --- /dev/null +++ b/tests/command_line/scripts/events/folder.sh @@ -0,0 +1,3 @@ +# print folder event log records +sos events folder "$(sos env paths -f identity-events)" +#$ wait diff --git a/tests/command_line/scripts/folder/commits.sh b/tests/command_line/scripts/folder/commits.sh index 9c30e32f83..fb5e510db6 100644 --- a/tests/command_line/scripts/folder/commits.sh +++ b/tests/command_line/scripts/folder/commits.sh @@ -1,3 +1,3 @@ -sos folder commits -f $FOLDER_NAME +sos folder commits -f "$FOLDER_NAME" #$ include ../includes/signin.sh #$ wait diff --git a/tests/command_line/scripts/folder/history/check.sh b/tests/command_line/scripts/folder/history/check.sh index a6c5dded9c..ba2c317b6d 100644 --- a/tests/command_line/scripts/folder/history/check.sh +++ b/tests/command_line/scripts/folder/history/check.sh @@ -1,3 +1,3 @@ -sos folder history check -f $FOLDER_NAME +sos folder history check -f "$FOLDER_NAME" #$ include ../../includes/signin.sh #$ wait diff --git a/tests/command_line/scripts/folder/history/compact.sh b/tests/command_line/scripts/folder/history/compact.sh index 658938ef00..b70c5274f7 100644 --- a/tests/command_line/scripts/folder/history/compact.sh +++ b/tests/command_line/scripts/folder/history/compact.sh @@ -1,4 +1,4 @@ -sos folder history compact -f $FOLDER_NAME +sos folder history compact -f "$FOLDER_NAME" #$ include ../../includes/signin.sh #$ regex (?i)remove history y diff --git a/tests/command_line/scripts/folder/history/list.sh b/tests/command_line/scripts/folder/history/list.sh index 08e988da1d..a288a4f766 100644 --- a/tests/command_line/scripts/folder/history/list.sh +++ b/tests/command_line/scripts/folder/history/list.sh @@ -1,3 +1,3 @@ -sos folder history list -f $FOLDER_NAME +sos folder history list -f "$FOLDER_NAME" #$ include ../../includes/signin.sh #$ wait diff --git a/tests/command_line/scripts/folder/info.sh b/tests/command_line/scripts/folder/info.sh index 3176501db6..25f2461263 100644 --- a/tests/command_line/scripts/folder/info.sh +++ b/tests/command_line/scripts/folder/info.sh @@ -1,3 +1,3 @@ -sos folder info -v +sos folder info -v -f "$FOLDER_NAME" #$ include ../includes/signin.sh #$ wait diff --git a/tests/command_line/scripts/folder/keys.sh b/tests/command_line/scripts/folder/keys.sh index f050af44fc..8c7230b865 100644 --- a/tests/command_line/scripts/folder/keys.sh +++ b/tests/command_line/scripts/folder/keys.sh @@ -1,3 +1,3 @@ -sos folder keys -f $FOLDER_NAME +sos folder keys -f "$FOLDER_NAME" #$ include ../includes/signin.sh #$ wait diff --git a/tests/command_line/scripts/folder/new.sh b/tests/command_line/scripts/folder/new.sh index 2f9f520884..aa84f3dca3 100644 --- a/tests/command_line/scripts/folder/new.sh +++ b/tests/command_line/scripts/folder/new.sh @@ -1,4 +1,4 @@ -sos folder new $FOLDER_NAME +sos folder new "$FOLDER_NAME" #$ include ../includes/signin.sh #$ regex (?i)created #$ wait diff --git a/tests/command_line/scripts/folder/remove.sh b/tests/command_line/scripts/folder/remove.sh index 6ccef8f8d9..cdbd73d7c5 100644 --- a/tests/command_line/scripts/folder/remove.sh +++ b/tests/command_line/scripts/folder/remove.sh @@ -1,4 +1,4 @@ -sos folder remove -f $FOLDER_NAME +sos folder remove -f "$FOLDER_NAME" #$ include ../includes/signin.sh #$ regex (?i)delete folder y diff --git a/tests/command_line/scripts/folder/rename.sh b/tests/command_line/scripts/folder/rename.sh index 20a223dd9a..987990f9db 100644 --- a/tests/command_line/scripts/folder/rename.sh +++ b/tests/command_line/scripts/folder/rename.sh @@ -1,7 +1,7 @@ -sos folder rename -f $FOLDER_NAME $NEW_FOLDER_NAME +sos folder rename -f "$FOLDER_NAME" "$NEW_FOLDER_NAME" #$ include ../includes/signin.sh #$ wait -sos folder rename -f $NEW_FOLDER_NAME $FOLDER_NAME +sos folder rename -f "$NEW_FOLDER_NAME" "$FOLDER_NAME" #$ include ../includes/signin.sh #$ wait diff --git a/tests/command_line/scripts/preferences/clear.sh b/tests/command_line/scripts/preferences/clear.sh new file mode 100644 index 0000000000..e59577c698 --- /dev/null +++ b/tests/command_line/scripts/preferences/clear.sh @@ -0,0 +1,5 @@ +# remove all preferences +sos prefs clear +#$ include ../includes/signin.sh +#$ regex (?i)cleared +#$ wait diff --git a/tests/command_line/scripts/preferences/list.sh b/tests/command_line/scripts/preferences/list.sh new file mode 100644 index 0000000000..471c5ca0be --- /dev/null +++ b/tests/command_line/scripts/preferences/list.sh @@ -0,0 +1,4 @@ +# list preferences +sos prefs ls +#$ include ../includes/signin.sh +#$ wait diff --git a/tests/command_line/scripts/preferences/remove.sh b/tests/command_line/scripts/preferences/remove.sh new file mode 100644 index 0000000000..69429bc90a --- /dev/null +++ b/tests/command_line/scripts/preferences/remove.sh @@ -0,0 +1,5 @@ +# remove a preference +sos prefs rm hashcheck.enabled +#$ include ../includes/signin.sh +#$ regex (?i)removed +#$ wait diff --git a/tests/command_line/scripts/preferences/set-bool.sh b/tests/command_line/scripts/preferences/set-bool.sh new file mode 100644 index 0000000000..a1ef7b8b06 --- /dev/null +++ b/tests/command_line/scripts/preferences/set-bool.sh @@ -0,0 +1,5 @@ +# disable hashcheck service +sos prefs bool hashcheck.enabled false +#$ include ../includes/signin.sh +#$ regex (?i)set +#$ wait diff --git a/tests/command_line/scripts/preferences/set-number.sh b/tests/command_line/scripts/preferences/set-number.sh new file mode 100644 index 0000000000..a4da3f3765 --- /dev/null +++ b/tests/command_line/scripts/preferences/set-number.sh @@ -0,0 +1,5 @@ +# set the autolock timeout minutes +sos prefs number autolock.timeout 30 +#$ include ../includes/signin.sh +#$ regex (?i)set +#$ wait diff --git a/tests/command_line/scripts/preferences/set-string-list.sh b/tests/command_line/scripts/preferences/set-string-list.sh new file mode 100644 index 0000000000..581d454b42 --- /dev/null +++ b/tests/command_line/scripts/preferences/set-string-list.sh @@ -0,0 +1,5 @@ +# disable some menu shortcut types +sos prefs string-list menu.shortcutTypeItems certificate card +#$ include ../includes/signin.sh +#$ regex (?i)set +#$ wait diff --git a/tests/command_line/scripts/preferences/set-string.sh b/tests/command_line/scripts/preferences/set-string.sh new file mode 100644 index 0000000000..698cbe4587 --- /dev/null +++ b/tests/command_line/scripts/preferences/set-string.sh @@ -0,0 +1,5 @@ +# set the hashcheck server URL +sos prefs string hashcheck.server https://hashcheck.saveoursecrets.com +#$ include ../includes/signin.sh +#$ regex (?i)set +#$ wait diff --git a/tests/command_line/scripts/server/add.sh b/tests/command_line/scripts/server/add.sh new file mode 100644 index 0000000000..99b4866ec9 --- /dev/null +++ b/tests/command_line/scripts/server/add.sh @@ -0,0 +1,4 @@ +sos server add $DEMO_SERVER +#$ include ../includes/signin.sh +#$ regex (?i)added +#$ wait diff --git a/tests/command_line/scripts/server/list.sh b/tests/command_line/scripts/server/list.sh new file mode 100644 index 0000000000..e16340bfaf --- /dev/null +++ b/tests/command_line/scripts/server/list.sh @@ -0,0 +1,3 @@ +sos server ls +#$ include ../includes/signin.sh +#$ wait diff --git a/tests/command_line/scripts/server/remove.sh b/tests/command_line/scripts/server/remove.sh new file mode 100644 index 0000000000..3e7234be48 --- /dev/null +++ b/tests/command_line/scripts/server/remove.sh @@ -0,0 +1,4 @@ +sos server rm $DEMO_SERVER +#$ include ../includes/signin.sh +#$ regex (?i)removed +#$ wait diff --git a/tests/command_line/scripts/shell/account.sh b/tests/command_line/scripts/shell/account.sh new file mode 100644 index 0000000000..a6465bd038 --- /dev/null +++ b/tests/command_line/scripts/shell/account.sh @@ -0,0 +1,34 @@ +# list accounts +account ls -v +#$ wait + +# backup current account +account backup --force $ACCOUNT_BACKUP +#$ regex (?i)archive created +#$ wait + +# restore from backup archive +account restore $ACCOUNT_BACKUP +#$ regex Overwrite all account +y +#$ include ../includes/signin.sh +#$ wait + +# account information +account info -v +#$ wait + +# account statistics +account stats +#$ wait + +account stats --json +#$ wait + +# rename the account +account rename -a $ACCOUNT_NAME NewDemo +#$ wait + +account rename -a NewDemo $ACCOUNT_NAME +#$ wait + diff --git a/tests/command_line/scripts/shell/basic.sh b/tests/command_line/scripts/shell/basic.sh new file mode 100644 index 0000000000..594a0969ca --- /dev/null +++ b/tests/command_line/scripts/shell/basic.sh @@ -0,0 +1,11 @@ +# show current account +whoami +# print current folder +#$ wait +pwd +# change folder +#$ wait +cd Archive +#$ wait +cd Documents +#$ wait diff --git a/tests/command_line/scripts/shell/contacts.sh b/tests/command_line/scripts/shell/contacts.sh new file mode 100644 index 0000000000..0886e0d595 --- /dev/null +++ b/tests/command_line/scripts/shell/contacts.sh @@ -0,0 +1,8 @@ +account contacts export --force $CONTACTS_EXPORT +#$ regex (?i)contacts exported +#$ wait + +account contacts import $ACCOUNT_CONTACTS +#$ regex (?i)contacts imported +#$ wait + diff --git a/tests/command_line/scripts/shell/folder.sh b/tests/command_line/scripts/shell/folder.sh new file mode 100644 index 0000000000..fc531b2e4b --- /dev/null +++ b/tests/command_line/scripts/shell/folder.sh @@ -0,0 +1,49 @@ +# create a new folder +folder new "$FOLDER_NAME" +#$ regex (?i)created +#$ wait + +# list folders +folder ls -v +#$ wait + +# print folder information +folder info -v -f "$FOLDER_NAME" +#$ wait + +# print secret keys +folder keys -f "$FOLDER_NAME" +#$ wait + +# print event commit hashes +folder commits -f "$FOLDER_NAME" +#$ wait + +# rename a folder +folder rename -f "$FOLDER_NAME" "$NEW_FOLDER_NAME" +#$ wait + +folder rename -f "$NEW_FOLDER_NAME" "$FOLDER_NAME" +#$ wait + +# compact the event log history +folder history compact -f "$FOLDER_NAME" +#$ regex (?i)remove history +y +#$ wait + +# check the event log history integrity +folder history check -f "$FOLDER_NAME" +#$ wait + +# list event log history +folder history list -f "$FOLDER_NAME" +#$ wait + +# remove a folder +folder remove -f "$FOLDER_NAME" +#$ regex (?i)delete folder +y +#$ regex (?i)folder deleted +#$ wait + diff --git a/tests/command_line/scripts/shell/migrate.sh b/tests/command_line/scripts/shell/migrate.sh new file mode 100644 index 0000000000..32a22aa1c2 --- /dev/null +++ b/tests/command_line/scripts/shell/migrate.sh @@ -0,0 +1,25 @@ +account migrate export --force target/demo-export.zip +#$ regex (?i)export unencrypted +y +#$ wait + +account migrate import --format onepassword.csv $MIGRATE_1PASSWORD +#$ regex (?i)imported +#$ wait + +account migrate import --format dashlane.zip $MIGRATE_DASHLANE +#$ regex (?i)imported +#$ wait + +account migrate import --format bitwarden.csv $MIGRATE_BITWARDEN +#$ regex (?i)imported +#$ wait + +account migrate import --format firefox.csv $MIGRATE_FIREFOX +#$ regex (?i)imported +#$ wait + +account migrate import --format macos.csv $MIGRATE_MACOS +#$ regex (?i)imported +#$ wait + diff --git a/tests/command_line/scripts/shell/secret.sh b/tests/command_line/scripts/shell/secret.sh new file mode 100644 index 0000000000..1085342d1c --- /dev/null +++ b/tests/command_line/scripts/shell/secret.sh @@ -0,0 +1,181 @@ +# create a note +secret add note +#$ expect Name: +$NOTE_NAME +#$ expect >> +Text for the secret note. +#$ expect >> +#$ sendcontrol ^D +#$ wait + +# create a file +secret add file -n "$FILE_NAME" "$FILE_INPUT" +#$ regex (?i)created +#$ wait + +# create a login +secret add login --name "$LOGIN_NAME" +#$ expect Username: +$LOGIN_SERVICE_NAME +#$ expect Website: +$LOGIN_URL +#$ expect Password: +$LOGIN_PASSWORD +#$ regex (?i)created +#$ wait + +# create a list +secret add list --name "$LIST_NAME" +#$ expect Key: +$LIST_KEY_1 +#$ expect Value: +$LIST_VALUE_1 +#$ expect Add more +y +#$ expect Key: +$LIST_KEY_2 +#$ expect Value: +$LIST_VALUE_2 +#$ expect Add more +n +#$ regex (?i)created +#$ wait + +# list secrets +secret ls -f Documents +#$ wait + +# view secrets +secret get "$NOTE_NAME" +#$ wait + +secret get "$FILE_NAME" +#$ wait + +secret get "$LOGIN_NAME" +#$ wait + +secret get "$LIST_NAME" +#$ wait + +# copy to clipboard +secret copy "$NOTE_NAME" +#$ wait + +# print secret information +secret info "$NOTE_NAME" +#$ wait + +secret info --json "$NOTE_NAME" +#$ wait + +# decrypt and download a file secret +secret download --force "$FILE_NAME" "$FILE_OUTPUT" +#$ regex (?i)download complete +#$ wait + +# add attachments to a secret +secret attach add file --name "$FILE_ATTACHMENT" --path "$ATTACH_INPUT" "$NOTE_NAME" +#$ regex (?i)updated +#$ wait + +secret attach add note --name "$NOTE_ATTACHMENT" "$NOTE_NAME" +#$ expect >> +Text for the note attachment. +#$ expect >> +#$ sendcontrol ^D +#$ regex (?i)updated +#$ wait + +secret attach add link --name "$LINK_ATTACHMENT" "$NOTE_NAME" +#$ expect URL: +$LINK_VALUE +#$ regex (?i)updated +#$ wait + +secret attach add password --name "$PASSWORD_ATTACHMENT" "$NOTE_NAME" +#$ expect Password: +$PASSWORD_ATTACHMENT +#$ regex (?i)updated +#$ wait + +# read an attachment +secret attach get "$NOTE_NAME" "$NOTE_ATTACHMENT" +#$ wait + +# list attachments +secret attach ls -v "$NOTE_NAME" +#$ wait + +# remove an attachment +secret attach rm "$NOTE_NAME" "$NOTE_ATTACHMENT" +#$ wait + +# move a secret between folders +folder new "$FOLDER_NAME" +#$ regex (?i)created +#$ wait + +secret move --target "$FOLDER_NAME" "$NOTE_NAME" +#$ regex (?i)moved +#$ wait + +secret move --target "$DEFAULT_FOLDER_NAME" -f "$FOLDER_NAME" "$NOTE_NAME" +#$ regex (?i)moved +#$ wait + +folder remove -f "$FOLDER_NAME" +#$ regex (?i)delete folder +y +#$ regex (?i)folder deleted +#$ wait + +# create, list and remove tags +secret tags add --tags foo,bar,qux "$NOTE_NAME" +#$ wait + +secret tags list "$NOTE_NAME" +#$ wait + +secret tags rm --tags foo,bar "$NOTE_NAME" +#$ wait + +secret tags clear "$NOTE_NAME" +#$ wait + +# toggle favorite for a secret +secret favorite "$NOTE_NAME" +#$ wait + +secret favorite "$NOTE_NAME" +#$ wait + +# move a secret to and from the archive +secret archive "$NOTE_NAME" +#$ regex (?i)moved to archive +#$ wait + +secret unarchive "$NOTE_NAME" +#$ regex (?i)restored from archive +#$ wait + +# modify secret comments +secret comment --text 'Mock comment' "$NOTE_NAME" +#$ wait + +secret comment --text '' "$NOTE_NAME" +#$ wait + +# rename a secret +secret rename --name "Demo Note" "$NOTE_NAME" +#$ wait + +secret rename --name "$NOTE_NAME" "Demo Note" +#$ wait + +# delete a secret +secret remove "$NOTE_NAME" +#$ regex (?i)delete secret +y +#$ regex (?i)secret deleted +#$ wait diff --git a/tests/command_line/scripts/shell/switch.sh b/tests/command_line/scripts/shell/switch.sh new file mode 100644 index 0000000000..f321e5a08d --- /dev/null +++ b/tests/command_line/scripts/shell/switch.sh @@ -0,0 +1,18 @@ +account new "$ACCOUNT_NAME_ALT" +#$ expect Choose a password +2 +#$ regex (?i)password +$ACCOUNT_PASSWORD +#$ regex (?i)password +$ACCOUNT_PASSWORD +#$ regex create a new account +y +#$ wait + +switch "$ACCOUNT_NAME_ALT" +#$ include ../includes/signin.sh +#$ wait + +switch "$ACCOUNT_NAME" +#$ include ../includes/signin.sh +#$ wait diff --git a/tests/command_line/scripts/specs/audit.sh b/tests/command_line/scripts/specs/audit.sh new file mode 100644 index 0000000000..3f19d9277f --- /dev/null +++ b/tests/command_line/scripts/specs/audit.sh @@ -0,0 +1 @@ +#$ include ../audit/logs.sh diff --git a/tests/command_line/scripts/specs/device.sh b/tests/command_line/scripts/specs/device.sh new file mode 100644 index 0000000000..acc2efdde0 --- /dev/null +++ b/tests/command_line/scripts/specs/device.sh @@ -0,0 +1 @@ +#$ include ../device/list.sh diff --git a/tests/command_line/scripts/specs/environment.sh b/tests/command_line/scripts/specs/environment.sh new file mode 100644 index 0000000000..d57df9db83 --- /dev/null +++ b/tests/command_line/scripts/specs/environment.sh @@ -0,0 +1,4 @@ +#$ include ../environment/vars.sh +#$ include ../environment/paths.sh +#$ include ../environment/print-path-filters.sh +#$ include ../environment/paths-filter.sh diff --git a/tests/command_line/scripts/specs/events.sh b/tests/command_line/scripts/specs/events.sh new file mode 100644 index 0000000000..7aeaf1dd14 --- /dev/null +++ b/tests/command_line/scripts/specs/events.sh @@ -0,0 +1,4 @@ +#$ include ../events/account.sh +#$ include ../events/device.sh +#$ include ../events/folder.sh +#$ include ../events/file.sh diff --git a/tests/command_line/scripts/specs/preferences.sh b/tests/command_line/scripts/specs/preferences.sh new file mode 100644 index 0000000000..8b18cddcf5 --- /dev/null +++ b/tests/command_line/scripts/specs/preferences.sh @@ -0,0 +1,7 @@ +#$ include ../preferences/set-bool.sh +#$ include ../preferences/set-number.sh +#$ include ../preferences/set-string.sh +#$ include ../preferences/set-string-list.sh +#$ include ../preferences/list.sh +#$ include ../preferences/remove.sh +#$ include ../preferences/clear.sh diff --git a/tests/command_line/scripts/specs/security-report.sh b/tests/command_line/scripts/specs/security-report.sh new file mode 100644 index 0000000000..567ad01ec6 --- /dev/null +++ b/tests/command_line/scripts/specs/security-report.sh @@ -0,0 +1,4 @@ +sos security-report --force target/report.csv +#$ include ../includes/signin.sh +#$ regex (?i)generated +#$ wait diff --git a/tests/command_line/scripts/specs/server.sh b/tests/command_line/scripts/specs/server.sh new file mode 100644 index 0000000000..0c66d66990 --- /dev/null +++ b/tests/command_line/scripts/specs/server.sh @@ -0,0 +1,3 @@ +#$ include ../server/add.sh +#$ include ../server/list.sh +#$ include ../server/remove.sh diff --git a/tests/command_line/scripts/specs/shell.sh b/tests/command_line/scripts/specs/shell.sh index 704efa24ae..70a287bdde 100644 --- a/tests/command_line/scripts/specs/shell.sh +++ b/tests/command_line/scripts/specs/shell.sh @@ -13,319 +13,43 @@ sos shell # BASIC ############################################################# -whoami -#$ wait -pwd -#$ wait -cd Archive -#$ wait -cd Documents -#$ wait +#$ include ../shell/basic.sh ############################################################# # SWITCH ############################################################# -account new "$ACCOUNT_NAME_ALT" -#$ expect Choose a password -2 -#$ regex (?i)password -$ACCOUNT_PASSWORD -#$ regex (?i)password -$ACCOUNT_PASSWORD -#$ regex create a new account -y -#$ wait - -switch "$ACCOUNT_NAME_ALT" -#$ include ../includes/signin.sh -#$ wait - -switch "$ACCOUNT_NAME" -#$ include ../includes/signin.sh -#$ wait +#$ include ../shell/switch.sh ############################################################# # ACCOUNT ############################################################# -account ls -v -#$ wait - -account backup --force $ACCOUNT_BACKUP -#$ regex (?i)archive created -#$ wait - -account restore $ACCOUNT_BACKUP -#$ regex Overwrite all account -y -#$ include ../includes/signin.sh -#$ wait - -account info -v -#$ wait - -account stats -#$ wait - -account stats --json -#$ wait - -account rename -a $ACCOUNT_NAME NewDemo -#$ wait - -account rename -a NewDemo $ACCOUNT_NAME -#$ wait +#$ include ../shell/account.sh ############################################################# # MIGRATE ############################################################# -account migrate export --force target/demo-export.zip -#$ regex (?i)export unencrypted -y -#$ wait - -account migrate import --format onepassword.csv $MIGRATE_1PASSWORD -#$ regex (?i)imported -#$ wait - -account migrate import --format dashlane.zip $MIGRATE_DASHLANE -#$ regex (?i)imported -#$ wait - -account migrate import --format bitwarden.csv $MIGRATE_BITWARDEN -#$ regex (?i)imported -#$ wait - -account migrate import --format firefox.csv $MIGRATE_FIREFOX -#$ regex (?i)imported -#$ wait - -account migrate import --format macos.csv $MIGRATE_MACOS -#$ regex (?i)imported -#$ wait +#$ include ../shell/migrate.sh ############################################################# # CONTACTS ############################################################# -account contacts export --force $CONTACTS_EXPORT -#$ regex (?i)contacts exported -#$ wait - -account contacts import $ACCOUNT_CONTACTS -#$ regex (?i)contacts imported -#$ wait +#$ include ../shell/contacts.sh ############################################################# # FOLDER ############################################################# -folder new $FOLDER_NAME -#$ regex (?i)created -#$ wait - -folder ls -v -#$ wait - -folder info -v -#$ wait - -folder keys -f $FOLDER_NAME -#$ wait - -folder commits -f $FOLDER_NAME -#$ wait - -folder rename -f $FOLDER_NAME $NEW_FOLDER_NAME -#$ wait - -folder rename -f $NEW_FOLDER_NAME $FOLDER_NAME -#$ wait - -folder history compact -f $FOLDER_NAME -#$ regex (?i)remove history -y -#$ wait - -folder history check -f $FOLDER_NAME -#$ wait - -folder history list -f $FOLDER_NAME -#$ wait - -folder remove -f $FOLDER_NAME -#$ regex (?i)delete folder -y -#$ regex (?i)folder deleted -#$ wait +#$ include ../shell/folder.sh ############################################################# # SECRET ############################################################# -secret add note -#$ expect Name: -$NOTE_NAME -#$ expect >> -Text for the secret note. -#$ expect >> -#$ sendcontrol ^D -#$ wait - -secret add file -n "$FILE_NAME" "$FILE_INPUT" -#$ regex (?i)created -#$ wait - -secret add login --name "$LOGIN_NAME" -#$ expect Username: -$LOGIN_SERVICE_NAME -#$ expect Website: -$LOGIN_URL -#$ expect Password: -$LOGIN_PASSWORD -#$ regex (?i)created -#$ wait - -secret add list --name "$LIST_NAME" -#$ expect Key: -$LIST_KEY_1 -#$ expect Value: -$LIST_VALUE_1 -#$ expect Add more -y -#$ expect Key: -$LIST_KEY_2 -#$ expect Value: -$LIST_VALUE_2 -#$ expect Add more -n -#$ regex (?i)created -#$ wait - -secret ls -f Documents -#$ wait - -secret get "$NOTE_NAME" -#$ wait - -secret get "$FILE_NAME" -#$ wait - -secret get "$LOGIN_NAME" -#$ wait - -secret get "$LIST_NAME" -#$ wait - -secret copy "$NOTE_NAME" -#$ wait - -secret info "$NOTE_NAME" -#$ wait - -secret info --json "$NOTE_NAME" -#$ wait - -secret download --force "$FILE_NAME" "$FILE_OUTPUT" -#$ regex (?i)download complete -#$ wait - -secret attach add file --name "$FILE_ATTACHMENT" --path "$ATTACH_INPUT" "$NOTE_NAME" -#$ regex (?i)updated -#$ wait - -secret attach add note --name "$NOTE_ATTACHMENT" "$NOTE_NAME" -#$ expect >> -Text for the note attachment. -#$ expect >> -#$ sendcontrol ^D -#$ regex (?i)updated -#$ wait - -secret attach add link --name "$LINK_ATTACHMENT" "$NOTE_NAME" -#$ expect URL: -$LINK_VALUE -#$ regex (?i)updated -#$ wait - -secret attach add password --name "$PASSWORD_ATTACHMENT" "$NOTE_NAME" -#$ expect Password: -$PASSWORD_ATTACHMENT -#$ regex (?i)updated -#$ wait - -secret attach get "$NOTE_NAME" "$NOTE_ATTACHMENT" -#$ wait - -secret attach ls -v "$NOTE_NAME" -#$ wait - -secret attach rm "$NOTE_NAME" "$NOTE_ATTACHMENT" -#$ wait - -folder new $FOLDER_NAME -#$ regex (?i)created -#$ wait - -secret move --target "$FOLDER_NAME" "$NOTE_NAME" -#$ regex (?i)moved -#$ wait - -secret move --target "$DEFAULT_FOLDER_NAME" -f "$FOLDER_NAME" "$NOTE_NAME" -#$ regex (?i)moved -#$ wait - -folder remove -f $FOLDER_NAME -#$ regex (?i)delete folder -y -#$ regex (?i)folder deleted -#$ wait - -secret tags add --tags foo,bar,qux "$NOTE_NAME" -#$ wait - -secret tags list "$NOTE_NAME" -#$ wait - -secret tags rm --tags foo,bar "$NOTE_NAME" -#$ wait - -secret tags clear "$NOTE_NAME" -#$ wait - -secret favorite "$NOTE_NAME" -#$ wait - -secret favorite "$NOTE_NAME" -#$ wait - -secret archive "$NOTE_NAME" -#$ regex (?i)moved to archive -#$ wait - -secret unarchive "$NOTE_NAME" -#$ regex (?i)restored from archive -#$ wait - -secret comment --text 'Mock comment' "$NOTE_NAME" -#$ wait - -secret comment --text '' "$NOTE_NAME" -#$ wait - -secret rename --name "Demo Note" "$NOTE_NAME" -#$ wait - -secret rename --name "$NOTE_NAME" "Demo Note" -#$ wait - -secret remove "$NOTE_NAME" -#$ regex (?i)delete secret -y -#$ regex (?i)secret deleted -#$ wait +#$ include ../shell/secret.sh ############################################################# # TEARDOWN diff --git a/tests/pairing/device_revoke.rs b/tests/pairing/device_revoke.rs index 3360f122d3..bc9b89ec10 100644 --- a/tests/pairing/device_revoke.rs +++ b/tests/pairing/device_revoke.rs @@ -25,7 +25,9 @@ async fn pairing_device_revoke() -> Result<()> { let folders = primary_device.folders.clone(); let mut enrolled_account = - run_pairing_protocol(&mut primary_device, TEST_ID).await?; + run_pairing_protocol(&mut primary_device, TEST_ID, true) + .await? + .unwrap(); // Cannot revoke the current device let current_device_public_key = primary_device diff --git a/tests/pairing/main.rs b/tests/pairing/main.rs index 99badaeaf6..f61fd2e910 100644 --- a/tests/pairing/main.rs +++ b/tests/pairing/main.rs @@ -10,5 +10,8 @@ mod pairing_protocol; #[cfg(not(target_arch = "wasm32"))] mod pairing_websocket_shutdown; +#[cfg(not(target_arch = "wasm32"))] +mod pending_enrollment; + #[cfg(not(target_arch = "wasm32"))] pub use sos_test_utils as test_utils; diff --git a/tests/pairing/pairing_protocol.rs b/tests/pairing/pairing_protocol.rs index de0482ef32..d7da90dda3 100644 --- a/tests/pairing/pairing_protocol.rs +++ b/tests/pairing/pairing_protocol.rs @@ -31,7 +31,9 @@ async fn pairing_protocol() -> Result<()> { // Run the pairing protocol to completion. let mut enrolled_account = - run_pairing_protocol(&mut primary_device, TEST_ID).await?; + run_pairing_protocol(&mut primary_device, TEST_ID, true) + .await? + .unwrap(); // Sync on the original device to fetch the updated device logs assert!(primary_device.owner.sync().await.is_none()); diff --git a/tests/pairing/pending_enrollment.rs b/tests/pairing/pending_enrollment.rs new file mode 100644 index 0000000000..b2436dcf8f --- /dev/null +++ b/tests/pairing/pending_enrollment.rs @@ -0,0 +1,48 @@ +use crate::test_utils::{ + assert_local_remote_events_eq, mock, run_pairing_protocol, + simulate_device, spawn, teardown, +}; +use anyhow::Result; +use sos_net::{client::RemoteSync, sdk::prelude::*}; + +/// Tests that a pending enrollment that never finishes +/// has an enrollment.json file on disc. +#[tokio::test] +async fn pairing_pending_enrollment() -> Result<()> { + const TEST_ID: &str = "pairing_pending_enrollment"; + //crate::test_utils::init_tracing(); + + // Spawn a backend server and wait for it to be listening + let server = spawn(TEST_ID, None, None).await?; + + // Prepare mock devices + let mut primary_device = + simulate_device(TEST_ID, 2, Some(&server)).await?; + let origin = primary_device.origin.clone(); + let folders = primary_device.folders.clone(); + + // Create a secret in the primary owner which won't exist + // in the second device + let (meta, secret) = mock::note(TEST_ID, TEST_ID); + let result = primary_device + .owner + .create_secret(meta, secret, Default::default()) + .await?; + assert!(result.sync_error.is_none()); + + // Run the pairing protocol to completion. + run_pairing_protocol(&mut primary_device, TEST_ID, false).await?; + + // Data directory of the connecting device + let data_dir = primary_device.dirs.clients.get(1).cloned().unwrap(); + let paths = + Paths::new(data_dir, primary_device.owner.address().to_string()); + let enrollment_file = paths.enrollment(); + assert!(vfs::try_exists(enrollment_file).await?); + + primary_device.owner.sign_out().await?; + + teardown(TEST_ID).await; + + Ok(()) +} diff --git a/workspace/net/Cargo.toml b/workspace/net/Cargo.toml index 0ba704526a..9188758576 100644 --- a/workspace/net/Cargo.toml +++ b/workspace/net/Cargo.toml @@ -83,6 +83,7 @@ urlencoding.workspace = true parking_lot.workspace = true indexmap.workspace = true async-stream = "0.3" +colored = "2" # server toml = { workspace = true, optional = true } diff --git a/workspace/net/src/client/account/network_account.rs b/workspace/net/src/client/account/network_account.rs index fa1b8a6c29..c0b8204d48 100644 --- a/workspace/net/src/client/account/network_account.rs +++ b/workspace/net/src/client/account/network_account.rs @@ -99,6 +99,9 @@ pub struct NetworkAccount { /// Shutdown handle for the file transfers background task. file_transfers: Option<(UnboundedSender<()>, oneshot::Receiver<()>)>, + + /// Disable networking. + pub(crate) offline: bool, } impl NetworkAccount { @@ -241,10 +244,14 @@ impl NetworkAccount { } async fn before_change(&self) { - let remotes = self.remotes.read().await; - for remote in remotes.values() { - if let Some(e) = remote.sync().await { - tracing::error!(error = ?e, "failed to sync before change"); + if self.offline { + tracing::warn!("offline mode active, ignoring before change"); + } else { + let remotes = self.remotes.read().await; + for remote in remotes.values() { + if let Some(e) = remote.sync().await { + tracing::error!(error = ?e, "failed to sync before change"); + } } } } @@ -255,6 +262,11 @@ impl NetworkAccount { return Err(crate::sdk::Error::NotAuthenticated.into()); } + if self.offline { + tracing::warn!("offline mode active, ignoring file transfers"); + return Ok(()); + } + // Stop any existing transfers task self.stop_file_transfers().await; @@ -318,14 +330,15 @@ impl From<&NetworkAccount> for AccountRef { } } -#[async_trait] -impl Account for NetworkAccount { - type Account = NetworkAccount; - type Error = Error; - - async fn new_unauthenticated( +impl NetworkAccount { + /// Prepare an account for sign in. + /// + /// After preparing an account call `sign_in` + /// to authenticate a user. + pub async fn new_unauthenticated( address: Address, data_dir: Option, + offline: bool, ) -> Result { let account = LocalAccount::new_unauthenticated(address, data_dir).await?; @@ -339,18 +352,27 @@ impl Account for NetworkAccount { listeners: Mutex::new(Default::default()), connection_id: None, file_transfers: None, + offline, }) } - async fn new_account( + /// Create a new account with the given + /// name, passphrase and provider. + /// + /// Uses standard flags for the account builder for + /// more control of the created account use + /// `new_account_with_builder()`. + pub async fn new_account( account_name: String, passphrase: SecretString, data_dir: Option, + offline: bool, ) -> Result { Self::new_account_with_builder( account_name, passphrase, data_dir, + offline, |builder| { builder .save_passphrase(false) @@ -363,10 +385,14 @@ impl Account for NetworkAccount { .await } - async fn new_account_with_builder( + /// Create a new account with the given + /// name, passphrase and provider and modify the + /// account builder. + pub async fn new_account_with_builder( account_name: String, passphrase: SecretString, data_dir: Option, + offline: bool, builder: impl Fn(AccountBuilder) -> AccountBuilder + Send, ) -> Result { let account = LocalAccount::new_account_with_builder( @@ -387,10 +413,17 @@ impl Account for NetworkAccount { listeners: Mutex::new(Default::default()), connection_id: None, file_transfers: None, + offline, }; Ok(owner) } +} + +#[async_trait] +impl Account for NetworkAccount { + type Account = NetworkAccount; + type Error = Error; fn address(&self) -> &Address { &self.address diff --git a/workspace/net/src/client/account/sync.rs b/workspace/net/src/client/account/sync.rs index 32a4deead6..40d751812a 100644 --- a/workspace/net/src/client/account/sync.rs +++ b/workspace/net/src/client/account/sync.rs @@ -22,6 +22,11 @@ impl NetworkAccount { &self, options: &SyncOptions, ) -> HashMap>> { + if self.offline { + tracing::warn!("offline mode active, ignoring server status"); + return Default::default(); + } + let remotes = self.remotes.read().await; let mut server_status = HashMap::new(); for (origin, remote) in &*remotes { @@ -53,6 +58,11 @@ impl RemoteSync for NetworkAccount { &self, options: &SyncOptions, ) -> Option { + if self.offline { + tracing::warn!("offline mode active, ignoring sync"); + return None; + } + let _ = self.sync_lock.lock().await; let mut maybe_error: SyncError = Default::default(); let remotes = self.remotes.read().await; @@ -74,6 +84,13 @@ impl RemoteSync for NetworkAccount { &self, options: &SyncOptions, ) -> Option { + if self.offline { + tracing::warn!( + "offline mode active, ignoring sync file transfers" + ); + return None; + } + let _ = self.sync_lock.lock().await; let mut maybe_error: SyncError = Default::default(); let remotes = self.remotes.read().await; @@ -93,6 +110,11 @@ impl RemoteSync for NetworkAccount { } async fn patch_devices(&self) -> Option { + if self.offline { + tracing::warn!("offline mode active, ignoring patch devices"); + return None; + } + let _ = self.sync_lock.lock().await; let mut maybe_error: SyncError = Default::default(); let remotes = self.remotes.read().await; diff --git a/workspace/net/src/client/hashcheck.rs b/workspace/net/src/client/hashcheck.rs index b87117763a..0ad45af8e8 100644 --- a/workspace/net/src/client/hashcheck.rs +++ b/workspace/net/src/client/hashcheck.rs @@ -1,5 +1,5 @@ //! Check password hashes using the hashcheck service. -use super::Result; +use super::{is_offline, Result}; /// Default endpoint for HIBP database checks. const ENDPOINT: &str = "https://hashcheck.saveoursecrets.com"; @@ -10,6 +10,11 @@ pub async fn single( password_hash: String, host: Option, ) -> Result { + if is_offline() { + tracing::warn!("offline mode active, ignoring hashcheck"); + return Ok(false); + } + let host = host.unwrap_or_else(|| ENDPOINT.to_owned()); tracing::info!(host = %host, "hashcheck"); let url = format!("{}/{}", host, password_hash.to_uppercase()); @@ -28,6 +33,11 @@ pub async fn batch( hashes: &[String], host: Option, ) -> Result> { + if is_offline() { + tracing::warn!("offline mode active, ignoring batch hashcheck"); + return Ok(hashes.iter().map(|_| false).collect()); + } + let host = host.unwrap_or_else(|| ENDPOINT.to_owned()); tracing::info!(host = %host, "hashcheck"); let url = format!("{}/", host); diff --git a/workspace/net/src/client/mod.rs b/workspace/net/src/client/mod.rs index 0303d1c844..82f2e8d110 100644 --- a/workspace/net/src/client/mod.rs +++ b/workspace/net/src/client/mod.rs @@ -19,6 +19,12 @@ pub use sync::{RemoteSync, SyncError, SyncOptions}; /// Result type for the client module. pub type Result = std::result::Result; +/// Determine if the offline environment variable is set. +pub fn is_offline() -> bool { + use crate::sdk::constants::SOS_OFFLINE; + std::env::var(SOS_OFFLINE).ok().is_some() +} + #[cfg(any(feature = "listen", feature = "pairing"))] mod websocket_request { use super::Result; diff --git a/workspace/net/src/client/pairing/enrollment.rs b/workspace/net/src/client/pairing/enrollment.rs index c4186df554..2c9cf7c7f2 100644 --- a/workspace/net/src/client/pairing/enrollment.rs +++ b/workspace/net/src/client/pairing/enrollment.rs @@ -24,6 +24,7 @@ use crate::{ vfs, Paths, }, }; +use serde::{Deserialize, Serialize}; use std::{ collections::{HashMap, HashSet}, path::{Path, PathBuf}, @@ -35,6 +36,17 @@ use crate::sdk::{ sync::DevicePatch, }; +/// Pending enrollment written to disc between +/// fetching an account and finishing enrollment. +/// +/// Can be used to detect that an account was +/// created from an enrollment that was not finished. +#[derive(Debug, Serialize, Deserialize)] +pub struct PendingEnrollment { + /// Server origin the account was fetched from. + pub origin: Origin, +} + /// Enroll a device. /// /// Once pairing is completed call [DeviceEnrollment::fetch_account] @@ -127,6 +139,14 @@ impl DeviceEnrollment { #[cfg(feature = "device")] self.create_device(change_set.device).await?; self.create_identity(change_set.identity).await?; + + // Write the pending enrollment + let data = PendingEnrollment { + origin: self.origin.clone(), + }; + let contents = serde_json::to_vec_pretty(&data)?; + vfs::write(self.paths.enrollment(), &contents).await?; + Ok(()) } @@ -139,6 +159,7 @@ impl DeviceEnrollment { let mut account = NetworkAccount::new_unauthenticated( self.address.clone(), self.data_dir.clone(), + false, ) .await?; @@ -157,6 +178,9 @@ impl DeviceEnrollment { // Sign in to the new account account.sign_in(key).await?; + // Clean up the pending enrollment + vfs::remove_file(self.paths.enrollment()).await?; + // Sync to save the amended identity folder on the remote if let Some(e) = account.sync().await { tracing::error!(error = ?e); diff --git a/workspace/net/src/client/pairing/mod.rs b/workspace/net/src/client/pairing/mod.rs index 9116452386..08c4027daf 100644 --- a/workspace/net/src/client/pairing/mod.rs +++ b/workspace/net/src/client/pairing/mod.rs @@ -7,7 +7,7 @@ mod error; mod share_url; mod websocket; -pub use enrollment::DeviceEnrollment; +pub use enrollment::{DeviceEnrollment, PendingEnrollment}; pub use error::Error; pub use share_url::ServerPairUrl; pub use websocket::{AcceptPairing, OfferPairing}; diff --git a/workspace/net/src/server/server.rs b/workspace/net/src/server/server.rs index bc3e7e520e..d3e8dfd70a 100644 --- a/workspace/net/src/server/server.rs +++ b/workspace/net/src/server/server.rs @@ -7,7 +7,7 @@ use super::{ }, Backend, Result, ServerConfig, }; -use crate::sdk::storage::files::ExternalFile; +use crate::sdk::{storage::files::ExternalFile, UtcDateTime}; use axum::{ extract::Extension, http::{ @@ -20,6 +20,7 @@ use axum::{ Router, }; use axum_server::{tls_rustls::RustlsConfig, Handle}; +use colored::Colorize; use sos_sdk::signer::ecdsa::Address; use std::{ collections::{HashMap, HashSet}, @@ -135,19 +136,28 @@ impl Server { addr: &SocketAddr, tls: bool, ) { - tracing::info!(addr = %addr); - tracing::info!(tls = %tls); + let now = UtcDateTime::now().to_rfc3339().unwrap(); + println!("Started {}", now.yellow()); + println!("Listen {}", addr.to_string().yellow()); + println!("TLS enabled {}", tls.to_string().yellow()); + { let reader = state.read().await; if let Some(access) = &reader.config.access { if let Some(allow) = &access.allow { for address in allow { - tracing::info!(allow = %address); + println!( + "Allow {}", + address.to_string().green() + ); } } if let Some(deny) = &access.deny { for address in deny { - tracing::info!(deny = %address); + println!( + "Deny {}", + address.to_string().red() + ); } } } diff --git a/workspace/sdk/Cargo.toml b/workspace/sdk/Cargo.toml index 2cd316e424..689440e67b 100644 --- a/workspace/sdk/Cargo.toml +++ b/workspace/sdk/Cargo.toml @@ -59,6 +59,7 @@ parking_lot.workspace = true bs58.workspace = true indexmap.workspace = true bitflags.workspace = true +enum-iterator.workspace = true async_zip = { version = "0.0.16", default-features = false, features = ["deflate", "tokio"], optional = true } csv-async = { version = "1", features = ["tokio", "with_serde"], optional = true } sos-vfs = { version = "0.2.2" } diff --git a/workspace/sdk/src/account/account.rs b/workspace/sdk/src/account/account.rs index b3ed30f85d..57030b392a 100644 --- a/workspace/sdk/src/account/account.rs +++ b/workspace/sdk/src/account/account.rs @@ -205,37 +205,6 @@ pub trait Account { /// Errors for this account. type Error: std::error::Error + std::fmt::Debug; - /// Prepare an account for sign in. - /// - /// After preparing an account call `sign_in` - /// to authenticate a user. - async fn new_unauthenticated( - address: Address, - data_dir: Option, - ) -> std::result::Result; - - /// Create a new account with the given - /// name, passphrase and provider. - /// - /// Uses standard flags for the account builder for - /// more control of the created account use - /// `new_account_with_builder()`. - async fn new_account( - account_name: String, - passphrase: SecretString, - data_dir: Option, - ) -> std::result::Result; - - /// Create a new account with the given - /// name, passphrase and provider and modify the - /// account builder. - async fn new_account_with_builder( - account_name: String, - passphrase: SecretString, - data_dir: Option, - builder: impl Fn(AccountBuilder) -> AccountBuilder + Send, - ) -> std::result::Result; - /// Account address. fn address(&self) -> &Address; @@ -1246,12 +1215,12 @@ impl From<&LocalAccount> for AccountRef { } } -#[async_trait] -impl Account for LocalAccount { - type Account = LocalAccount; - type Error = Error; - - async fn new_unauthenticated( +impl LocalAccount { + /// Prepare an account for sign in. + /// + /// After preparing an account call `sign_in` + /// to authenticate a user. + pub async fn new_unauthenticated( address: Address, data_dir: Option, ) -> Result { @@ -1270,7 +1239,13 @@ impl Account for LocalAccount { }) } - async fn new_account( + /// Create a new account with the given + /// name, passphrase and provider. + /// + /// Uses standard flags for the account builder for + /// more control of the created account use + /// `new_account_with_builder()`. + pub async fn new_account( account_name: String, passphrase: SecretString, data_dir: Option, @@ -1284,7 +1259,10 @@ impl Account for LocalAccount { .await } - async fn new_account_with_builder( + /// Create a new account with the given + /// name, passphrase and provider and modify the + /// account builder. + pub async fn new_account_with_builder( account_name: String, passphrase: SecretString, data_dir: Option, @@ -1330,6 +1308,12 @@ impl Account for LocalAccount { Ok(account) } +} + +#[async_trait] +impl Account for LocalAccount { + type Account = LocalAccount; + type Error = Error; fn address(&self) -> &Address { &self.address diff --git a/workspace/sdk/src/account/preferences.rs b/workspace/sdk/src/account/preferences.rs index c7b1f11974..3bc2191040 100644 --- a/workspace/sdk/src/account/preferences.rs +++ b/workspace/sdk/src/account/preferences.rs @@ -10,7 +10,7 @@ use crate::{ }; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, path::PathBuf, sync::Arc}; +use std::{collections::HashMap, fmt, path::PathBuf, sync::Arc}; use tokio::sync::Mutex; static CACHE: Lazy>>>> = @@ -82,6 +82,26 @@ pub enum Preference { StringList(Vec), } +impl fmt::Display for Preference { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Bool(val) => write!(f, "{}", val), + Self::Number(val) => write!(f, "{}", val), + Self::String(val) => write!(f, "{}", val), + Self::StringList(val) => { + write!(f, "[")?; + for (index, s) in val.iter().enumerate() { + write!(f, r#""{}""#, s)?; + if index < val.len() - 1 { + write!(f, ", ")?; + } + } + write!(f, "]") + } + } + } +} + impl From for Preference { fn from(value: bool) -> Self { Self::Bool(value) diff --git a/workspace/sdk/src/constants.rs b/workspace/sdk/src/constants.rs index ea6b7e00f9..6c81d5a2c6 100644 --- a/workspace/sdk/src/constants.rs +++ b/workspace/sdk/src/constants.rs @@ -138,6 +138,10 @@ mod folders { /// File that tracks external file transfers. pub const TRANSFERS_FILE: &str = "transfers"; + /// File that marks a pairing process as pending. + #[cfg(feature = "device")] + pub const ENROLLMENT_FILE: &str = "enrollment"; + /// File thats stores account-level preferences. #[cfg(feature = "preferences")] pub const PREFERENCES_FILE: &str = "preferences"; @@ -153,7 +157,18 @@ mod files { pub const ARCHIVE_MANIFEST: &str = "sos-manifest.json"; } +/// Environment variables. +mod env_vars { + /// Sets the storage directory. + pub const SOS_DATA_DIR: &str = "SOS_DATA_DIR"; + /// Disable networking. + pub const SOS_OFFLINE: &str = "SOS_OFFLINE"; + /// Shell session prompt. + pub const SOS_PROMPT: &str = "SOS_PROMPT"; +} + pub use self::urn::*; +pub use env_vars::*; pub use extensions::*; pub use files::*; pub use folders::*; diff --git a/workspace/sdk/src/date_time.rs b/workspace/sdk/src/date_time.rs index f44c939e62..2cc0bbe9a4 100644 --- a/workspace/sdk/src/date_time.rs +++ b/workspace/sdk/src/date_time.rs @@ -34,6 +34,11 @@ impl Default for UtcDateTime { } impl UtcDateTime { + /// Create a UTC date time for now. + pub fn now() -> Self { + Default::default() + } + /// Create from a calendar date. pub fn from_calendar_date( year: i32, diff --git a/workspace/sdk/src/device.rs b/workspace/sdk/src/device.rs index 7cc287feb2..b4495e2224 100644 --- a/workspace/sdk/src/device.rs +++ b/workspace/sdk/src/device.rs @@ -178,12 +178,6 @@ pub struct DeviceMetaData { impl Default for DeviceMetaData { fn default() -> Self { let mut info = BTreeMap::new(); - info.insert("realname".to_owned(), Value::String(whoami::realname())); - info.insert("username".to_owned(), Value::String(whoami::username())); - info.insert( - "device_name".to_owned(), - Value::String(whoami::devicename()), - ); info.insert("hostname".to_owned(), Value::String(whoami::hostname())); info.insert( "platform".to_owned(), diff --git a/workspace/sdk/src/migrate/import/mod.rs b/workspace/sdk/src/migrate/import/mod.rs index 5eb60e7c4f..fbd1ef24ff 100644 --- a/workspace/sdk/src/migrate/import/mod.rs +++ b/workspace/sdk/src/migrate/import/mod.rs @@ -1,4 +1,5 @@ //! Import secrets from other providers and software. +use enum_iterator::Sequence; use crate::migrate::{Error, Result}; use futures::StreamExt; use serde::de::DeserializeOwned; @@ -29,7 +30,7 @@ pub(crate) async fn read_csv_records< } /// File formats supported for import. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Sequence)] pub enum ImportFormat { /// 1Password CSV file. OnePasswordCsv, diff --git a/workspace/sdk/src/storage/paths.rs b/workspace/sdk/src/storage/paths.rs index 6e45462ff6..8ada40d8f5 100644 --- a/workspace/sdk/src/storage/paths.rs +++ b/workspace/sdk/src/storage/paths.rs @@ -5,6 +5,7 @@ use app_dirs2::{get_app_root, AppDataType, AppInfo}; #[cfg(feature = "audit")] use async_once_cell::OnceCell; use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; use std::{ path::{Path, PathBuf}, sync::RwLock, @@ -50,7 +51,7 @@ static AUDIT_LOG: OnceCell> = OnceCell::new(); /// Several functions require a user identifier and will panic if /// a user identifier has not been set, see the function documentation /// for details. -#[derive(Default, Debug, Clone)] +#[derive(Default, Debug, Clone, Serialize, Deserialize)] pub struct Paths { /// User identifier. user_id: String, @@ -385,6 +386,22 @@ impl Paths { vault_path } + /// Path to the file used to mark a pending enrollment. + /// + /// # Panics + /// + /// If this set of paths are global (no user identifier). + #[cfg(feature = "device")] + pub fn enrollment(&self) -> PathBuf { + use crate::constants::ENROLLMENT_FILE; + if self.is_global() { + panic!("enrollment is not accessible for global paths"); + } + let mut vault_path = self.user_dir.join(ENROLLMENT_FILE); + vault_path.set_extension(JSON_EXT); + vault_path + } + /// Path to the file used to store account-level preferences. /// /// # Panics diff --git a/workspace/test_utils/src/network.rs b/workspace/test_utils/src/network.rs index 4535f7bbfb..04946dc7b3 100644 --- a/workspace/test_utils/src/network.rs +++ b/workspace/test_utils/src/network.rs @@ -59,6 +59,7 @@ impl SimulatedDevice { let mut owner = NetworkAccount::new_unauthenticated( self.owner.address().clone(), Some(data_dir.clone()), + false, ) .await?; @@ -115,6 +116,7 @@ pub async fn simulate_device_with_builder( test_id.to_owned(), password.clone(), Some(data_dir.clone()), + false, builder, ) .await?; diff --git a/workspace/test_utils/src/pairing.rs b/workspace/test_utils/src/pairing.rs index 91c1905e09..7d2a4167a9 100644 --- a/workspace/test_utils/src/pairing.rs +++ b/workspace/test_utils/src/pairing.rs @@ -15,7 +15,8 @@ use tokio::sync::mpsc; pub async fn run_pairing_protocol( primary_device: &mut SimulatedDevice, _test_id: &str, -) -> Result { + finish_enrollment: bool, +) -> Result> { let origin = primary_device.origin.clone(); let password = primary_device.password.clone(); let key: AccessKey = password.into(); @@ -80,7 +81,11 @@ pub async fn run_pairing_protocol( enrollment.fetch_account().await?; - Ok(enrollment.finish(&key).await?) + if finish_enrollment { + Ok(Some(enrollment.finish(&key).await?)) + } else { + Ok(None) + } } /// Run the inverted pairing protocol to completion.